--- /dev/null
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2001 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache BCEL" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache BCEL", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.aspectj.apache.bcel.classfile;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.aspectj.apache.bcel.Constants;
+import org.aspectj.apache.bcel.classfile.Module.Export;
+
+/**
+ * This class is derived from <em>Attribute</em> and represents the module
+ * information captured in a class file.
+ * http://cr.openjdk.java.net/~mr/jigsaw/spec/lang-vm.html
+ *
+ * @author Andy Clement
+ */
+public final class Module extends Attribute {
+
+ private static final String[] NO_MODULE_NAMES = {};
+
+ private byte[] moduleInfo;
+ private int ptr;
+ private boolean unpacked = false;
+ private Require[] requires;
+ private Export[] exports;
+ private Uses[] uses;
+ private Provide[] provides;
+
+ /**
+ * Build a Module attribute from a previously Unknown attribute.
+ */
+ public Module(Unknown unknown) {
+ super(unknown.getTag(), unknown.getNameIndex(), unknown.getLength(), unknown.getConstantPool());
+ moduleInfo = unknown.getBytes();
+ }
+
+ public class Require {
+
+ private final int moduleNameIndex;
+ private final int requiresFlags;
+
+ public Require(int moduleNameIndex, int requiresFlags) {
+ this.moduleNameIndex = moduleNameIndex;
+ this.requiresFlags = requiresFlags;
+ }
+
+ public String getModuleName() {
+ return cpool.getConstantUtf8(moduleNameIndex).getStringValue();
+ }
+
+ public int getRequiresFlags() {
+ return requiresFlags;
+ }
+
+ public String getRequiresFlagsAsString() {
+ StringBuilder s = new StringBuilder();
+ if ((requiresFlags & Constants.MODULE_ACC_PUBLIC)!=0) {
+ s.append("public ");
+ }
+ if ((requiresFlags & Constants.MODULE_ACC_SYNTHETIC)!=0) {
+ s.append("synthetic ");
+ }
+ if ((requiresFlags & Constants.MODULE_ACC_MANDATED)!=0) {
+ s.append("mandated ");
+ }
+ return s.toString();
+ }
+
+ public String toString() {
+ return "requires "+getRequiresFlagsAsString()+getModuleName();
+ }
+
+ }
+
+
+ public class Export {
+
+ private final int exportedPackageNameIndex;
+ private final int[] toModuleNameIndices;
+
+ public Export(int exportedPackageNameIndex, int[] toModuleNameIndices) {
+ this.exportedPackageNameIndex = exportedPackageNameIndex;
+ this.toModuleNameIndices = toModuleNameIndices;
+ }
+
+ public String getExportedPackage() {
+ return cpool.getConstantUtf8(exportedPackageNameIndex).getStringValue();
+ }
+
+ public String[] getToModuleNames() {
+ if (toModuleNameIndices==null) {
+ return NO_MODULE_NAMES;
+ }
+ String[] toModuleNames = new String[toModuleNameIndices.length];
+ for (int i=0;i<toModuleNameIndices.length;i++) {
+ toModuleNames[i] = cpool.getConstantUtf8(toModuleNameIndices[i]).getStringValue();
+ }
+ return toModuleNames;
+ }
+
+ public String toString() {
+ StringBuilder s =new StringBuilder();
+ s.append("exports ").append(getExportedPackage().replace('/', '.'));
+ String[] toModules = getToModuleNames();
+ if (toModules.length!=0) {
+ s.append(" to ");
+ for (int i=0;i<toModules.length;i++) {
+ if (i>0) {
+ s.append(", ");
+ }
+ s.append(toModules[i]);
+ }
+ }
+ return s.toString().trim();
+ }
+ }
+
+ public class Provide {
+ private final int providedTypeIndex;
+ private final int withTypeIndex;
+
+ public Provide(int providedTypeIndex, int withTypeIndex) {
+ this.providedTypeIndex = providedTypeIndex;
+ this.withTypeIndex = withTypeIndex;
+ }
+
+ public String getProvidedType() {
+ return cpool.getConstantString_CONSTANTClass(providedTypeIndex);
+ }
+
+ public int getProvidedTypeIndex() {
+ return providedTypeIndex;
+ }
+
+ public String getWithType() {
+ return cpool.getConstantString_CONSTANTClass(withTypeIndex);
+ }
+
+ public int getWithTypeIndex() {
+ return withTypeIndex;
+ }
+
+ public String toString() {
+ StringBuilder s =new StringBuilder();
+ s.append("provides ").append(getProvidedType().replace('/', '.'));
+ s.append(" with ").append(getWithType().replace('/','.'));
+ return s.toString();
+ }
+ }
+
+ public class Uses {
+ private final int typeNameIndex;
+
+ public Uses(int typeNameIndex) {
+ this.typeNameIndex = typeNameIndex;
+ }
+
+ public String getTypeName() {
+ return cpool.getConstantString_CONSTANTClass(typeNameIndex);
+ }
+
+ public int getTypeNameIndex() {
+ return typeNameIndex;
+ }
+
+ public String toString() {
+ StringBuilder s =new StringBuilder();
+ s.append("uses ").append(getTypeName().replace('/', '.'));
+ return s.toString().trim();
+ }
+ }
+
+ private final int readInt() {
+ return ((moduleInfo[ptr++] & 0xFF) << 24) + ((moduleInfo[ptr++] & 0xFF) << 16)
+ + ((moduleInfo[ptr++] & 0xFF) << 8) + (moduleInfo[ptr++] & 0xFF);
+ }
+
+ private final int readUnsignedShort() {
+ return ((moduleInfo[ptr++] & 0xff) << 8) + (moduleInfo[ptr++] & 0xff);
+ }
+
+ private final int readUnsignedShort(int offset) {
+ return ((moduleInfo[offset++] & 0xff) << 8) + (moduleInfo[offset] & 0xff);
+ }
+
+ private void ensureUnpacked() {
+ if (!unpacked) {
+ ptr = 0;
+ int count = readUnsignedShort();
+ requires = new Require[count];
+ for (int i = 0; i < count; i++) {
+ requires[i] = new Require(readUnsignedShort(), readUnsignedShort());
+ }
+ count = readUnsignedShort();
+ exports = new Export[count];
+ for (int i = 0; i < count; i++) {
+ int index = readUnsignedShort();
+ int toCount = readUnsignedShort();
+ int[] to = new int[toCount];
+ for (int j = 0; j < toCount; j++) {
+ to[j] = readUnsignedShort();
+ }
+ exports[i] = new Export(index, to);
+ }
+ count = readUnsignedShort();
+ uses = new Uses[count];
+ for (int i = 0; i < count; i++) {
+ uses[i] = new Uses(readUnsignedShort());
+ }
+ count = readUnsignedShort();
+ provides = new Provide[count];
+ for (int i = 0; i < count; i++) {
+ provides[i] = new Provide(readUnsignedShort(), readUnsignedShort());
+ }
+ unpacked = true;
+ }
+ }
+
+ @Override
+ public final void dump(DataOutputStream file) throws IOException {
+ super.dump(file);
+ if (!unpacked) {
+ file.write(moduleInfo);
+ } else {
+ file.writeShort(requires.length);
+ for (int i = 0; i < requires.length; i++) {
+ file.writeShort(requires[i].moduleNameIndex);
+ file.writeShort(requires[i].requiresFlags);
+ }
+ file.writeShort(exports.length);
+ for (Export export : exports) {
+ file.writeShort(export.exportedPackageNameIndex);
+ int[] toIndices = export.toModuleNameIndices;
+ file.writeShort(toIndices.length);
+ for (int index : toIndices) {
+ file.writeShort(index);
+ }
+ }
+ file.writeShort(uses.length);
+ for (Uses use : uses) {
+ file.writeShort(use.getTypeNameIndex());
+ }
+ file.writeShort(provides.length);
+ for (Provide provide : provides) {
+ file.writeShort(provide.providedTypeIndex);
+ file.writeShort(provide.withTypeIndex);
+ }
+ }
+ }
+
+ public String toStringRequires() {
+ StringBuilder s = new StringBuilder();
+ s.append('#').append(requires.length);
+ if (requires.length > 0) {
+ for (Require require : requires) {
+ s.append(' ');
+ s.append(require.moduleNameIndex).append(':').append(require.requiresFlags);
+ }
+ }
+ return s.toString();
+ }
+
+ public String toStringExports() {
+ StringBuilder s = new StringBuilder();
+ s.append('#').append(exports.length);
+ if (exports.length > 0) {
+ for (Export export : exports) {
+ s.append(' ');
+ s.append(export.exportedPackageNameIndex).append(":[");
+ int[] toIndices = export.toModuleNameIndices;
+ for (int i = 0; i < toIndices.length; i++) {
+ if (i > 0)
+ s.append(',');
+ s.append(toIndices[i]);
+ }
+ s.append("]");
+ }
+ }
+ return s.toString();
+ }
+
+ public String toStringUses() {
+ StringBuilder s = new StringBuilder();
+ s.append('#').append(uses.length);
+ if (uses.length > 0) {
+ for (Uses use : uses) {
+ s.append(' ');
+ s.append(use.getTypeName());
+ }
+ }
+ return s.toString();
+ }
+
+ public String toStringProvides() {
+ StringBuilder s = new StringBuilder();
+ s.append('#').append(provides.length);
+ if (provides.length > 0) {
+ for (Provide provide : provides) {
+ s.append(' ');
+ s.append(provide.providedTypeIndex).append(':').append(provide.withTypeIndex);
+ }
+ }
+ return s.toString();
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder s = new StringBuilder();
+ ensureUnpacked();
+ s.append("Module(");
+ if (requires.length != 0) {
+ s.append("requires=");
+ s.append(toStringRequires());
+ s.append(" ");
+ }
+ if (exports.length != 0) {
+ s.append("exports=");
+ s.append(toStringExports());
+ s.append(" ");
+ }
+ if (uses.length != 0) {
+ s.append("uses=");
+ s.append(toStringUses());
+ s.append(" ");
+ }
+ if (provides.length != 0) {
+ s.append("provides=");
+ s.append(toStringProvides());
+ s.append(" ");
+ }
+ return s.toString().trim()+")";
+ }
+
+ /**
+ * @return deep copy of this attribute //
+ */
+ // @Override
+ // public Attribute copy(ConstantPool constant_pool) {
+ // return (SourceFile) clone();
+ // }
+ @Override
+ public void accept(ClassVisitor v) {
+ v.visitSourceFile(this);
+ }
+
+ public Require[] getRequires() {
+ ensureUnpacked();
+ return requires;
+ }
+
+ public String[] getRequiredModuleNames() {
+ ensureUnpacked();
+ String[] results = new String[requires.length];
+ for (int i=0;i<requires.length;i++) {
+ results[i] = cpool.getConstantUtf8(requires[i].moduleNameIndex).getStringValue();
+ }
+ return results;
+ }
+
+ public byte[] getBytes() {
+ return moduleInfo;
+ }
+
+ public Export[] getExports() {
+ ensureUnpacked();
+ return exports;
+ }
+
+ public Uses[] getUses() {
+ ensureUnpacked();
+ return uses;
+ }
+
+ public Provide[] getProvides() {
+ ensureUnpacked();
+ return provides;
+ }
+}
--- /dev/null
+/* *******************************************************************
+ * Copyright (c) 2016 Contributors
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement - initial implementation
+ * ******************************************************************/
+
+package org.aspectj.apache.bcel.classfile.tests;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+
+import org.aspectj.apache.bcel.Constants;
+import org.aspectj.apache.bcel.classfile.Attribute;
+import org.aspectj.apache.bcel.classfile.ClassParser;
+import org.aspectj.apache.bcel.classfile.JavaClass;
+import org.aspectj.apache.bcel.classfile.Module;
+import org.aspectj.apache.bcel.classfile.Module.Export;
+import org.aspectj.apache.bcel.classfile.Module.Provide;
+import org.aspectj.apache.bcel.classfile.Module.Require;
+import org.aspectj.apache.bcel.classfile.Module.Uses;
+import org.aspectj.apache.bcel.classfile.SourceFile;
+import org.aspectj.apache.bcel.classfile.Unknown;
+
+/**
+ * http://cr.openjdk.java.net/~mr/jigsaw/spec/lang-vm.html
+ *
+ * @author Andy Clement
+ */
+public class ModuleTest extends BcelTestCase {
+
+ public void testLoadSimpleModuleClass() throws Exception {
+ String moduleFilename = "testdata/modules/one/module-info.class";
+ ClassParser classParser = new ClassParser(moduleFilename);
+ JavaClass javaClass = classParser.parse();
+ assertNotNull(javaClass);
+ assertEquals(Constants.MAJOR_1_9,javaClass.getMajor());
+ assertEquals(Constants.MINOR_1_9,javaClass.getMinor());
+ assertEquals(Constants.ACC_MODULE,javaClass.getModifiers());
+ assertEquals(0,javaClass.getSuperclassNameIndex());
+ assertEquals(0,javaClass.getInterfaceIndices().length);
+ assertEquals(0,javaClass.getFields().length);
+ assertEquals(0,javaClass.getMethods().length);
+ Attribute[] attrs = javaClass.getAttributes();
+ assertEquals(2,attrs.length);
+ SourceFile sourceFile = (SourceFile) getAttribute(attrs,Constants.ATTR_SOURCE_FILE);
+ Module moduleAttr = new Module((Unknown)getAttribute(attrs,Constants.ATTR_UNKNOWN));
+ byte[] originalData = moduleAttr.getBytes();
+ String[] requiredModuleNames = moduleAttr.getRequiredModuleNames();
+ assertEquals(1,requiredModuleNames.length);
+ assertEquals("java.base",requiredModuleNames[0]);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ moduleAttr.dump(new DataOutputStream(baos));
+ byte[] newData = baos.toByteArray();
+ // The 6 offset here is because the newdata includes the 2byte cpool pointer for the name 'Module'
+ // and the 4byte int length field for the attribute data
+ if (newData.length!=originalData.length+6) {
+ fail("Expected the length of the original attribute ("+originalData.length+") to match the new written length ("+newData.length+")");
+ }
+ for (int i=0;i<originalData.length;i++) {
+ if (originalData[i]!=newData[i+6]) {
+ fail("byte mismatch at position "+i+" of "+newData.length);
+ }
+ }
+ }
+
+
+ public void testRequires() throws Exception {
+ Module moduleAttr = getModuleAttribute("testdata/modules/two/d/module-info.class");
+ Require[] requires = moduleAttr.getRequires();
+ assertEquals(3,requires.length);
+ assertEquals("requires mandated java.base",requires[0].toString());
+ assertEquals("requires a.b.c",requires[1].toString());
+ assertEquals("requires public b.c.d",requires[2].toString());
+ assertEquals("java.base",requires[0].getModuleName());
+ assertEquals("a.b.c",requires[1].getModuleName());
+ assertEquals("b.c.d",requires[2].getModuleName());
+ }
+
+ public void testExports() throws Exception {
+ Module moduleAttr = getModuleAttribute("testdata/modules/two/e/module-info.class");
+ Export[] exports = moduleAttr.getExports();
+ assertEquals(3,exports.length);
+ assertEquals("exports com.foo1",exports[0].toString());
+ assertEquals("exports com.foo2 to a.b.c",exports[1].toString());
+ assertEquals("exports com.foo3 to b.c.d, a.b.c",exports[2].toString());
+ assertEquals("com/foo1",exports[0].getExportedPackage());
+ assertEquals("com/foo2",exports[1].getExportedPackage());
+ assertEquals("com/foo3",exports[2].getExportedPackage());
+ assertEquals("a.b.c",exports[1].getToModuleNames()[0]);
+ assertEquals("b.c.d",exports[2].getToModuleNames()[0]);
+ assertEquals("a.b.c",exports[2].getToModuleNames()[1]);
+ }
+
+ public void testUses() throws Exception {
+ Module moduleAttr = getModuleAttribute("testdata/modules/two/f/module-info.class");
+ Uses[] uses = moduleAttr.getUses();
+ assertEquals(1,uses.length);
+ assertEquals("com/foo1/I1",uses[0].getTypeName());
+ assertEquals("uses com.foo1.I1",uses[0].toString());
+ }
+
+ public void testProvides() throws Exception {
+ Module moduleAttr = getModuleAttribute("testdata/modules/two/g/module-info.class");
+ Provide[] provides = moduleAttr.getProvides();
+ assertEquals(2,provides.length);
+ assertEquals("provides com.foo1.I1 with com.foo1.C1",provides[0].toString());
+ assertEquals("provides com.foo2.I2 with com.foo2.C2",provides[1].toString());
+ assertEquals("com/foo1/I1",provides[0].getProvidedType());
+ assertEquals("com/foo1/C1",provides[0].getWithType());
+ assertEquals("com/foo2/I2",provides[1].getProvidedType());
+ assertEquals("com/foo2/C2",provides[1].getWithType());
+ }
+
+ // ---
+
+ private Module getModuleAttribute(String moduleInfoClass) throws Exception {
+ ClassParser classParser = new ClassParser(moduleInfoClass);
+ JavaClass javaClass = classParser.parse();
+ Module moduleAttr = new Module((Unknown)getAttribute(javaClass.getAttributes(),Constants.ATTR_UNKNOWN));
+ return moduleAttr;
+ }
+
+}