1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.module; 27 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.lang.module.ModuleDescriptor.Version; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 import jdk.internal.org.objectweb.asm.Attribute; 40 import jdk.internal.org.objectweb.asm.ClassReader; 41 import jdk.internal.org.objectweb.asm.ClassVisitor; 42 import jdk.internal.org.objectweb.asm.ClassWriter; 43 import jdk.internal.org.objectweb.asm.Opcodes; 44 45 import static jdk.internal.module.ClassFileAttributes.*; 46 47 /** 48 * Utility class to extend a module-info.class with additional attributes. 49 */ 50 51 public final class ModuleInfoExtender { 52 53 // the input stream to read the original module-info.class 54 private final InputStream in; 55 56 // the packages in the ModulePackages attribute 57 private Set<String> packages; 58 59 // the value of the module_version in Module attribute 60 private Version version; 61 62 // the value of the ModuleMainClass attribute 63 private String mainClass; 64 65 // the values for the ModuleTarget attribute 66 private String osName; 67 private String osArch; 68 private String osVersion; 69 70 // the hashes for the ModuleHashes attribute 71 private ModuleHashes hashes; 72 73 // the value of the ModuleResolution attribute 74 private ModuleResolution moduleResolution; 75 76 private ModuleInfoExtender(InputStream in) { 77 this.in = in; 78 } 79 80 /** 81 * Sets the set of packages for the ModulePackages attribute 82 */ 83 public ModuleInfoExtender packages(Set<String> packages) { 84 this.packages = Collections.unmodifiableSet(packages); 85 return this; 86 } 87 88 /** 89 * Sets the value of the module_version in Module attribute. 90 */ 91 public ModuleInfoExtender version(Version version) { 92 this.version = version; 93 return this; 94 } 95 96 /** 97 * Sets the value of the ModuleMainClass attribute. 98 */ 99 public ModuleInfoExtender mainClass(String mainClass) { 100 this.mainClass = mainClass; 101 return this; 102 } 103 104 /** 105 * Sets the values for the ModuleTarget attribute. 106 */ 107 public ModuleInfoExtender targetPlatform(String osName, 108 String osArch, 109 String osVersion) { 110 this.osName = osName; 111 this.osArch = osArch; 112 this.osVersion = osVersion; 113 return this; 114 } 115 116 /** 117 * The ModuleHashes attribute will be emitted to the module-info with 118 * the hashes encapsulated in the given {@code ModuleHashes} 119 * object. 120 */ 121 public ModuleInfoExtender hashes(ModuleHashes hashes) { 122 this.hashes = hashes; 123 return this; 124 } 125 126 /** 127 * Sets the value for the ModuleResolution attribute. 128 */ 129 public ModuleInfoExtender moduleResolution(ModuleResolution mres) { 130 this.moduleResolution = mres; 131 return this; 132 } 133 134 /** 135 * A ClassVisitor that supports adding class file attributes. If an 136 * attribute already exists then the first occurence of the attribute 137 * is replaced. 138 */ 139 private static class AttributeAddingClassVisitor extends ClassVisitor { 140 private Map<String, Attribute> attrs = new HashMap<>(); 141 142 AttributeAddingClassVisitor(int api, ClassVisitor cv) { 143 super(api, cv); 144 } 145 146 void addAttribute(Attribute attr) { 147 attrs.put(attr.type, attr); 148 } 149 150 @Override 151 public void visitAttribute(Attribute attr) { 152 String name = attr.type; 153 Attribute replacement = attrs.get(name); 154 if (replacement != null) { 155 attr = replacement; 156 attrs.remove(name); 157 } 158 super.visitAttribute(attr); 159 } 160 161 /** 162 * Adds any remaining attributes that weren't replaced to the 163 * class file. 164 */ 165 void finish() { 166 attrs.values().forEach(a -> super.visitAttribute(a)); 167 attrs.clear(); 168 } 169 } 170 171 /** 172 * Outputs the modified module-info.class to the given output stream. 173 * Once this method has been called then the Extender object should 174 * be discarded. 175 */ 176 public void write(OutputStream out) throws IOException { 177 // emit to the output stream 178 out.write(toByteArray()); 179 } 180 181 /** 182 * Returns the bytes of the modified module-info.class. 183 * Once this method has been called then the Extender object should 184 * be discarded. 185 */ 186 public byte[] toByteArray() throws IOException { 187 ClassWriter cw 188 = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); 189 190 AttributeAddingClassVisitor cv 191 = new AttributeAddingClassVisitor(Opcodes.ASM5, cw); 192 193 ClassReader cr = new ClassReader(in); 194 195 if (packages != null) 196 cv.addAttribute(new ModulePackagesAttribute(packages)); 197 if (mainClass != null) 198 cv.addAttribute(new ModuleMainClassAttribute(mainClass)); 199 if (osName != null || osArch != null || osVersion != null) 200 cv.addAttribute(new ModuleTargetAttribute(osName, osArch, osVersion)); 201 if (hashes != null) 202 cv.addAttribute(new ModuleHashesAttribute(hashes)); 203 if (moduleResolution != null) 204 cv.addAttribute(new ModuleResolutionAttribute(moduleResolution.value())); 205 206 List<Attribute> attrs = new ArrayList<>(); 207 208 // prototypes of attributes that should be parsed 209 attrs.add(new ModuleAttribute(version)); 210 attrs.add(new ModulePackagesAttribute()); 211 attrs.add(new ModuleMainClassAttribute()); 212 attrs.add(new ModuleTargetAttribute()); 213 attrs.add(new ModuleHashesAttribute()); 214 215 cr.accept(cv, attrs.toArray(new Attribute[0]), 0); 216 217 // add any attributes that didn't replace previous attributes 218 cv.finish(); 219 220 return cw.toByteArray(); 221 } 222 223 /** 224 * Returns an {@code Extender} that may be used to add additional 225 * attributes to the module-info.class read from the given input 226 * stream. 227 */ 228 public static ModuleInfoExtender newExtender(InputStream in) { 229 return new ModuleInfoExtender(in); 230 } 231 232 }