001: /*
002: * ClassFile.java
003: *
004: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
005: *
006: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
007: *
008: * The contents of this file are subject to the terms of either the GNU
009: * General Public License Version 2 only ("GPL") or the Common
010: * Development and Distribution License("CDDL") (collectively, the
011: * "License"). You may not use this file except in compliance with the
012: * License. You can obtain a copy of the License at
013: * http://www.netbeans.org/cddl-gplv2.html
014: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
015: * specific language governing permissions and limitations under the
016: * License. When distributing the software, include this License Header
017: * Notice in each file and include the License file at
018: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
019: * particular file as subject to the "Classpath" exception as provided
020: * by Sun in the GPL Version 2 section of the License file that
021: * accompanied this code. If applicable, add the following below the
022: * License Header, with the fields enclosed by brackets [] replaced by
023: * your own identifying information:
024: * "Portions Copyrighted [year] [name of copyright owner]"
025: *
026: * Contributor(s):
027: *
028: * The Original Software is NetBeans. The Initial Developer of the Original
029: * Software is Sun Microsystems, Inc. Portions Copyright 2000-2001 Sun
030: * Microsystems, Inc. All Rights Reserved.
031: *
032: * If you wish your version of this file to be governed by only the CDDL
033: * or only the GPL Version 2, indicate your decision by adding
034: * "[Contributor] elects to include this software in this distribution
035: * under the [CDDL or GPL Version 2] license." If you do not indicate a
036: * single choice of license, a recipient has the option to distribute
037: * your version of this file under either the CDDL, the GPL Version 2 or
038: * to extend the choice of license to its licensees as provided above.
039: * However, if you add GPL Version 2 code and therefore, elected the GPL
040: * Version 2 license, then the option applies only if the new code is
041: * made subject to such option by the copyright holder.
042: *
043: * Contributor(s): Thomas Ball
044: *
045: * Version: $Revision$
046: */
047:
048: package org.netbeans.modules.classfile;
049:
050: import java.io.*;
051: import java.util.*;
052:
053: /**
054: * Class representing a Java class file.
055: *
056: * @author Thomas Ball
057: */
058: public class ClassFile {
059:
060: ConstantPool constantPool;
061: int classAccess;
062: CPClassInfo classInfo;
063: CPClassInfo super ClassInfo;
064: CPClassInfo[] interfaces;
065: Variable[] variables;
066: Method[] methods;
067: String sourceFileName;
068: InnerClass[] innerClasses;
069: private AttributeMap attributes;
070: private Map<ClassName, Annotation> annotations;
071: short majorVersion;
072: short minorVersion;
073: String typeSignature;
074: EnclosingMethod enclosingMethod;
075: private boolean includeCode = false;
076:
077: /** size of buffer in buffered input streams */
078: private static final int BUFFER_SIZE = 4096;
079:
080: /**
081: * Create a new ClassFile object.
082: * @param classData an InputStream from which the defining bytes of this
083: * class or interface are read.
084: * @throws IOException if InputStream can't be read, or if the class data
085: * is malformed.
086: */
087: public ClassFile(InputStream classData) throws IOException {
088: this (classData, true);
089: }
090:
091: /**
092: * Create a new ClassFile object.
093: * @param classFileName the path of a class file.
094: * @throws IOException if file cannot be opened or read.
095: **/
096: public ClassFile(String classFileName) throws IOException {
097: this (classFileName, true);
098: }
099:
100: /**
101: * Create a new ClassFile object.
102: * @param file a File instance of a class file.
103: * @param includeCode true if this classfile should support operations
104: * at the bytecode level. Specify false to conserve
105: * memory if code access isn't needed.
106: * @throws IOException if file cannot be opened or read.
107: **/
108: public ClassFile(File file, boolean includeCode) throws IOException {
109: InputStream is = null;
110: this .includeCode = includeCode;
111: if (file == null || !file.exists())
112: throw new FileNotFoundException(file != null ? file
113: .getPath() : "null");
114: try {
115: is = new BufferedInputStream(new FileInputStream(file),
116: BUFFER_SIZE);
117: load(is);
118: } catch (InvalidClassFormatException e) {
119: throw new InvalidClassFormatException(file.getPath() + '('
120: + e.getMessage() + ')');
121: } finally {
122: if (is != null)
123: is.close();
124: }
125: }
126:
127: /**
128: * Create a new ClassFile object.
129: * @param classData an InputStream from which the defining bytes of this
130: * class or interface are read.
131: * @param includeCode true if this classfile should support operations
132: * at the bytecode level. Specify false to conserve
133: * memory if code access isn't needed.
134: * @throws IOException if InputStream can't be read, or if the class data
135: * is malformed.
136: */
137: public ClassFile(InputStream classData, boolean includeCode)
138: throws IOException {
139: if (classData == null)
140: throw new IOException("input stream not specified");
141: this .includeCode = includeCode;
142: try {
143: load(classData);
144: } catch (IndexOutOfBoundsException e) {
145: throw new InvalidClassFormatException(
146: "invalid classfile format");
147: }
148: }
149:
150: /**
151: * Create a new ClassFile object.
152: * @param classFileName the path of a class file.
153: * @param includeCode true if this classfile should support operations
154: * at the bytecode level. Specify false to conserve
155: * memory if code access isn't needed.
156: * @throws IOException if file cannot be opened or read.
157: **/
158: public ClassFile(String classFileName, boolean includeCode)
159: throws IOException {
160: InputStream in = null;
161: this .includeCode = includeCode;
162: try {
163: if (classFileName == null)
164: throw new IOException("input stream not specified");
165: in = new BufferedInputStream(new FileInputStream(
166: classFileName), BUFFER_SIZE);
167: load(in);
168: } catch (InvalidClassFormatException e) {
169: throw new InvalidClassFormatException(classFileName + '('
170: + e.getMessage() + ')');
171: } finally {
172: if (in != null)
173: in.close();
174: }
175: }
176:
177: /** Returns the ConstantPool object associated with this ClassFile.
178: * @return the constant pool object
179: */
180: public final ConstantPool getConstantPool() {
181: return constantPool;
182: }
183:
184: private void load(InputStream classData) throws IOException {
185: try {
186: DataInputStream in = new DataInputStream(classData);
187: constantPool = loadClassHeader(in);
188: interfaces = getCPClassList(in, constantPool);
189: variables = Variable.loadFields(in, constantPool, this );
190: methods = Method.loadMethods(in, constantPool, this ,
191: includeCode);
192: attributes = AttributeMap.load(in, constantPool);
193: } catch (IOException ioe) {
194: throw new InvalidClassFormatException(ioe);
195: }
196: }
197:
198: private ConstantPool loadClassHeader(DataInputStream in)
199: throws IOException {
200: int magic = in.readInt();
201: if (magic != 0xCAFEBABE) {
202: throw new InvalidClassFormatException();
203: }
204:
205: minorVersion = in.readShort();
206: majorVersion = in.readShort();
207: int count = in.readUnsignedShort();
208: ConstantPool pool = new ConstantPool(count, in);
209: classAccess = in.readUnsignedShort();
210: classInfo = pool.getClass(in.readUnsignedShort());
211: if (classInfo == null)
212: throw new InvalidClassFormatException();
213: int index = in.readUnsignedShort();
214: if (index != 0) // true for java.lang.Object
215: super ClassInfo = pool.getClass(index);
216: return pool;
217: }
218:
219: static CPClassInfo[] getCPClassList(DataInputStream in,
220: ConstantPool pool) throws IOException {
221: int count = in.readUnsignedShort();
222: CPClassInfo[] classes = new CPClassInfo[count];
223: for (int i = 0; i < count; i++) {
224: classes[i] = pool.getClass(in.readUnsignedShort());
225: }
226: return classes;
227: }
228:
229: /**
230: * Returns the access permissions of this class or interface.
231: * @return a mask of access flags.
232: * @see org.netbeans.modules.classfile.Access
233: */
234: public final int getAccess() {
235: return classAccess;
236: }
237:
238: /** Returns the name of this class.
239: * @return the name of this class.
240: */
241: public final ClassName getName() {
242: return classInfo.getClassName();
243: }
244:
245: /** Returns the name of this class's superclass. A string is returned
246: * instead of a ClassFile object to reduce object creation.
247: * @return the name of the superclass of this class.
248: */
249: public final ClassName getSuperClass() {
250: if (super ClassInfo == null)
251: return null;
252: return super ClassInfo.getClassName();
253: }
254:
255: /**
256: * @return a collection of Strings describing this class's interfaces.
257: */
258: public final Collection<ClassName> getInterfaces() {
259: List<ClassName> l = new ArrayList<ClassName>();
260: int n = interfaces.length;
261: for (int i = 0; i < n; i++)
262: l.add(interfaces[i].getClassName());
263: return l;
264: }
265:
266: /**
267: * Looks up a variable by its name.
268: *
269: * NOTE: this method only looks up variables defined by this class,
270: * and not inherited from its superclass.
271: *
272: * @param name the name of the variable
273: * @return the variable,or null if no such variable in this class.
274: */
275: public final Variable getVariable(String name) {
276: int n = variables.length;
277: for (int i = 0; i < n; i++) {
278: Variable v = variables[i];
279: if (v.getName().equals(name))
280: return v;
281: }
282: return null;
283: }
284:
285: /**
286: * @return a Collection of Variable objects representing the fields
287: * defined by this class.
288: */
289: public final Collection<Variable> getVariables() {
290: return Arrays.asList(variables);
291: }
292:
293: /**
294: * @return the number of variables defined by this class.
295: */
296: public final int getVariableCount() {
297: return variables.length;
298: }
299:
300: /**
301: * Looks up a method by its name and type signature, as defined
302: * by the Java Virtual Machine Specification, section 4.3.3.
303: *
304: * NOTE: this method only looks up methods defined by this class,
305: * and not methods inherited from its superclass.
306: *
307: * @param name the name of the method
308: * @param signature the method's type signature
309: * @return the method, or null if no such method in this class.
310: */
311: public final Method getMethod(String name, String signature) {
312: int n = methods.length;
313: for (int i = 0; i < n; i++) {
314: Method m = methods[i];
315: if (m.getName().equals(name)
316: && m.getDescriptor().equals(signature))
317: return m;
318: }
319: return null;
320: }
321:
322: /**
323: * @return a Collection of Method objects representing the methods
324: * defined by this class.
325: */
326: public final Collection<Method> getMethods() {
327: return Arrays.asList(methods);
328: }
329:
330: /**
331: * @return the number of methods defined by this class.
332: */
333: public final int getMethodCount() {
334: return methods.length;
335: }
336:
337: /**
338: * @return the name of the source file the compiler used to create this class.
339: */
340: public final String getSourceFileName() {
341: if (sourceFileName == null) {
342: DataInputStream in = attributes.getStream("SourceFile"); // NOI18N
343: if (in != null) {
344: try {
345: int ipool = in.readUnsignedShort();
346: CPUTF8Info entry = (CPUTF8Info) constantPool
347: .get(ipool);
348: sourceFileName = entry.getName();
349: in.close();
350: } catch (IOException e) {
351: throw new InvalidClassFileAttributeException(
352: "invalid SourceFile attribute", e);
353: }
354: }
355: }
356: return sourceFileName;
357: }
358:
359: public final boolean isDeprecated() {
360: return attributes.get("Deprecated") != null;
361: }
362:
363: public final boolean isSynthetic() {
364: return (classAccess & Access.SYNTHETIC) == Access.SYNTHETIC
365: || attributes.get("Synthetic") != null;
366: }
367:
368: /**
369: * Returns true if this class is an annotation type.
370: */
371: public final boolean isAnnotation() {
372: return (classAccess & Access.ANNOTATION) == Access.ANNOTATION;
373: }
374:
375: /**
376: * Returns true if this class defines an enum type.
377: */
378: public final boolean isEnum() {
379: return (classAccess & Access.ENUM) == Access.ENUM;
380: }
381:
382: /**
383: * Returns a map of the raw attributes for this classfile.
384: * Field attributes are
385: * not returned in this map.
386: *
387: * @see org.netbeans.modules.classfile.Field#getAttributes
388: */
389: public final AttributeMap getAttributes() {
390: return attributes;
391: }
392:
393: public final Collection<InnerClass> getInnerClasses() {
394: if (innerClasses == null) {
395: DataInputStream in = attributes.getStream("InnerClasses"); // NOI18N
396: if (in != null) {
397: try {
398: innerClasses = InnerClass.loadInnerClasses(in,
399: constantPool);
400: in.close();
401: } catch (IOException e) {
402: throw new InvalidClassFileAttributeException(
403: "invalid InnerClasses attribute", e);
404: }
405: } else
406: innerClasses = new InnerClass[0];
407: }
408: return Arrays.asList(innerClasses);
409: }
410:
411: /**
412: * Returns the major version number of this classfile.
413: */
414: public int getMajorVersion() {
415: return majorVersion;
416: }
417:
418: /**
419: * Returns the minor version number of this classfile.
420: */
421: public int getMinorVersion() {
422: return minorVersion;
423: }
424:
425: /**
426: * Returns the generic type information associated with this class.
427: * If this class does not have generic type information, then null
428: * is returned.
429: */
430: public String getTypeSignature() {
431: if (typeSignature == null) {
432: DataInputStream in = attributes.getStream("Signature"); // NOI18N
433: if (in != null) {
434: try {
435: CPUTF8Info entry = (CPUTF8Info) constantPool.get(in
436: .readUnsignedShort());
437: typeSignature = entry.getName();
438: in.close();
439: } catch (IOException e) {
440: throw new InvalidClassFileAttributeException(
441: "invalid Signature attribute", e);
442: }
443: }
444: }
445: return typeSignature;
446: }
447:
448: /**
449: * Returns the enclosing method for this class. A class will have an
450: * enclosing class if and only if it is a local class or an anonymous
451: * class, and has been compiled with a compiler target level of 1.5
452: * or above. If no such attribute is present in the classfile, then
453: * null is returned.
454: */
455: public EnclosingMethod getEnclosingMethod() {
456: if (enclosingMethod == null) {
457: DataInputStream in = attributes
458: .getStream("EnclosingMethod"); // NOI18N
459: if (in != null) {
460: try {
461: int classIndex = in.readUnsignedShort();
462: int natIndex = in.readUnsignedShort();
463: CPEntry classInfo = constantPool.get(classIndex);
464: if (classInfo.getTag() == ConstantPool.CONSTANT_Class)
465: enclosingMethod = new EnclosingMethod(
466: constantPool, (CPClassInfo) classInfo,
467: natIndex);
468: else
469: ; // JDK 1.5 beta1 bug
470: in.close();
471: } catch (IOException e) {
472: throw new InvalidClassFileAttributeException(
473: "invalid EnclosingMethod attribute", e);
474: }
475: }
476: }
477: return enclosingMethod;
478: }
479:
480: private void loadAnnotations() {
481: if (annotations == null)
482: annotations = buildAnnotationMap(constantPool, attributes);
483: }
484:
485: static Map<ClassName, Annotation> buildAnnotationMap(
486: ConstantPool pool, AttributeMap attrs) {
487: Map<ClassName, Annotation> annotations = new HashMap<ClassName, Annotation>(
488: 2);
489: DataInputStream in = attrs
490: .getStream("RuntimeVisibleAnnotations"); //NOI18N
491: if (in != null) {
492: try {
493: Annotation.load(in, pool, true, annotations);
494: in.close();
495: } catch (IOException e) {
496: throw new InvalidClassFileAttributeException(
497: "invalid RuntimeVisibleAnnotations attribute",
498: e);
499: }
500: }
501: in = attrs.getStream("RuntimeInvisibleAnnotations"); //NOI18N
502: if (in != null) {
503: try {
504: Annotation.load(in, pool, false, annotations);
505: in.close();
506: } catch (IOException e) {
507: throw new InvalidClassFileAttributeException(
508: "invalid RuntimeInvisibleAnnotations attribute",
509: e);
510: }
511: }
512: return annotations;
513: }
514:
515: /**
516: * Returns all runtime annotations defined for this class. Inherited
517: * annotations are not included in this collection.
518: */
519: public final Collection<Annotation> getAnnotations() {
520: loadAnnotations();
521: return annotations.values();
522: }
523:
524: /**
525: * Returns the annotation for a specified annotation type, or null if
526: * no annotation of that type exists for this class.
527: */
528: public final Annotation getAnnotation(
529: final ClassName annotationClass) {
530: loadAnnotations();
531: return annotations.get(annotationClass);
532: }
533:
534: /**
535: * Returns true if an annotation of the specified type is defined for
536: * this class.
537: */
538: public final boolean isAnnotationPresent(
539: final ClassName annotationClass) {
540: loadAnnotations();
541: return annotations.get(annotationClass) != null;
542: }
543:
544: /** Return the collection of all unique class references in this class.
545: *
546: * @return a Set of ClassNames specifying the referenced classnames.
547: */
548: public final Set<ClassName> getAllClassNames() {
549: Set<ClassName> set = new HashSet<ClassName>();
550:
551: // include all class name constants from constant pool
552: Collection c = constantPool.getAllConstants(CPClassInfo.class);
553: for (Iterator i = c.iterator(); i.hasNext();) {
554: CPClassInfo ci = (CPClassInfo) i.next();
555: set.add(ci.getClassName());
556: }
557:
558: // scan variables and methods for other class references
559: // (inner classes will caught above)
560: for (int i = 0; i < variables.length; i++)
561: addClassNames(set, variables[i].getDescriptor());
562: for (int i = 0; i < methods.length; i++)
563: addClassNames(set, methods[i].getDescriptor());
564:
565: return Collections.unmodifiableSet(set);
566: }
567:
568: private void addClassNames(Set<ClassName> set, String type) {
569: int i = 0;
570: while ((i = type.indexOf('L', i)) != -1) {
571: int j = type.indexOf(';', i);
572: if (j > i) {
573: // get name, minus leading 'L' and trailing ';'
574: String classType = type.substring(i + 1, j);
575: set.add(ClassName.getClassName(classType));
576: i = j + 1;
577: } else
578: break;
579: }
580: }
581:
582: public String toString() {
583: StringBuffer sb = new StringBuffer();
584: sb.append("ClassFile: "); //NOI18N
585: sb.append(Access.toString(classAccess));
586: sb.append(' ');
587: sb.append(classInfo);
588: if (isSynthetic())
589: sb.append(" (synthetic)"); //NOI18N
590: if (isDeprecated())
591: sb.append(" (deprecated)"); //NOI18N
592: sb.append("\n source: "); //NOI18N
593: sb.append(getSourceFileName());
594: sb.append("\n super: "); //NOI18N
595: sb.append(super ClassInfo);
596: if (getTypeSignature() != null) {
597: sb.append("\n signature: "); //NOI18N
598: sb.append(typeSignature);
599: }
600: if (getEnclosingMethod() != null) {
601: sb.append("\n enclosing method: "); //NOI18N
602: sb.append(enclosingMethod);
603: }
604: sb.append("\n ");
605: loadAnnotations();
606: if (annotations.size() > 0) {
607: Iterator iter = annotations.values().iterator();
608: sb.append("annotations: ");
609: while (iter.hasNext()) {
610: sb.append("\n ");
611: sb.append(iter.next().toString());
612: }
613: sb.append("\n ");
614: }
615: if (interfaces.length > 0) {
616: sb.append(arrayToString("interfaces", interfaces)); //NOI18N
617: sb.append("\n ");
618: }
619: if (getInnerClasses().size() > 0) {
620: sb.append(arrayToString("innerclasses", innerClasses)); //NOI18N
621: sb.append("\n ");
622: }
623: if (variables.length > 0) {
624: sb.append(arrayToString("variables", variables)); //NOI18N
625: sb.append("\n ");
626: }
627: if (methods.length > 0)
628: sb.append(arrayToString("methods", methods)); //NOI18N
629: return sb.toString();
630: }
631:
632: private String arrayToString(String name, Object[] array) {
633: StringBuffer sb = new StringBuffer();
634: sb.append(name);
635: sb.append(": ");
636: int n = array.length;
637: if (n > 0) {
638: int i = 0;
639: do {
640: sb.append("\n ");
641: sb.append(array[i++].toString());
642: } while (i < n);
643: } else
644: sb.append("none"); //NOI18N
645: return sb.toString();
646: }
647: }
|