/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.utility.classfile; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.util.Collection; import java.util.HashSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.eclipse.persistence.tools.workbench.utility.ClassTools; import org.eclipse.persistence.tools.workbench.utility.Classpath; import org.eclipse.persistence.tools.workbench.utility.classfile.descriptor.ObjectType; import org.eclipse.persistence.tools.workbench.utility.classfile.tools.ClassFileDataInputStream; import org.eclipse.persistence.tools.workbench.utility.io.IndentingPrintWriter; /** * This class models the information held in a Java .class file. * * See "The Java Virtual Machine Specification" Chapter 4. */ /* * TODO * ClassFileCodeAttribute - byte codes are simply read in to a byte array * may want to manipulate and *write* the byte codes... */ public class ClassFile { private Header header; private ConstantPool constantPool; private ClassDeclaration declaration; private FieldPool fieldPool; private MethodPool methodPool; private AttributePool attributePool; // ********** static methods ********** /** * Construct a class file from the specified stream, * closing the stream once the class file has been built. * Also note that we buffer the stream to improve performance. */ public static ClassFile fromInputStream(InputStream stream) throws IOException { ClassFile classFile = null; try { // I can't seem to remember to buffer, so I put this here... ~bjv stream = new BufferedInputStream(stream); classFile = new ClassFile(stream); } finally { if (stream != null) { stream.close(); } } return classFile; } /** * Construct a class file from the specified JAR entry. * Use this method if you are processing a number of JAR entries and * want to keep the JAR file open for the duration of the processing. */ public static ClassFile fromArchiveEntry(JarFile jarFile, JarEntry jarEntry) throws IOException { return ClassFile.fromInputStream(jarFile.getInputStream(jarEntry)); } /** * Construct a class file from the specified JAR entry. * Use this method if you are processing a number of JAR entries and * want to keep the JAR file open for the duration of the processing. */ public static ClassFile fromArchiveEntry(JarFile jarFile, String className) throws IOException { String entryName = Classpath.convertToArchiveClassFileEntryName(className); return ClassFile.fromInputStream(jarFile.getInputStream(jarFile.getJarEntry(entryName))); } /** * Construct a class file from the specified JAR entry. * Use this method if you are processing a single JAR entry, * because we will close the JAR file file when we are finished * building the class file. */ public static ClassFile fromArchiveEntry(File file, String className) throws IOException { JarFile jarFile = null; try { jarFile = new JarFile(file); return ClassFile.fromArchiveEntry(jarFile, className); } finally { if (jarFile != null) { jarFile.close(); } } } /** * Construct a class file from the specified file. */ public static ClassFile fromFile(File file) throws IOException { return ClassFile.fromInputStream(new FileInputStream(file)); } /** * Construct a class file from the specified class file. */ public static ClassFile fromClassFile(File classpathEntryDirectory, String className) throws IOException { return ClassFile.fromFile(new File(classpathEntryDirectory, Classpath.convertToClassFileName(className))); } /** * Construct a class file for the specified class. */ public static ClassFile forClass(File classPathEntry, String className) throws IOException { if (Classpath.fileNameIsArchive(classPathEntry.getPath())) { return ClassFile.fromArchiveEntry(classPathEntry, className); } return ClassFile.fromClassFile(classPathEntry, className); } /** * Construct a class file for the specified class. */ public static ClassFile forClass(Class javaClass) throws IOException { return ClassFile.forClass(new File(Classpath.locationFor(javaClass)), javaClass.getName()); } // ********** constructor ********** /** * Construct a class file from the specified stream of byte codes. * The stream will remain open after the class file has been built. */ public ClassFile(InputStream stream) throws IOException { super(); this.initialize(new ClassFileDataInputStream(stream)); } // ********** instance methods ********** private void initialize(ClassFileDataInputStream stream) throws IOException { this.header = new Header(stream); this.constantPool = new ConstantPool(stream); this.declaration = new ClassDeclaration(stream, this.constantPool); this.fieldPool = new FieldPool(stream, this); this.methodPool = new MethodPool(stream, this); this.attributePool = new AttributePool(stream, this); if (this.isNestedClass()) { this.declaration.setStandardAccessFlagsForNestedClass(this.nestedClassAccessFlags()); } } public String className() { return this.declaration.thisClassName(); } public String displayString() { StringWriter sw = new StringWriter(2000); IndentingPrintWriter writer = new IndentingPrintWriter(sw); this.displayStringOn(writer); return sw.toString(); } public void displayStringOn(IndentingPrintWriter writer) { writer.print("ClassFile: "); writer.println(this.className()); writer.indent(); this.header.displayStringOn(writer); this.constantPool.displayStringOn(writer); this.declaration.displayStringOn(writer); this.fieldPool.displayStringOn(writer); this.methodPool.displayStringOn(writer); this.attributePool.displayStringOn(writer); writer.undent(); } /** * Return the set of flags that will match those * returned by java.lang.Class#getModifiers() */ public short standardAccessFlags() { return this.declaration.standardAccessFlags(); } /** * Return whether the class is an interface. */ public boolean isInterface() { return this.declaration.isInterface(); } /** * Return whether the class is a class, * as opposed to an interface. */ public boolean isClass() { return this.declaration.isClass(); } public String superClassName() { return this.declaration.superClassName(); } public String[] interfaceNames() { return this.declaration.interfaceNames(); } public boolean isDeprecated() { return this.attributePool.isDeprecated(); } /** * Return whether the class was generated by the compiler * and has no Java source code corresponding to it. * "A class member that does not appear in the source code * must be marked using a Synthetic attribute, or else it must * have its ACC_SYNTHETIC bit set." */ public boolean isSynthetic() { return this.declaration.isSynthetic() || this.attributePool.isSynthetic(); } /** * There is only one "top-level" class per Java source file. */ public boolean isTopLevelClass() { return ! this.isNestedClass(); } /** * Return whether the class is a "nested" class, meaning * the class is either a "member" class, a "local" class, or * an "anonymous" class. */ public boolean isNestedClass() { return this.attributePool.isNestedClass(); } /** * Return whether the class is a "member" class, * i.e. it is a peer of the "outer" class's fields and methods. */ public boolean isMemberClass() { return this.attributePool.isMemberClass(); } /** * Return whether the class is a "local" class, * i.e. it is a peer of the local variables in one of * the "outer" class's methods. */ public boolean isLocalClass() { return this.attributePool.isLocalClass(); } /** * Return whether the class is an "anonymous" class, * i.e. it is a defined in an expression. */ public boolean isAnonymousClass() { return this.attributePool.isAnonymousClass(); } public String sourceFileName() { return this.attributePool.sourceFileName(); } /** * If the class is an "member" class, return the name of its * "outer" class. */ public String declaringClassName() { return this.attributePool.declaringClassName(); } /** * If the class is a "member" or "local" class, return its name * relative to the scope of its "outer" class. */ public String nestedClassName() { return this.attributePool.nestedClassName(); } /** * If the class is a "nested" class, return the access flags * associated with the class as a "nested" class, as opposed * to the access flags declared in the class's declaration. */ public short nestedClassAccessFlags() { return this.attributePool.nestedClassAccessFlags(); } /** * Return the names of the class's "nested" classes; this * includes "member", "local", and "anonymous" classes. */ public String[] nestedClassNames() { return this.attributePool.nestedClassNames(); } /** * Return the names of the class's "member" classes. These * are the classes that are declared as members of * the class file's class (i.e. the classes are peers to the * class's fields and methods). */ public String[] declaredMemberClassNames() { return this.attributePool.declaredMemberClassNames(); } /** * Return the names of all the classes referenced directly by the * class file's class. NB: This will NOT detect classes that are * referenced indirectly. Classes that are loaded dynamically * by name (e.g. Class.forName("com.foo.Bar")) will not be detected. * Unfortunately, this is what happens when referencing a class * in source code via the .class technique (e.g. com.foo.Bar.class). * Also, classes have indirect references to any superclasses of * directly-referenced classes. Therefore the list returned by this * method cannot be used to determine compile-time dependencies, * nevermind run-time dependences. */ public String[] referencedClassNames() { return new ReferencedClassNamesVisitor(this).referencedClassNames(); } public void accept(Visitor visitor) { visitor.visit(this); this.header.accept(visitor); this.constantPool.accept(visitor); this.declaration.accept(visitor); this.fieldPool.accept(visitor); this.methodPool.accept(visitor); this.attributePool.accept(visitor); } public Header getHeader() { return this.header; } public ConstantPool getConstantPool() { return this.constantPool; } public ClassDeclaration getDeclaration() { return this.declaration; } public FieldPool getFieldPool() { return this.fieldPool; } public MethodPool getMethodPool() { return this.methodPool; } public AttributePool getAttributePool() { return this.attributePool; } public String toString() { return ClassTools.shortClassNameForObject(this) + '(' + this.className() + ')'; } // ********** member classes ********** /** * This class models a class file header: * u4 magic; * u2 minor_version; * u2 major_version; * * See "The Java Virtual Machine Specification" Chapter 4. */ public static class Header extends Object { /** Java tag */ private int magic; /** this the class file format version, not the class version */ private short minorVersion; private short majorVersion; /** * Construct a class file header from the specified stream * of byte codes. */ Header(ClassFileDataInputStream stream) throws IOException { super(); this.initialize(stream); } private void initialize(ClassFileDataInputStream stream) throws IOException { this.magic = stream.readU4(); if (this.magic != 0xCAFEBABE) { throw new IOException("bad magic"); } this.minorVersion = stream.readU2(); this.majorVersion = stream.readU2(); } public String displayString() { StringWriter sw = new StringWriter(); IndentingPrintWriter writer = new IndentingPrintWriter(sw); this.displayStringOn(writer); return sw.toString(); } public void displayStringOn(IndentingPrintWriter writer) { writer.println("Header"); writer.indent(); writer.print("magic: 0x"); writer.println(this.magicString()); writer.print("class file format version: "); writer.println(this.version()); writer.undent(); } public String magicString() { return Integer.toHexString(this.magic).toUpperCase(); } public float version() { float temp = this.minorVersion; while (temp > 1) { temp = temp / 10; } return this.majorVersion + temp; } public String versionString() { return String.valueOf(this.version()); } public void accept(Visitor visitor) { visitor.visit(this); } public int getMagic() { return this.magic; } public short getMinorVersion() { return this.minorVersion; } public short getMajorVersion() { return this.majorVersion; } public String toString() { return ClassTools.shortClassNameForObject(this) + "(class file version: " + this.version() + ')'; } } /** * Example visitor that gathers up all the referenced class names in the * class file. */ private static class ReferencedClassNamesVisitor extends VisitorAdapter { private final Collection referencedClassNames = new HashSet(200); ReferencedClassNamesVisitor(ClassFile classFile) { super(); classFile.accept(this); } public void visit(ObjectType objectType) { this.addReferencedClassName(objectType.elementTypeName()); } private void addReferencedClassName(String className) { if (ClassTools.classNamedIsReference(className)) { this.referencedClassNames.add(className); } } String[] referencedClassNames() { return (String[]) this.referencedClassNames.toArray(new String[this.referencedClassNames.size()]); } } }