001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.bcel.classfile;
021
022import java.io.DataInput;
023import java.io.DataOutputStream;
024import java.io.IOException;
025import java.util.Arrays;
026
027import org.apache.bcel.Const;
028import org.apache.bcel.util.Args;
029
030/**
031 * This class is derived from <em>Attribute</em> and represents the list of modules required, exported, opened or
032 * provided by a module. There may be at most one Module attribute in a ClassFile structure.
033 *
034 * @see Attribute
035 * @since 6.4.0
036 */
037public final class Module extends Attribute {
038
039    /**
040     * The module file name extension.
041     *
042     * @since 6.7.0
043     */
044    public static final String EXTENSION = ".jmod";
045
046    private static String getClassNameAtIndex(final ConstantPool cp, final int index, final boolean compactClassName) {
047        final String className = cp.getConstantString(index, Const.CONSTANT_Class);
048        if (compactClassName) {
049            return Utility.compactClassName(className, false);
050        }
051        return className;
052    }
053    private final int moduleNameIndex;
054    private final int moduleFlags;
055
056    private final int moduleVersionIndex;
057    private ModuleRequires[] requiresTable;
058    private ModuleExports[] exportsTable;
059    private ModuleOpens[] opensTable;
060    private final int usesCount;
061    private final int[] usesIndex;
062
063    private ModuleProvides[] providesTable;
064
065    /**
066     * Constructs object from input stream.
067     *
068     * @param nameIndex Index in constant pool.
069     * @param length Content length in bytes.
070     * @param dataInput Input stream.
071     * @param constantPool Array of constants.
072     * @throws IOException Thrown if an I/O error occurs.
073     */
074    Module(final int nameIndex, final int length, final DataInput dataInput, final ConstantPool constantPool) throws IOException {
075        super(Const.ATTR_MODULE, nameIndex, length, constantPool);
076
077        moduleNameIndex = dataInput.readUnsignedShort();
078        moduleFlags = dataInput.readUnsignedShort();
079        moduleVersionIndex = dataInput.readUnsignedShort();
080
081        final int requiresCount = dataInput.readUnsignedShort();
082        requiresTable = new ModuleRequires[requiresCount];
083        for (int i = 0; i < requiresCount; i++) {
084            requiresTable[i] = new ModuleRequires(dataInput);
085        }
086
087        final int exportsCount = dataInput.readUnsignedShort();
088        exportsTable = new ModuleExports[exportsCount];
089        for (int i = 0; i < exportsCount; i++) {
090            exportsTable[i] = new ModuleExports(dataInput);
091        }
092
093        final int opensCount = dataInput.readUnsignedShort();
094        opensTable = new ModuleOpens[opensCount];
095        for (int i = 0; i < opensCount; i++) {
096            opensTable[i] = new ModuleOpens(dataInput);
097        }
098
099        usesIndex = ClassParser.readU2U2Table(dataInput);
100        usesCount = usesIndex.length;
101
102        final int providesCount = dataInput.readUnsignedShort();
103        providesTable = new ModuleProvides[providesCount];
104        for (int i = 0; i < providesCount; i++) {
105            providesTable[i] = new ModuleProvides(dataInput);
106        }
107    }
108
109    /**
110     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
111     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
112     *
113     * @param v Visitor object.
114     */
115    @Override
116    public void accept(final Visitor v) {
117        v.visitModule(this);
118    }
119
120    /**
121     * @return deep copy of this attribute.
122     */
123    @Override
124    public Attribute copy(final ConstantPool constantPool) {
125        final Module c = (Module) clone();
126
127        c.requiresTable = new ModuleRequires[requiresTable.length];
128        Arrays.setAll(c.requiresTable, i -> requiresTable[i].copy());
129
130        c.exportsTable = new ModuleExports[exportsTable.length];
131        Arrays.setAll(c.exportsTable, i -> exportsTable[i].copy());
132
133        c.opensTable = new ModuleOpens[opensTable.length];
134        Arrays.setAll(c.opensTable, i -> opensTable[i].copy());
135
136        c.providesTable = new ModuleProvides[providesTable.length];
137        Arrays.setAll(c.providesTable, i -> providesTable[i].copy());
138
139        c.setConstantPool(constantPool);
140        return c;
141    }
142
143    /**
144     * Dumps Module attribute to file stream in binary format.
145     *
146     * @param file Output file stream.
147     * @throws IOException Thrown if an I/O error occurs.
148     */
149    @Override
150    public void dump(final DataOutputStream file) throws IOException {
151        super.dump(file);
152
153        file.writeShort(moduleNameIndex);
154        file.writeShort(moduleFlags);
155        file.writeShort(moduleVersionIndex);
156
157        file.writeShort(Args.requireU2(requiresTable.length, "requiresTable.length"));
158        for (final ModuleRequires entry : requiresTable) {
159            entry.dump(file);
160        }
161
162        file.writeShort(Args.requireU2(exportsTable.length, "exportsTable.length"));
163        for (final ModuleExports entry : exportsTable) {
164            entry.dump(file);
165        }
166
167        file.writeShort(Args.requireU2(opensTable.length, "opensTable.length"));
168        for (final ModuleOpens entry : opensTable) {
169            entry.dump(file);
170        }
171
172        file.writeShort(Args.requireU2(usesIndex.length, "usesIndex.length"));
173        for (final int entry : usesIndex) {
174            file.writeShort(entry);
175        }
176
177        file.writeShort(Args.requireU2(providesTable.length, "providesTable.length"));
178        for (final ModuleProvides entry : providesTable) {
179            entry.dump(file);
180        }
181    }
182
183    /**
184     * Gets the table of exported interfaces.
185     *
186     * @return table of exported interfaces.
187     * @see ModuleExports
188     */
189    public ModuleExports[] getExportsTable() {
190        return exportsTable;
191    }
192
193    /**
194     * Gets flags for this module.
195     *
196     * @return module flags.
197     * @since 6.10.0
198     */
199    public int getModuleFlags() {
200        return moduleFlags;
201    }
202
203    /**
204     * Gets module name.
205     *
206     * @param cp Array of constants.
207     * @return module name.
208     * @since 6.10.0
209     */
210    public String getModuleName(final ConstantPool cp) {
211        return cp.getConstantString(moduleNameIndex, Const.CONSTANT_Module);
212    }
213
214    /**
215     * Gets the table of open interfaces.
216     *
217     * @return table of provided interfaces.
218     * @see ModuleOpens
219     */
220    public ModuleOpens[] getOpensTable() {
221        return opensTable;
222    }
223
224    /**
225     * Gets the table of provided interfaces.
226     *
227     * @return table of provided interfaces.
228     * @see ModuleProvides
229     */
230    public ModuleProvides[] getProvidesTable() {
231        return providesTable;
232    }
233
234    /**
235     * Gets the table of required modules.
236     *
237     * @return table of required modules.
238     * @see ModuleRequires
239     */
240    public ModuleRequires[] getRequiresTable() {
241        return requiresTable;
242    }
243
244    /**
245     * Gets the array of class names for this module's uses.
246     *
247     * @param constantPool Array of constants usually obtained from the ClassFile object.
248     * @param compactClassName false for original constant pool value, true to replace '/' with '.'.
249     * @return array of used class names.
250     * @since 6.10.0
251     */
252    public String[] getUsedClassNames(final ConstantPool constantPool, final boolean compactClassName) {
253        final String[] usedClassNames = new String[usesCount];
254        for (int i = 0; i < usesCount; i++) {
255            usedClassNames[i] = getClassNameAtIndex(constantPool, usesIndex[i], compactClassName);
256        }
257        return usedClassNames;
258    }
259
260    /**
261     * Gets version for this module.
262     *
263     * @param cp Array of constants.
264     * @return version from constant pool, "0" if version index is 0.
265     * @since 6.10.0
266     */
267    public String getVersion(final ConstantPool cp) {
268        return moduleVersionIndex == 0 ? "0" : cp.getConstantString(moduleVersionIndex, Const.CONSTANT_Utf8);
269    }
270
271    /**
272     * @return String representation, that is, a list of packages.
273     */
274    @Override
275    public String toString() {
276        final ConstantPool cp = super.getConstantPool();
277        final StringBuilder buf = new StringBuilder();
278        buf.append("Module:\n");
279        buf.append("  name:    ").append(Utility.pathToPackage(getModuleName(cp))).append("\n");
280        buf.append("  flags:   ").append(String.format("%04x", moduleFlags)).append("\n");
281        final String version = getVersion(cp);
282        buf.append("  version: ").append(version).append("\n");
283
284        buf.append("  requires(").append(requiresTable.length).append("):\n");
285        for (final ModuleRequires module : requiresTable) {
286            buf.append("    ").append(module.toString(cp)).append("\n");
287        }
288
289        buf.append("  exports(").append(exportsTable.length).append("):\n");
290        for (final ModuleExports module : exportsTable) {
291            buf.append("    ").append(module.toString(cp)).append("\n");
292        }
293
294        buf.append("  opens(").append(opensTable.length).append("):\n");
295        for (final ModuleOpens module : opensTable) {
296            buf.append("    ").append(module.toString(cp)).append("\n");
297        }
298
299        buf.append("  uses(").append(usesIndex.length).append("):\n");
300        for (final int index : usesIndex) {
301            final String className = getClassNameAtIndex(cp, index, true);
302            buf.append("    ").append(className).append("\n");
303        }
304
305        buf.append("  provides(").append(providesTable.length).append("):\n");
306        for (final ModuleProvides module : providesTable) {
307            buf.append("    ").append(module.toString(cp)).append("\n");
308        }
309
310        return buf.substring(0, buf.length() - 1); // remove the last newline
311    }
312}