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