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