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