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