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 for the module version in the 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 packages for the ModulePackages attribute 82 * 83 * @apiNote This method does not check that the package names are legal 84 * package names or that the set of packages is a super set of the 85 * packages in the module. 86 */ 87 public ModuleInfoExtender packages(Set<String> packages) { 88 this.packages = Collections.unmodifiableSet(packages); 89 return this; 90 } 91 92 /** 93 * Sets the value for the module version in the Module attribute 94 */ 95 public ModuleInfoExtender version(Version version) { 96 this.version = version; 97 return this; 98 } 99 100 /** 101 * Sets the value of the ModuleMainClass attribute. 102 * 103 * @apiNote This method does not check that the main class is a legal 104 * class name in a named package. 105 */ 106 public ModuleInfoExtender mainClass(String mainClass) { 107 this.mainClass = mainClass; 108 return this; 109 } 110 111 /** 112 * Sets the values for the ModuleTarget attribute. 113 */ 114 public ModuleInfoExtender targetPlatform(String osName, 115 String osArch, 116 String osVersion) { 117 this.osName = osName; 118 this.osArch = osArch; 119 this.osVersion = osVersion; 120 return this; 121 } 122 123 /** 124 * The ModuleHashes attribute will be emitted to the module-info with 125 * the hashes encapsulated in the given {@code ModuleHashes} 126 * object. 127 */ 128 public ModuleInfoExtender hashes(ModuleHashes hashes) { 129 this.hashes = hashes; 130 return this; 131 } 132 133 /** 134 * Sets the value for the ModuleResolution attribute. 135 */ 136 public ModuleInfoExtender moduleResolution(ModuleResolution mres) { 137 this.moduleResolution = mres; 138 return this; 139 } 140 141 /** 142 * A ClassVisitor that supports adding class file attributes. If an 143 * attribute already exists then the first occurrence of the attribute 144 * is replaced. 145 */ 146 private static class AttributeAddingClassVisitor extends ClassVisitor { 147 private Map<String, Attribute> attrs = new HashMap<>(); 148 149 AttributeAddingClassVisitor(int api, ClassVisitor cv) { 150 super(api, cv); 151 } 152 153 void addAttribute(Attribute attr) { 154 attrs.put(attr.type, attr); 155 } 156 157 @Override 158 public void visitAttribute(Attribute attr) { 159 String name = attr.type; 160 Attribute replacement = attrs.get(name); 161 if (replacement != null) { 162 attr = replacement; 163 attrs.remove(name); 164 } 165 super.visitAttribute(attr); 166 } 167 168 /** 169 * Adds any remaining attributes that weren't replaced to the 170 * class file. 171 */ 172 void finish() { 173 attrs.values().forEach(a -> super.visitAttribute(a)); 174 attrs.clear(); 175 } 176 } 177 178 /** 179 * Outputs the modified module-info.class to the given output stream. 180 * Once this method has been called then the Extender object should 181 * be discarded. 182 */ 183 public void write(OutputStream out) throws IOException { 184 // emit to the output stream 185 out.write(toByteArray()); 186 } 187 188 /** 189 * Returns the bytes of the modified module-info.class. 190 * Once this method has been called then the Extender object should 191 * be discarded. 192 */ 193 public byte[] toByteArray() throws IOException { 194 ClassWriter cw 195 = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); 196 197 AttributeAddingClassVisitor cv 198 = new AttributeAddingClassVisitor(Opcodes.ASM5, cw); 199 200 ClassReader cr = new ClassReader(in); 201 202 if (packages != null) 203 cv.addAttribute(new ModulePackagesAttribute(packages)); 204 if (mainClass != null) 205 cv.addAttribute(new ModuleMainClassAttribute(mainClass)); 206 if (osName != null || osArch != null || osVersion != null) 207 cv.addAttribute(new ModuleTargetAttribute(osName, osArch, osVersion)); 208 if (hashes != null) 209 cv.addAttribute(new ModuleHashesAttribute(hashes)); 210 if (moduleResolution != null) 211 cv.addAttribute(new ModuleResolutionAttribute(moduleResolution.value())); 212 213 List<Attribute> attrs = new ArrayList<>(); 214 215 // prototypes of attributes that should be parsed 216 attrs.add(new ModuleAttribute(version)); 217 attrs.add(new ModulePackagesAttribute()); 218 attrs.add(new ModuleMainClassAttribute()); 219 attrs.add(new ModuleTargetAttribute()); 220 attrs.add(new ModuleHashesAttribute()); 221 222 cr.accept(cv, attrs.toArray(new Attribute[0]), 0); 223 224 // add any attributes that didn't replace previous attributes 225 cv.finish(); 226 227 return cw.toByteArray(); 228 } 229 230 /** 231 * Returns an {@code Extender} that may be used to add additional 232 * attributes to the module-info.class read from the given input 233 * stream. 234 */ 235 public static ModuleInfoExtender newExtender(InputStream in) { 236 return new ModuleInfoExtender(in); 237 } 238 239 }