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