blob: dae030c7b55e6ce77dd66aca90529de694a57455 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Contributor(s): Thomas Ball
*/
package org.netbeans.modules.classfile;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
/**
* Class representing a Java class file.
*
* @author Thomas Ball
*/
public class ClassFile {
private static final Logger LOG = Logger.getLogger(ClassFile.class.getName());
ConstantPool constantPool;
int classAccess;
CPClassInfo classInfo;
CPClassInfo superClassInfo;
CPClassInfo[] interfaces;
Variable[] variables;
Method[] methods;
String sourceFileName;
InnerClass[] innerClasses;
BootstrapMethod[] bootstrapMethods;
Module module;
List<String> modulePackages;
ClassName moduleMainClz;
ModuleTarget moduleTarget;
private AttributeMap attributes;
private Map<ClassName,Annotation> annotations;
short majorVersion;
short minorVersion;
String typeSignature;
EnclosingMethod enclosingMethod;
private boolean includeCode = false;
/** size of buffer in buffered input streams */
private static final int BUFFER_SIZE = 4096;
private static final Set<String> badNonJavaClassNames =
new HashSet<String>(Arrays.asList(new String[] {";","[","."})); //NOI18N
/**
* Create a new ClassFile object.
* @param classData an InputStream from which the defining bytes of this
* class or interface are read.
* @throws IOException if InputStream can't be read, or if the class data
* is malformed.
*/
public ClassFile(InputStream classData) throws IOException {
this(classData, true);
}
/**
* Create a new ClassFile object.
* @param classFileName the path of a class file.
* @throws IOException if file cannot be opened or read.
**/
public ClassFile(String classFileName) throws IOException {
this(classFileName, true);
}
/**
* Create a new ClassFile object.
* @param file a File instance of a class file.
* @param includeCode true if this classfile should support operations
* at the bytecode level. Specify false to conserve
* memory if code access isn't needed.
* @throws IOException if file cannot be opened or read.
**/
public ClassFile(File file, boolean includeCode) throws IOException {
InputStream is = null;
this.includeCode = includeCode;
if( file == null || !file.exists() )
throw new FileNotFoundException(file != null ?
file.getPath() : "null");
try {
is = new BufferedInputStream( new FileInputStream( file ), BUFFER_SIZE);
load(is);
} catch (InvalidClassFormatException e) {
throw new InvalidClassFormatException(file.getPath() + '(' +
e.getMessage() + ')');
} finally {
if (is != null)
is.close();
}
}
/**
* Create a new ClassFile object.
* @param classData an InputStream from which the defining bytes of this
* class or interface are read.
* @param includeCode true if this classfile should support operations
* at the bytecode level. Specify false to conserve
* memory if code access isn't needed.
* @throws IOException if InputStream can't be read, or if the class data
* is malformed.
*/
public ClassFile(InputStream classData, boolean includeCode) throws IOException {
if (classData == null)
throw new IOException("input stream not specified");
this.includeCode = includeCode;
try {
load(classData);
} catch (IndexOutOfBoundsException e) {
throw new InvalidClassFormatException("invalid classfile format");
}
}
/**
* Create a new ClassFile object.
* @param classFileName the path of a class file.
* @param includeCode true if this classfile should support operations
* at the bytecode level. Specify false to conserve
* memory if code access isn't needed.
* @throws IOException if file cannot be opened or read.
**/
public ClassFile(String classFileName, boolean includeCode) throws IOException {
InputStream in = null;
this.includeCode = includeCode;
try {
if (classFileName == null)
throw new IOException("input stream not specified");
in = new BufferedInputStream(new FileInputStream(classFileName), BUFFER_SIZE);
load(in);
} catch (InvalidClassFormatException e) {
throw new InvalidClassFormatException(classFileName + '(' +
e.getMessage() + ')');
} finally {
if (in != null)
in.close();
}
}
/** Returns the ConstantPool object associated with this ClassFile.
* @return the constant pool object
*/
public final ConstantPool getConstantPool() {
return constantPool;
}
private void load(InputStream classData) throws IOException {
try {
DataInputStream in = new DataInputStream(classData);
constantPool = loadClassHeader(in);
interfaces = getCPClassList(in, constantPool);
variables = Variable.loadFields(in, constantPool, this);
methods = Method.loadMethods(in, constantPool, this, includeCode);
attributes = AttributeMap.load(in, constantPool);
} catch (IOException ioe) {
throw new InvalidClassFormatException(ioe);
} catch (ClassCastException cce) {
//May throw CCE when the class file format is broken,
//see issue #211402 - the MethodInfo is on place of ClassInfo
throw new InvalidClassFormatException(cce);
}
}
private ConstantPool loadClassHeader(DataInputStream in) throws IOException {
int magic = in.readInt();
if (magic != 0xCAFEBABE) {
throw new InvalidClassFormatException();
}
minorVersion = in.readShort();
majorVersion = in.readShort();
int count = in.readUnsignedShort();
ConstantPool pool = new ConstantPool(count, in);
classAccess = in.readUnsignedShort();
classInfo = pool.getClass(in.readUnsignedShort());
if (classInfo == null)
throw new InvalidClassFormatException();
if (isBadNonJavaClassName(classInfo.getName())) {
throw new InvalidClassFormatException(
String.format(
"Invalid non java class name: %s", //NOI18N
classInfo.getName()));
}
int index = in.readUnsignedShort();
if (index != 0) // true for java.lang.Object
superClassInfo = pool.getClass(index);
return pool;
}
static CPClassInfo[] getCPClassList(DataInputStream in, ConstantPool pool)
throws IOException {
int count = in.readUnsignedShort();
CPClassInfo[] classes = new CPClassInfo[count];
for (int i = 0; i < count; i++) {
classes[i] = pool.getClass(in.readUnsignedShort());
}
return classes;
}
/**
* Returns the access permissions of this class or interface.
* @return a mask of access flags.
* @see org.netbeans.modules.classfile.Access
*/
public final int getAccess() {
return classAccess;
}
/** Returns the name of this class.
* @return the name of this class.
*/
public final ClassName getName() {
return classInfo.getClassName();
}
/** Returns the name of this class's superclass. A string is returned
* instead of a ClassFile object to reduce object creation.
* @return the name of the superclass of this class.
*/
public final ClassName getSuperClass() {
if (superClassInfo == null)
return null;
return superClassInfo.getClassName();
}
/**
* @return a collection of Strings describing this class's interfaces.
*/
public final Collection<ClassName> getInterfaces() {
List<ClassName> l = new ArrayList<ClassName>();
int n = interfaces.length;
for (int i = 0; i < n; i++)
l.add(interfaces[i].getClassName());
return l;
}
/**
* Looks up a variable by its name.
*
* NOTE: this method only looks up variables defined by this class,
* and not inherited from its superclass.
*
* @param name the name of the variable
* @return the variable,or null if no such variable in this class.
*/
public final Variable getVariable(String name) {
int n = variables.length;
for (int i = 0; i < n; i++) {
Variable v = variables[i];
if (v.getName().equals(name))
return v;
}
return null;
}
/**
* @return a Collection of Variable objects representing the fields
* defined by this class.
*/
public final Collection<Variable> getVariables() {
return Arrays.asList(variables);
}
/**
* @return the number of variables defined by this class.
*/
public final int getVariableCount() {
return variables.length;
}
/**
* Looks up a method by its name and type signature, as defined
* by the Java Virtual Machine Specification, section 4.3.3.
*
* NOTE: this method only looks up methods defined by this class,
* and not methods inherited from its superclass.
*
* @param name the name of the method
* @param signature the method's type signature
* @return the method, or null if no such method in this class.
*/
public final Method getMethod(String name, String signature) {
int n = methods.length;
for (int i = 0; i < n; i++) {
Method m = methods[i];
if (m.getName().equals(name) && m.getDescriptor().equals(signature))
return m;
}
return null;
}
/**
* @return a Collection of Method objects representing the methods
* defined by this class.
*/
public final Collection<Method> getMethods() {
return Arrays.asList(methods);
}
/**
* @return the number of methods defined by this class.
*/
public final int getMethodCount() {
return methods.length;
}
/**
* @return the name of the source file the compiler used to create this class.
*/
public final String getSourceFileName() {
if (sourceFileName == null) {
DataInputStream in = attributes.getStream("SourceFile"); // NOI18N
if (in != null) {
try {
int ipool = in.readUnsignedShort();
CPUTF8Info entry = (CPUTF8Info)constantPool.get(ipool);
sourceFileName = entry.getName();
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid SourceFile attribute", e);
}
}
}
return sourceFileName;
}
public final boolean isDeprecated() {
return attributes.get("Deprecated") != null;
}
public final boolean isSynthetic() {
return (classAccess & Access.SYNTHETIC) == Access.SYNTHETIC ||
attributes.get("Synthetic") != null;
}
/**
* Returns true if this class is an annotation type.
*/
public final boolean isAnnotation() {
return (classAccess & Access.ANNOTATION) == Access.ANNOTATION;
}
/**
* Returns true if this class defines an enum type.
*/
public final boolean isEnum() {
return (classAccess & Access.ENUM) == Access.ENUM;
}
/**
* Returns true if this class defines a module.
* @since 1.51
*/
public final boolean isModule() {
return (classAccess & Access.MODULE) == Access.MODULE;
}
/**
* Returns a map of the raw attributes for this classfile.
* Field attributes are
* not returned in this map.
*
* @see org.netbeans.modules.classfile.Field#getAttributes
*/
public final AttributeMap getAttributes(){
return attributes;
}
public final Collection<InnerClass> getInnerClasses(){
if (innerClasses == null) {
DataInputStream in = attributes.getStream("InnerClasses"); // NOI18N
if (in != null) {
try {
innerClasses =
InnerClass.loadInnerClasses(in, constantPool);
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid InnerClasses attribute", e);
}
} else
innerClasses = new InnerClass[0];
}
return Arrays.asList(innerClasses);
}
/**Return the content of the <code>BootstrapMethods</code> attribute.
*
* @return
* @since 1.40
*/
public final List<BootstrapMethod> getBootstrapMethods(){
if (bootstrapMethods == null) {
DataInputStream in = attributes.getStream("BootstrapMethods"); // NOI18N
if (in != null) {
try {
bootstrapMethods =
BootstrapMethod.loadBootstrapMethod(in, constantPool);
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid InnerClasses attribute", e);
}
} else
bootstrapMethods = new BootstrapMethod[0];
}
return Arrays.asList(bootstrapMethods);
}
/**
* Returns the content of the <code>Module</code> attribute.
* @return the {@link Module} or null when there is no <code>Module</code> attribute.
* @since 1.51
*/
public final Module getModule() {
if (module == null) {
final DataInputStream in = attributes.getStream("Module"); //NOI18N
if (in != null) {
try {
try {
module = new Module (in, constantPool);
} finally {
in.close();
}
} catch (LegacyClassFile legacy) {
LOG.warning(legacy.getMessage());
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid Module attribute", e);
}
}
}
return module;
}
/**
Returns the content of the <code>ModulePackages</code> attribute.
* @return the {@link List> of packages or null when there is no <code>ModulePackages</code> attribute.
* @since 1.53
*/
public final List<String> getModulePackages() {
if (modulePackages == null) {
final DataInputStream in = attributes.getStream("ModulePackages"); //NOI18N
if (in != null) {
try {
try {
int cnt = in.readUnsignedShort();
String[] pkgs = new String[cnt];
for (int i=0; i < cnt; i++) {
pkgs[i] = ((CPPackageInfo)constantPool.get(in.readUnsignedShort())).getName();
}
modulePackages = Collections.unmodifiableList(Arrays.asList(pkgs));
} finally {
in.close();
}
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid ModulePackages attribute", e);
}
}
}
return modulePackages;
}
/**
Returns the content of the <code>ModuleMainClass</code> attribute.
* @return the module main class or null when there is no <code>ModuleMainClass</code> attribute.
* @since 1.53
*/
public final ClassName getModuleMainClass() {
if (moduleMainClz == null) {
final DataInputStream in = attributes.getStream("ModuleMainClass"); //NOI18N
if (in != null) {
try {
try {
moduleMainClz = ((CPClassInfo)constantPool.get(in.readUnsignedShort())).getClassName();
} finally {
in.close();
}
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid ModuleMainClass attribute", e);
}
}
}
return moduleMainClz;
}
/**
* Returns the content of the <code>ModuleTarget</code> attribute.
* @return the {@link ModuleTarget} or null when there is no <code>ModuleTarget</code> attribute.
* @since 1.53
*/
public final ModuleTarget getModuleTarget() {
if (moduleTarget == null) {
final DataInputStream in = attributes.getStream("ModuleTarget"); //NOI18N
if (in != null) {
try {
try {
moduleTarget = new ModuleTarget (in, constantPool);
} finally {
in.close();
}
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid ModuleTarget attribute", e);
}
}
}
return moduleTarget;
}
/**
* Returns the major version number of this classfile.
*/
public int getMajorVersion() {
return majorVersion;
}
/**
* Returns the minor version number of this classfile.
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Returns the generic type information associated with this class.
* If this class does not have generic type information, then null
* is returned.
*/
public String getTypeSignature() {
if (typeSignature == null) {
DataInputStream in = attributes.getStream("Signature"); // NOI18N
if (in != null) {
try {
CPUTF8Info entry =
(CPUTF8Info)constantPool.get(in.readUnsignedShort());
typeSignature = entry.getName();
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid Signature attribute", e);
}
}
}
return typeSignature;
}
/**
* Returns the enclosing method for this class. A class will have an
* enclosing class if and only if it is a local class or an anonymous
* class, and has been compiled with a compiler target level of 1.5
* or above. If no such attribute is present in the classfile, then
* null is returned.
*/
public EnclosingMethod getEnclosingMethod() {
if (enclosingMethod == null) {
DataInputStream in =
attributes.getStream("EnclosingMethod"); // NOI18N
if (in != null) {
try {
int classIndex = in.readUnsignedShort();
int natIndex = in.readUnsignedShort();
CPEntry entry = constantPool.get(classIndex);
if (entry.getTag() == ConstantPool.CONSTANT_Class)
enclosingMethod =
new EnclosingMethod(constantPool,
(CPClassInfo)entry,
natIndex);
else
; // JDK 1.5 beta1 bug
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid EnclosingMethod attribute", e);
}
}
}
return enclosingMethod;
}
private void loadAnnotations() {
if (annotations == null)
annotations = buildAnnotationMap(constantPool, attributes);
}
static Map<ClassName,Annotation> buildAnnotationMap(ConstantPool pool, AttributeMap attrs) {
Map<ClassName,Annotation> annotations = new HashMap<ClassName,Annotation>(2);
DataInputStream in =
attrs.getStream("RuntimeVisibleAnnotations"); //NOI18N
if (in != null) {
try {
Annotation.load(in, pool, true, annotations);
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid RuntimeVisibleAnnotations attribute", e);
}
}
in = attrs.getStream("RuntimeInvisibleAnnotations"); //NOI18N
if (in != null) {
try {
Annotation.load(in, pool, false, annotations);
in.close();
} catch (IOException e) {
throw new InvalidClassFileAttributeException("invalid RuntimeInvisibleAnnotations attribute", e);
}
}
return annotations;
}
/**
* Returns all runtime annotations defined for this class. Inherited
* annotations are not included in this collection.
*/
public final Collection<Annotation> getAnnotations() {
loadAnnotations();
return annotations.values();
}
/**
* Returns the annotation for a specified annotation type, or null if
* no annotation of that type exists for this class.
*/
public final Annotation getAnnotation(final ClassName annotationClass) {
loadAnnotations();
return annotations.get(annotationClass);
}
/**
* Returns true if an annotation of the specified type is defined for
* this class.
*/
public final boolean isAnnotationPresent(final ClassName annotationClass) {
loadAnnotations();
return annotations.get(annotationClass) != null;
}
/** Return the collection of all unique class references in this class.
*
* @return a Set of ClassNames specifying the referenced classnames.
*/
public final Set<ClassName> getAllClassNames() {
Set<ClassName> set = new HashSet<ClassName>();
// include all class name constants from constant pool
Collection<? extends CPClassInfo> c = constantPool.getAllConstants(CPClassInfo.class);
for (Iterator<? extends CPClassInfo> i = c.iterator(); i.hasNext();) {
CPClassInfo ci = i.next();
set.add(ci.getClassName());
}
// scan variables and methods for other class references
// (inner classes will caught above)
for (int i = 0; i < variables.length; i++)
addClassNames(set, variables[i].getDescriptor());
for (int i = 0; i < methods.length; i++)
addClassNames(set, methods[i].getDescriptor());
return Collections.unmodifiableSet(set);
}
private void addClassNames(Set<ClassName> set, String type) {
int i = 0;
while ((i = type.indexOf('L', i)) != -1) {
int j = type.indexOf(';', i);
if (j > i) {
// get name, minus leading 'L' and trailing ';'
String classType = type.substring(i + 1, j);
set.add(ClassName.getClassName(classType));
i = j + 1;
} else
break;
}
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("ClassFile: "); //NOI18N
sb.append(Access.toString(classAccess));
sb.append(' ');
sb.append(classInfo);
if (isSynthetic())
sb.append(" (synthetic)"); //NOI18N
if (isDeprecated())
sb.append(" (deprecated)"); //NOI18N
sb.append("\n source: "); //NOI18N
sb.append(getSourceFileName());
sb.append("\n super: "); //NOI18N
sb.append(superClassInfo);
if (getTypeSignature() != null) {
sb.append("\n signature: "); //NOI18N
sb.append(typeSignature);
}
if (getEnclosingMethod() != null) {
sb.append("\n enclosing method: "); //NOI18N
sb.append(enclosingMethod);
}
sb.append("\n ");
loadAnnotations();
if (annotations.size() > 0) {
Iterator<Annotation> iter = annotations.values().iterator();
sb.append("annotations: ");
while (iter.hasNext()) {
sb.append("\n ");
sb.append(iter.next().toString());
}
sb.append("\n ");
}
if (interfaces.length > 0) {
sb.append(arrayToString("interfaces", interfaces)); //NOI18N
sb.append("\n ");
}
if (getInnerClasses().size() > 0) {
sb.append(arrayToString("innerclasses", innerClasses)); //NOI18N
sb.append("\n ");
}
if (variables.length > 0) {
sb.append(arrayToString("variables", variables)); //NOI18N
sb.append("\n ");
}
if (methods.length > 0)
sb.append(arrayToString("methods", methods)); //NOI18N
return sb.toString();
}
private String arrayToString(String name, Object[] array) {
StringBuffer sb = new StringBuffer();
sb.append(name);
sb.append(": ");
int n = array.length;
if (n > 0) {
int i = 0;
do {
sb.append("\n ");
sb.append(array[i++].toString());
} while (i < n);
} else
sb.append("none"); //NOI18N
return sb.toString();
}
private static boolean isBadNonJavaClassName(final String name) {
return name.length() == 1 && badNonJavaClassNames.contains(name);
}
}