Java tutorial
/******************************************************************************* * Copyright (c) 2007, 2014 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.pde.api.tools.internal.builder; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.Stack; import java.util.TreeSet; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.api.tools.internal.model.AbstractApiTypeRoot; import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; import org.eclipse.pde.api.tools.internal.provisional.model.IApiField; import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember; import org.eclipse.pde.api.tools.internal.provisional.model.IApiMethod; import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; import org.eclipse.pde.api.tools.internal.util.Signatures; import org.eclipse.pde.api.tools.internal.util.Util; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import org.objectweb.asm.tree.ClassNode; /** * Extracts references from a class file * * @since 1.0.0 */ public class ReferenceExtractor extends ClassVisitor { /** * A visitor for visiting java 5+ signatures * * ClassSignature = (visitFormalTypeParameter visitClassBound? * visitInterfaceBound* )* (visitSuperClass visitInterface* ) * MethodSignature = (visitFormalTypeParameter visitClassBound? * visitInterfaceBound* )* (visitParameterType visitReturnType * visitExceptionType* ) TypeSignature = visitBaseType | visitTypeVariable | * visitArrayType | (visitClassType visitTypeArgument* (visitInnerClassType * visitTypeArgument* )* visitEnd</tt> ) ) */ class ClassFileSignatureVisitor extends SignatureVisitor { protected int kind = -1; protected int originalkind = -1; protected int argumentcount = 0; protected int type = 0; protected String signature = null; protected String name = null; protected List<Reference> references; public ClassFileSignatureVisitor() { super(Opcodes.ASM5); this.references = new ArrayList<Reference>(); } /** * Resets the visitor to its initial state. This method should be called * after processing is done with the visitor */ protected void reset() { // do not reset argument count, as it is needed once the signature // visitor is done this.kind = -1; this.originalkind = -1; this.name = null; this.signature = null; this.type = 0; this.references.clear(); } /** * Processes the type specified by the name for the current signature * context. The kind flag is set to a parameterized type as subsequent * calls to this method without visiting other nodes only occurs when we * are processing parameterized types of generic declarations * * @param name the name of the type */ protected void processType(String name) { Type type = ReferenceExtractor.this.resolveType(Type.getObjectType(name).getDescriptor()); if (type != null) { String tname = type.getClassName(); if (tname.equals("E") || tname.equals("T")) { //$NON-NLS-1$//$NON-NLS-2$ type = Type.getObjectType("java.lang.Object"); //$NON-NLS-1$ tname = type.getClassName(); } if (ReferenceExtractor.this.consider(tname) && this.kind != -1) { if (this.name != null && this.signature != null) { this.references.add(Reference.typeReference(ReferenceExtractor.this.getMember(), tname, this.signature, this.kind)); } } } this.kind = this.originalkind; } @Override public void visitClassType(String name) { this.processType(name); } @Override public void visitFormalTypeParameter(String name) { if (this.type != TYPE) { this.processType(name); } } @Override public void visitTypeVariable(String name) { } @Override public void visitInnerClassType(String name) { this.processType(name); } @Override public SignatureVisitor visitParameterType() { this.argumentcount++; this.kind = IReference.REF_PARAMETER; return this; } @Override public SignatureVisitor visitInterface() { this.kind = IReference.REF_IMPLEMENTS; return this; } @Override public SignatureVisitor visitExceptionType() { this.kind = IReference.REF_THROWS; return this; } @Override public SignatureVisitor visitArrayType() { return this; } @Override public SignatureVisitor visitReturnType() { this.kind = IReference.REF_RETURNTYPE; return this; } @Override public SignatureVisitor visitClassBound() { this.kind = IReference.REF_PARAMETERIZED_TYPEDECL; return this; } @Override public SignatureVisitor visitInterfaceBound() { this.kind = IReference.REF_PARAMETERIZED_TYPEDECL; return this; } @Override public SignatureVisitor visitSuperclass() { this.kind = IReference.REF_EXTENDS; return this; } @Override public SignatureVisitor visitTypeArgument(char wildcard) { return this; } @Override public void visitEnd() { } @Override public void visitBaseType(char descriptor) { switch (descriptor) { case 'J': case 'D': argumentcount += 2; break; default: this.argumentcount++; } } @Override public void visitTypeArgument() { } } /** * Visitor used to visit the methods of a type [ visitCode ( visitFrame | * visit<i>X</i>Insn | visitLabel | visitTryCatchBlock | visitLocalVariable * | visitLineNumber)* visitMaxs ] visitEnd */ class ClassFileMethodVisitor extends MethodVisitor { int argumentcount = 0; LinePositionTracker linePositionTracker; /** * Most recent string literal encountered. Used to infer * Class.forName("...") references. */ String stringLiteral; String methodName; int lastLineNumber; boolean implicitConstructor = false; LocalLineNumberMarker localVariableMarker; HashMap<Label, List<LocalLineNumberMarker>> labelsToLocalMarkers; /** * Constructor * * @param mv */ public ClassFileMethodVisitor(MethodVisitor mv, String name, int argumentcount) { super(Opcodes.ASM5, mv); this.argumentcount = argumentcount; this.linePositionTracker = new LinePositionTracker(); this.lastLineNumber = -1; this.labelsToLocalMarkers = new HashMap<Label, List<LocalLineNumberMarker>>(); this.methodName = name; } @Override public void visitEnd() { this.implicitConstructor = false; this.argumentcount = 0; ReferenceExtractor.this.exitMember(); this.linePositionTracker.computeLineNumbers(); this.labelsToLocalMarkers = null; } @Override public void visitVarInsn(int opcode, int var) { this.stringLiteral = null; switch (opcode) { case Opcodes.ASTORE: { if (this.lastLineNumber != -1) { this.localVariableMarker = new LocalLineNumberMarker(this.lastLineNumber, var); } break; } default: { break; } } } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { int refType = -1; switch (opcode) { case Opcodes.PUTSTATIC: { refType = IReference.REF_PUTSTATIC; break; } case Opcodes.PUTFIELD: { refType = IReference.REF_PUTFIELD; break; } case Opcodes.GETSTATIC: { refType = IReference.REF_GETSTATIC; break; } case Opcodes.GETFIELD: { refType = IReference.REF_GETFIELD; break; } default: { break; } } if (refType != -1) { Reference reference = ReferenceExtractor.this.addFieldReference(Type.getObjectType(owner), name, refType); if (reference != null) { this.linePositionTracker.addLocation(reference); if (refType == IReference.REF_GETFIELD || refType == IReference.REF_PUTFIELD) { ReferenceExtractor.this.fieldtracker.addField(reference); } } } } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { if (type != null) { Type ctype = Type.getObjectType(type); Reference reference = ReferenceExtractor.this.addTypeReference(ctype, IReference.REF_CATCHEXCEPTION); if (reference != null) { this.linePositionTracker.addCatchLabelInfos(reference, handler); this.linePositionTracker.addLocation(reference); } } } @Override public void visitLabel(Label label) { this.linePositionTracker.addLabel(label); if (this.localVariableMarker != null) { List<LocalLineNumberMarker> list = this.labelsToLocalMarkers.get(label); if (list != null) { list.add(this.localVariableMarker); } else { list = new ArrayList<LocalLineNumberMarker>(); list.add(this.localVariableMarker); this.labelsToLocalMarkers.put(label, list); } this.localVariableMarker = null; } } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean inf) { Type declaringType = Type.getObjectType(owner); int kind = -1; int flags = 0; switch (opcode) { case Opcodes.INVOKESPECIAL: { kind = ("<init>".equals(name) ? IReference.REF_CONSTRUCTORMETHOD : IReference.REF_SPECIALMETHOD); //$NON-NLS-1$ if (kind == IReference.REF_CONSTRUCTORMETHOD) { if (!implicitConstructor && this.methodName.equals("<init>") && !fSuperStack.isEmpty() //$NON-NLS-1$ && (fSuperStack.peek()).equals(declaringType.getClassName())) { implicitConstructor = true; kind = IReference.REF_SUPER_CONSTRUCTORMETHOD; } else { Reference reference = ReferenceExtractor.this.addTypeReference(declaringType, IReference.REF_INSTANTIATE); if (reference != null) { this.linePositionTracker.addLocation(reference); } } } break; } case Opcodes.INVOKESTATIC: { kind = IReference.REF_STATICMETHOD; // check for reference to a class literal if (name.equals("forName")) { //$NON-NLS-1$ if (ReferenceExtractor.this.processName(owner).equals("java.lang.Class")) { //$NON-NLS-1$ if (this.stringLiteral != null) { try { Type classLiteral = Type.getObjectType(this.stringLiteral); Reference reference = ReferenceExtractor.this.addTypeReference(classLiteral, IReference.REF_CONSTANTPOOL); if (reference != null) { this.linePositionTracker.addLocation(reference); } } catch (Exception e) { // do nothing, but prevent bogus strings // from causing problems in ASM // https://bugs.eclipse.org/bugs/show_bug.cgi?id=399898 } } } } break; } case Opcodes.INVOKEVIRTUAL: { kind = IReference.REF_VIRTUALMETHOD; // try to determine if this is a default method if (fVersion >= Opcodes.V1_8) { IApiMember member = ReferenceExtractor.this.getMember(); if (member != null) { try { IApiComponent comp = fType.getApiComponent(); if (comp != null) { String owner_sig = processName(owner); AbstractApiTypeRoot root = (AbstractApiTypeRoot) comp.findTypeRoot(owner_sig); if (root == null) { // a quick look did not find it, now ask // for the components that provide the // package IApiBaseline baseline = comp.getBaseline(); IApiComponent[] comps = baseline.resolvePackage(comp, Signatures.getPackageName(owner_sig)); for (int i = 0; i < comps.length; i++) { root = (AbstractApiTypeRoot) comps[i].findTypeRoot(owner_sig); if (root != null) { break; } } } if (root != null) { IApiType type = root.getStructure(); if (type != null && getDefaultDefined(type, name, desc, false) != null) { flags = IReference.F_DEFAULT_METHOD; } } } } catch (CoreException ce) { // do nothing, give up } } } break; } case Opcodes.INVOKEINTERFACE: { kind = IReference.REF_INTERFACEMETHOD; break; } default: { break; } } if (kind != -1) { Reference reference = ReferenceExtractor.this.addMethodReference(declaringType, name, desc, kind, flags); if (reference != null) { this.linePositionTracker.addLocation(reference); if (kind == IReference.REF_STATICMETHOD) { ReferenceExtractor.this.fieldtracker.addAccessor(reference); } } } this.stringLiteral = null; } @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { for (Object arg : bsmArgs) { if (arg instanceof Handle) { Handle handle = (Handle) arg; Type declaringType = Type.getObjectType(handle.getOwner()); Reference reference = ReferenceExtractor.this.addMethodReference(declaringType, handle.getName(), handle.getDesc(), IReference.REF_VIRTUALMETHOD, 0); if (reference != null) { this.linePositionTracker.addLocation(reference); } } } } @Override public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return null; } @Override public void visitMultiANewArrayInsn(String desc, int dims) { Type type = this.getTypeFromDescription(desc); Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_ARRAYALLOC); if (reference != null) { this.linePositionTracker.addLocation(reference); } } @Override public void visitLineNumber(int line, Label start) { this.lastLineNumber = line; this.linePositionTracker.addLineInfo(line, start); } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Method visitor for: "); //$NON-NLS-1$ buffer.append(methodName); buffer.append("\nCurrent line number: "); //$NON-NLS-1$ buffer.append(lastLineNumber); return buffer.toString(); } /** * Creates a type from a type description. Works around bugs creating * types from array type signatures in ASM. * * @param desc signature * @return Type */ private Type getTypeFromDescription(String desc) { String ldesc = desc; while (ldesc.charAt(0) == '[') { ldesc = ldesc.substring(1); } Type type = null; if (ldesc.endsWith(";")) { //$NON-NLS-1$ type = Type.getType(ldesc); } else { type = Type.getObjectType(ldesc); } return type; } @Override public void visitTypeInsn(int opcode, String desc) { Type type = this.getTypeFromDescription(desc); int kind = -1; switch (opcode) { case Opcodes.ANEWARRAY: { kind = IReference.REF_ARRAYALLOC; break; } case Opcodes.CHECKCAST: { kind = IReference.REF_CHECKCAST; break; } case Opcodes.INSTANCEOF: { kind = IReference.REF_INSTANCEOF; break; } case Opcodes.NEW: { // we can omit the NEW case as it is caught by the // constructor call // handle it only for anonymous / local types List<Reference> refs = fAnonymousTypes.get(processName(type.getInternalName())); if (refs != null) { for (Iterator<Reference> iterator = refs.iterator(); iterator.hasNext();) { this.linePositionTracker.addLocation(iterator.next()); } } break; } default: { break; } } if (kind != -1) { Reference reference = ReferenceExtractor.this.addTypeReference(type, kind); if (reference != null) { this.linePositionTracker.addLocation(reference); } } } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { if (desc.length() == 1) { // base type return; } if (index > this.argumentcount) { List<LocalLineNumberMarker> list = this.labelsToLocalMarkers.get(start); int lineNumber = -1; if (list != null) { // list of potential localMarker // iterate the list to find the one that matches the // index LocalLineNumberMarker removeMarker = null; loop: for (Iterator<LocalLineNumberMarker> iterator = list.iterator(); iterator.hasNext();) { LocalLineNumberMarker marker = iterator.next(); if (marker.varIndex == index) { lineNumber = marker.lineNumber; removeMarker = marker; break loop; } } if (removeMarker != null) { list.remove(removeMarker); if (list.isEmpty()) { this.labelsToLocalMarkers.remove(start); } } } if (lineNumber == -1) { return; } if (signature != null) { List<Reference> references = ReferenceExtractor.this.processSignature(name, signature, IReference.REF_PARAMETERIZED_VARIABLE, METHOD); for (Iterator<Reference> iterator = references.iterator(); iterator.hasNext();) { Reference reference = iterator.next(); reference.setLineNumber(lineNumber); } } else { Type type = Type.getType(desc); if (type.getSort() == Type.OBJECT) { Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_LOCALVARIABLEDECL); if (reference != null) { reference.setLineNumber(lineNumber); } } } } } @Override public void visitLdcInsn(Object cst) { if (cst instanceof Type) { Type type = (Type) cst; Reference reference = ReferenceExtractor.this.addTypeReference(type, IReference.REF_CONSTANTPOOL); if (reference != null) { this.linePositionTracker.addLocation(reference); } } else if (cst instanceof String) { String str = (String) cst; this.stringLiteral = (Util.EMPTY_STRING.equals(str) ? null : str); } } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { Type ctype = this.getTypeFromDescription(desc); Reference reference = ReferenceExtractor.this.addTypeReference(ctype, IReference.REF_ANNOTATION_USE); if (reference != null) { linePositionTracker.addLocation(reference); } return null; } } /** * {@link FieldVisitor} to track use of types in annotations * * @since 1.0.600 */ class ClassFileFieldVisitor extends FieldVisitor { ClassFileFieldVisitor() { super(Opcodes.ASM5); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { addTypeReference(Type.getType(desc), IReference.REF_ANNOTATION_USE); return null; } @Override public void visitAttribute(Attribute attr) { } @Override public void visitEnd() { exitMember(); } } /** * @since 1.1 */ static class FieldTracker { HashMap<String, List<Reference>> accessors = new HashMap<String, List<Reference>>(); ArrayList<Reference> fields = new ArrayList<Reference>(); ReferenceExtractor extractor = null; /** * Constructor */ public FieldTracker(ReferenceExtractor extractor) { this.extractor = extractor; } /** * Add a field to be tracked * * @param field */ public void addField(Reference ref) { if (ref != null) { fields.add(ref); } } /** * Add an accessor to be tracked * * @param accessor */ public void addAccessor(Reference ref) { if (ref != null) { String key = ref.getReferencedMemberName(); List<Reference> refs = accessors.get(key); if (refs == null) { refs = new ArrayList<Reference>(); accessors.put(key, refs); } refs.add(ref); } } /** * Resolve any synthetic field access to their accessor */ public void resolveSyntheticFields() { Reference field = null; List<Reference> refs = null; for (int i = 0; i < fields.size(); i++) { field = fields.get(i); refs = accessors.get(field.getMember().getName()); if (refs != null) { for (Reference accessor : refs) { Reference refer = Reference.fieldReference(accessor.getMember(), field.getReferencedTypeName(), field.getReferencedMemberName(), field.getReferenceKind()); refer.setLineNumber(accessor.getLineNumber()); this.extractor.collector.add(refer); } // we resolved it, remove it this.extractor.collector.remove(field); } } } } static class LinePositionTracker { List<Object> labelsAndLocations; SortedSet<LineInfo> lineInfos; List<LabelInfo> catchLabelInfos; HashMap<Label, Integer> lineMap; public LinePositionTracker() { this.labelsAndLocations = new ArrayList<Object>(); this.lineInfos = new TreeSet<LineInfo>(); this.catchLabelInfos = new ArrayList<LabelInfo>(); this.lineMap = new HashMap<Label, Integer>(); } void addLocation(Reference location) { this.labelsAndLocations.add(location); } void addLineInfo(int line, Label label) { this.lineInfos.add(new LineInfo(line, label)); this.lineMap.put(label, new Integer(line)); } void addCatchLabelInfos(Reference location, Label label) { this.catchLabelInfos.add(new LabelInfo(location, label)); } void addLabel(Label label) { this.labelsAndLocations.add(label); } public void computeLineNumbers() { if (this.lineInfos.size() < 1 || this.labelsAndLocations.size() < 1) { // nothing to do return; } Iterator<LineInfo> lineInfosIterator = this.lineInfos.iterator(); LineInfo firstLineInfo = lineInfosIterator.next(); int currentLineNumber = firstLineInfo.line; List<LabelInfo> remainingCatchLabelInfos = new ArrayList<LabelInfo>(); for (Iterator<LabelInfo> iterator = this.catchLabelInfos.iterator(); iterator.hasNext();) { LabelInfo catchLabelInfo = iterator.next(); Integer lineValue = this.lineMap.get(catchLabelInfo.label); if (lineValue != null) { catchLabelInfo.location.setLineNumber(lineValue.intValue()); } else { remainingCatchLabelInfos.add(catchLabelInfo); } } // Iterate over List of Labels and SourceLocations. List<Object> computedEntries = new ArrayList<Object>(); for (Iterator<Object> iterator = this.labelsAndLocations.iterator(); iterator.hasNext();) { Object current = iterator.next(); if (current instanceof Label) { // label Integer lineValue = this.lineMap.get(current); if (lineValue != null) { computedEntries.add(new LineInfo(lineValue.intValue(), (Label) current)); } else { computedEntries.add(current); } } else { // location computedEntries.add(current); } } List<LabelInfo> remaingEntriesTemp; for (Iterator<Object> iterator = computedEntries.iterator(); iterator.hasNext();) { Object current = iterator.next(); if (current instanceof Label) { // try to set the line number for remaining catch labels if (remainingCatchLabelInfos != null) { remaingEntriesTemp = new ArrayList<LabelInfo>(); loop: for (Iterator<LabelInfo> catchLabelInfosIterator = remainingCatchLabelInfos .iterator(); catchLabelInfosIterator.hasNext();) { LabelInfo catchLabelInfo = catchLabelInfosIterator.next(); if (!current.equals(catchLabelInfo.label)) { remaingEntriesTemp.add(catchLabelInfo); continue loop; } catchLabelInfo.location.setLineNumber(currentLineNumber); } if (remaingEntriesTemp.size() == 0) { remainingCatchLabelInfos = null; } else { remainingCatchLabelInfos = remaingEntriesTemp; } } } else if (current instanceof Reference) { Reference ref = (Reference) current; if (ref.getLineNumber() == -1) { ref.setLineNumber(currentLineNumber); } else { currentLineNumber = ref.getLineNumber(); } } else if (current instanceof LineInfo) { LineInfo lineInfo = (LineInfo) current; currentLineNumber = lineInfo.line; } } } } static class LabelInfo { public Reference location; public Label label; public LabelInfo(Reference location, Label label) { this.location = location; this.label = label; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append('(').append(this.label).append(',').append(this.location).append(')'); return String.valueOf(buffer); } } static class LineInfo implements Comparable<Object> { int line; Label label; LineInfo(int line, Label label) { this.line = line; this.label = label; } @Override public int compareTo(Object o) { return this.line - ((LineInfo) o).line; } @Override public boolean equals(Object obj) { if (obj instanceof LineInfo) { LineInfo lineInfo2 = (LineInfo) obj; return this.line == lineInfo2.line && this.label.equals(lineInfo2.label); } return super.equals(obj); } @Override public int hashCode() { return this.line + (this.label != null ? this.label.hashCode() : 0); } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append('(').append(this.line).append(',').append(this.label).append(')'); return String.valueOf(buffer); } } static class LocalLineNumberMarker { int lineNumber; int varIndex; public LocalLineNumberMarker(int line, int varIndex) { this.lineNumber = line; this.varIndex = varIndex; } @Override public boolean equals(Object obj) { if (obj instanceof LocalLineNumberMarker) { LocalLineNumberMarker marker = (LocalLineNumberMarker) obj; return this.lineNumber == marker.lineNumber && this.varIndex == marker.varIndex; } return false; } @Override public int hashCode() { return this.varIndex; } } /** * The list we collect references in. Entries in the list are of the type * {@link org.eclipse.pde.api.tools.internal.provisional.builder.IReference} */ Set<Reference> collector = null; /** * The full internal name of the class we are extracting references from */ private String classname = null; /** * Current type being visited. */ IApiType fType; /** * Stack of members being visited. When a member is entered its element * descriptor is pushed onto the stack. When a member is exited, the stack * is popped. */ Stack<IApiMember> fMemberStack = new Stack<IApiMember>(); /** * Stack of super types *names* (String) being visited. When a type is * entered, its super type is pushed onto the stack. When a type is exited, * the stack is popped. */ Stack<String> fSuperStack = new Stack<String>(); /** * Mapping of anonymous type names to their reference */ HashMap<String, List<Reference>> fAnonymousTypes = new HashMap<String, List<Reference>>(); /** * Whether to extract references to elements within the classfile being * scanned. */ private boolean fIncludeLocalRefs = false; /** * Bit mask of {@link ReferenceModifiers} to extract. */ private int fReferenceKinds = 0; /** * Track synthetic field / accessor * * @since 1.1 */ FieldTracker fieldtracker = null; /** * The version for the class being visited * * @since 1.0.600 */ private int fVersion; /** * Bit mask that determines if we need to visit members */ private static final int VISIT_MEMBERS_MASK = IReference.MASK_REF_ALL ^ (IReference.REF_EXTENDS | IReference.REF_IMPLEMENTS); /** * If members should be visited for type visits */ private boolean fIsVisitMembers = false; /** * Current field being visited, or <code>null</code> (when not within a * field). */ private ClassFileSignatureVisitor signaturevisitor = new ClassFileSignatureVisitor(); static int TYPE = 0, FIELD = 1, METHOD = 2; /** * {@link FieldVisitor} used to track and collect references to annotation * types * * @since 1.0.600 */ private ClassFileFieldVisitor fieldvisitor = new ClassFileFieldVisitor(); /** * Constructor * * @param type the type to extract references from * @param collector the listing of references to annotate from this pass * @param referenceKinds kinds of references to extract as defined by * {@link ReferenceModifiers} */ public ReferenceExtractor(IApiType type, Set<Reference> collector, int referenceKinds) { super(Opcodes.ASM5, new ClassNode()); fType = type; this.collector = collector; fReferenceKinds = referenceKinds; fIsVisitMembers = (VISIT_MEMBERS_MASK & fReferenceKinds) > 0; fieldtracker = new FieldTracker(this); } /** * Constructor * * @param type * @param collector * @param referenceKinds * @param tracker */ protected ReferenceExtractor(IApiType type, Set<Reference> collector, int referenceKinds, FieldTracker tracker) { super(Opcodes.ASM5, new ClassNode()); fType = type; this.collector = collector; fReferenceKinds = referenceKinds; fIsVisitMembers = (VISIT_MEMBERS_MASK & fReferenceKinds) > 0; fieldtracker = tracker; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Reference extractor for: "); //$NON-NLS-1$ buffer.append(fType.getName()); buffer.append("\n"); //$NON-NLS-1$ buffer.append("Reference kinds: "); //$NON-NLS-1$ buffer.append(Reference.getReferenceText(fReferenceKinds)); buffer.append("\n"); //$NON-NLS-1$ buffer.append("Is visiting members: "); //$NON-NLS-1$ buffer.append(fIsVisitMembers); return buffer.toString(); } /** * Returns whether to consider a reference to the specified type. Configured * by setting to include references within the same class file. * * @param owner * @return true if considered, false otherwise */ protected boolean consider(String owner) { if (this.fIncludeLocalRefs) { return true; } return !(this.classname.equals(owner) || this.classname.startsWith(owner) || "<clinit>".equals(owner) //$NON-NLS-1$ || "this".equals(owner)); //$NON-NLS-1$ } /** * Returns whether the specified reference should be considered when * extracting references. Configured by setting on whether to include * references within the same class file. * * @param ref reference * @return whether to include the reference */ protected boolean consider(Reference ref) { int kind = ref.getReferenceKind(); if ((kind & fReferenceKinds) == 0) { return false; } if (this.fIncludeLocalRefs) { return true; } // don't consider references to anonymous types or elements in them String referencedTypeName = ref.getReferencedTypeName(); if (kind == IReference.REF_VIRTUALMETHOD || kind == IReference.REF_OVERRIDE || kind == IReference.REF_GETFIELD || kind == IReference.REF_PUTFIELD) { return true; } if (referencedTypeName.startsWith(fType.getName())) { // don't include references within this type or a member type if (referencedTypeName.length() > fType.getName().length()) { return referencedTypeName.charAt(fType.getName().length()) != '$'; } return false; } return true; } /** * Returns the full internal name (if available) from the given simple name. * The returned name has been modified to be '.' separated * * @param name * @return */ protected String processName(String name) { String newname = name; Type type = Type.getObjectType(name); if (type != null && type.getSort() == Type.OBJECT) { newname = type.getInternalName(); } return newname.replaceAll("/", "."); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Adds a reference to the given type from the current member. Discards the * reference if the type corresponds to the class file being scanned or if * the type is a primitive type. * * @param type referenced type * @param linenumber line number where referenced * @param kind kind of reference * @return reference added, or <code>null</code> if none */ protected Reference addTypeReference(Type type, int kind) { Type rtype = this.resolveType(type.getDescriptor()); if (rtype != null) { return addReference(Reference.typeReference(getMember(), rtype.getClassName(), kind)); } return null; } /** * Adds a reference to the given field from the current member. Discards the * reference if the field is defined in the class file being scanned. * * @param declaringType type declaring the field being referenced * @param name of the field being referenced * @param linenumber line number where referenced * @param kind kind of reference * @return reference added, or <code>null</code> if none */ protected Reference addFieldReference(Type declaringType, String name, int kind) { Type rtype = this.resolveType(declaringType.getDescriptor()); if (rtype != null) { return addReference(Reference.fieldReference(getMember(), rtype.getClassName(), name, kind)); } return null; } /** * Adds a reference to the given method from the current member. Discards * the reference if the method is defined in the class file being scanned. * * @param declaringType type declaring the method (but could be a virtual * lookup) * @param name of the method being referenced * @param signature signature of the method * @param linenumber line number where referenced * @param kind kind of reference * @param flags the flags for the reference * @return reference added, or <code>null</code> if none */ protected Reference addMethodReference(Type declaringType, String name, String signature, int kind, int flags) { Type rtype = this.resolveType(declaringType.getDescriptor()); if (rtype != null) { return this.addReference( Reference.methodReference(getMember(), rtype.getClassName(), name, signature, kind, flags)); } return null; } /** * Adds a reference to the given target member from the given line number in * the class file being scanned. If the target member is contained in the * class file being scanned it is discarded based on the setting to include * local references. * * @param target reference * @param reference added, or <code>null</code> if none */ protected Reference addReference(Reference target) { if (this.consider(target)) { this.collector.add(target); return target; } return null; } /** * Processes the member signature from the specified type with the given * signature and kind. A member can be either a type, method, field or local * variable * * @param name the name of the member to process * @param signature the signature of the member to process * @param kind the kind * @param type the type of member wanting to use the visitor * * @return the collection of references created for this signature */ protected List<Reference> processSignature(String name, String signature, int kind, int type) { SignatureReader reader = new SignatureReader(signature); this.signaturevisitor.kind = kind; this.signaturevisitor.name = this.processName(name); this.signaturevisitor.signature = signature; this.signaturevisitor.originalkind = kind; this.signaturevisitor.argumentcount = 0; this.signaturevisitor.type = type; if (kind == IReference.REF_PARAMETERIZED_TYPEDECL || kind == IReference.REF_PARAMETERIZED_METHODDECL) { reader.accept(this.signaturevisitor); } else { reader.acceptType(this.signaturevisitor); } List<Reference> result = new ArrayList<Reference>(); result.addAll(this.signaturevisitor.references); this.collector.addAll(this.signaturevisitor.references); this.signaturevisitor.reset(); return result; } /** * Resolves the type from the string description. This method takes only * type descriptions as a parameter, all else will throw an exception from * the ASM framework If the description is an array, the underlying type of * the array is returned. * * @param desc * @return the {@link Type} of the description or <code>null</code> */ protected Type resolveType(String desc) { Type type = Type.getType(desc); if (type.getSort() == Type.OBJECT) { return type; } if (type.getSort() == Type.ARRAY) { type = type.getElementType(); if (type.getSort() == Type.OBJECT) { return type; } } return null; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.fVersion = version; this.classname = this.processName(name); if (ApiPlugin.DEBUG_REFERENCE_EXTRACTOR) { System.out.println("Starting visit of type: [" + this.fType.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } this.enterMember(this.fType); // if there is a signature we get more information from it, so we don't // need to do both if (signature != null) { this.processSignature(name, signature, IReference.REF_PARAMETERIZED_TYPEDECL, TYPE); } else { if ((access & Opcodes.ACC_INTERFACE) != 0) { // the type is an interface and we need to treat the interfaces // set as extends, not implements Type supertype = null; for (int i = 0; i < interfaces.length; i++) { supertype = Type.getObjectType(interfaces[i]); this.addTypeReference(supertype, IReference.REF_EXTENDS); this.fSuperStack.add(supertype.getClassName()); } } else { Type supertype = null; if (superName != null) { supertype = Type.getObjectType(superName); this.addTypeReference(supertype, IReference.REF_EXTENDS); this.fSuperStack.add(supertype.getClassName()); } for (int i = 0; i < interfaces.length; i++) { supertype = Type.getObjectType(interfaces[i]); this.addTypeReference(supertype, IReference.REF_IMPLEMENTS); } } } } @Override public void visitEnd() { this.exitMember(); if (!this.fSuperStack.isEmpty()) { String typeName = this.fSuperStack.pop(); if (ApiPlugin.DEBUG_REFERENCE_EXTRACTOR) { System.out.println("ending visit of type: [" + typeName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } } if (!this.fType.isMemberType()) { fieldtracker.resolveSyntheticFields(); } } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if (fIsVisitMembers) { IApiType owner = (IApiType) this.getMember(); IApiField field = owner.getField(name); if (field == null) { ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_field, new String[] { name, Signatures.getQualifiedTypeSignature(owner) }))); // if we can't find the method there is no point trying to // process it return null; } this.enterMember(field); if ((access & Opcodes.ACC_SYNTHETIC) == 0) { if (signature != null) { this.processSignature(name, signature, IReference.REF_PARAMETERIZED_FIELDDECL, FIELD); } else { this.addTypeReference(Type.getType(desc), IReference.REF_FIELDDECL); } } else { fieldtracker.addField(addTypeReference(Type.getType(desc), IReference.REF_FIELDDECL)); } return fieldvisitor; } return null; } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { try { String pname = processName(name); if (fType.getName().equals(pname) || !pname.startsWith(fType.getName())) { return; } IApiComponent comp = fType.getApiComponent(); if (comp == null) { return; } AbstractApiTypeRoot root = (AbstractApiTypeRoot) comp.findTypeRoot(pname); if (root != null) { IApiType type = root.getStructure(); if (type == null) { // do nothing for a bad classfile return; } Set<Reference> refs = processInnerClass(type, fReferenceKinds); if (type.isAnonymous() || type.isLocal()) { // visit the class files for the dependent anonymous and // local inner types // set a line number for all references with no line numbers List<Reference> allRefs = new ArrayList<Reference>(); for (Reference reference : refs) { if (reference.getLineNumber() < 0) { allRefs.add(reference); } } fAnonymousTypes.put(pname, allRefs); } if (refs != null && !refs.isEmpty()) { this.collector.addAll(refs); } } } catch (CoreException ce) { } } /** * Processes the dependent inner class * * @param type * @param refkinds * @return * @throws CoreException */ private Set<Reference> processInnerClass(IApiType type, int refkinds) throws CoreException { HashSet<Reference> refs = new HashSet<Reference>(); ReferenceExtractor extractor = new ReferenceExtractor(type, refs, refkinds, this.fieldtracker); ClassReader reader = new ClassReader(((AbstractApiTypeRoot) type.getTypeRoot()).getContents()); reader.accept(extractor, ClassReader.SKIP_FRAMES); return refs; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { try { addTypeReference(Type.getType(desc), IReference.REF_ANNOTATION_USE); } catch (ArrayIndexOutOfBoundsException e) { // when file has compile errors this gets thrown, but we can ignore // it } return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (fIsVisitMembers) { IApiMember member = this.getMember(); IApiType owner = null; if (member instanceof IApiType) { owner = (IApiType) member; } else { try { owner = member.getEnclosingType(); } catch (CoreException e) { // should not happen for field or method ApiPlugin.log(e.getStatus()); } } if (owner == null) { return null; } IApiMethod method = owner.getMethod(name, desc); if (method == null) { ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_method, new String[] { name, desc, Signatures.getQualifiedTypeSignature(owner) }))); // if we can't find the method there is no point trying to // process it return null; } this.enterMember(method); // record potential method override reference if ((access & (Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) > 0) { try { IApiType def = null; if (fVersion >= Opcodes.V1_8) { // See if we are overriding a default interface method def = getDefaultDefined(owner, name, desc, true); } if (def != null) { addReference(Reference.methodReference(method, def.getName(), method.getName(), method.getSignature(), IReference.REF_OVERRIDE, IReference.F_DEFAULT_METHOD)); } else if (!this.fSuperStack.isEmpty()) { String superTypeName = this.fSuperStack.peek(); addReference(Reference.methodReference(method, superTypeName, method.getName(), method.getSignature(), IReference.REF_OVERRIDE)); } } catch (CoreException e) { // Do nothing, skip this reference } } int argumentcount = 0; if ((access & Opcodes.ACC_SYNTHETIC) == 0) { if (signature != null) { this.processSignature(name, signature, IReference.REF_PARAMETERIZED_METHODDECL, METHOD); argumentcount = this.signaturevisitor.argumentcount; } else { Type[] arguments = Type.getArgumentTypes(desc); for (int i = 0; i < arguments.length; i++) { Type type = arguments[i]; this.addTypeReference(type, IReference.REF_PARAMETER); argumentcount += type.getSize(); } this.addTypeReference(Type.getReturnType(desc), IReference.REF_RETURNTYPE); if (exceptions != null) { for (int i = 0; i < exceptions.length; i++) { this.addTypeReference(Type.getObjectType(exceptions[i]), IReference.REF_THROWS); } } } } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (mv != null && ((access & (Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT)) == 0)) { return new ClassFileMethodVisitor(mv, name, argumentcount); } } return null; } /** * Called when a member is entered. Pushes the member onto the member stack. * * @param member current member */ protected void enterMember(IApiMember member) { this.fMemberStack.push(member); } /** * Called when a member is exited. Pops the top member off the stack. */ protected void exitMember() { this.fMemberStack.pop(); } /** * Returns the member currently being visited. * * @return current member */ protected IApiMember getMember() { return this.fMemberStack.peek(); } /** * Find out if the method declaration is a default method and return the * type defining it. Uses the JLS specified order of lookup between * superclasses and superinterfaces. * * @param type the type used as a starting point for the search, will not be * searched if <code>isOverride</code> is <code>true</code> * @param name name of the method * @param signature signature of the method * @param isOverride is <code>true</code> the provided IApiType will not be * searched for a declaration * @return the IApiType containing the default method definition or * <code>null</code> * @throws CoreException */ static IApiType getDefaultDefined(IApiType type, String name, String signature, boolean isOverride) throws CoreException { if (type != null) { if (!isOverride) { IApiMethod method = type.getMethod(name, signature); if (method != null) { if (method.isDefaultMethod()) { return type; } } } // TODO We should skip checking super class if it is // java.lang.Object (or system library class) IApiType superclass = getDefaultDefined(type.getSuperclass(), name, signature, false); if (superclass != null) { return superclass; } IApiType ints[] = type.getSuperInterfaces(); for (int i = 0; i < ints.length; i++) { IApiType superint = getDefaultDefined(ints[i], name, signature, false); if (superint != null) { return superint; } } } return null; } }