| /* |
| * 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); |
| } |
| } |