/******************************************************************************* * Copyright (c) 2006 Mountainminds GmbH & Co. KG * This software is provided under the terms of the Eclipse Public License v1.0 * See http://www.eclipse.org/legal/epl-v10.html. * * $Id$ ******************************************************************************/ package com.mountainminds.eclemma.internal.core.analysis; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import com.mountainminds.eclemma.internal.core.DebugOptions; import com.mountainminds.eclemma.internal.core.DebugOptions.ITracer; /** * Internal utility class for traversal of all types within a list of package * fragment roots. * * @author Marc R. Hoffmann * @version $Revision$ */ public class TypeTraverser { private static final ITracer TRACER = DebugOptions.ANALYSISTRACER; private final IPackageFragmentRoot[] roots; /** * Creates a traverser for the given list of package fragment roots. * * @param roots * list of package fragment roots for traversal */ public TypeTraverser(IPackageFragmentRoot[] roots) { this.roots = roots; } /** * Processes all types and methods reporting all types found to the given * {@link ITypeVisitor} instance. * * @param visitor * type visitor * @param monitor * progress monitor to report progress and allow cancelation * @throws JavaModelException * thrown by the underlying Java model */ public void process(ITypeVisitor visitor, IProgressMonitor monitor) throws JavaModelException { monitor.beginTask("", roots.length); //$NON-NLS-1$ for (int i = 0; i < roots.length && !monitor.isCanceled(); i++) { processPackageFragmentRoot(visitor, roots[i], new SubProgressMonitor( monitor, 1)); } visitor.done(); monitor.done(); } private void processPackageFragmentRoot(ITypeVisitor visitor, IPackageFragmentRoot root, IProgressMonitor monitor) throws JavaModelException { if (isOnClasspath(root)) { IJavaElement[] fragments = root.getChildren(); monitor.beginTask("", fragments.length); //$NON-NLS-1$ for (int i = 0; i < fragments.length && !monitor.isCanceled(); i++) { IPackageFragment fragment = (IPackageFragment) fragments[i]; IProgressMonitor submonitor = new SubProgressMonitor(monitor, 1); processPackageFragment(visitor, fragment, submonitor); } } else { TRACER.trace("Package fragment root {0} not on classpath.", //$NON-NLS-1$ root.getPath()); } monitor.done(); } /** * This methods checks whether the given package fragment root is still on the * classpath. This check is required as the user might change the classpath * and old coverage sessions afterwards (SF #1836551). * * @param root package fragment root * @return true, if the classpath entry still exists * @throws JavaModelException */ private boolean isOnClasspath(IPackageFragmentRoot root) throws JavaModelException { IPath path = root.getPath(); IJavaProject project = root.getJavaProject(); return project.findPackageFragmentRoot(path) != null; } private void processPackageFragment(ITypeVisitor visitor, IPackageFragment fragment, IProgressMonitor monitor) throws JavaModelException { switch (fragment.getKind()) { case IPackageFragmentRoot.K_SOURCE: ICompilationUnit[] units = fragment.getCompilationUnits(); monitor.beginTask("", units.length); //$NON-NLS-1$ for (int i = 0; i < units.length && !monitor.isCanceled(); i++) { processCompilationUnit(visitor, units[i], monitor); monitor.worked(1); } break; case IPackageFragmentRoot.K_BINARY: IClassFile[] classfiles = fragment.getClassFiles(); monitor.beginTask("", classfiles.length); //$NON-NLS-1$ for (int i = 0; i < classfiles.length && !monitor.isCanceled(); i++) { processClassFile(visitor, classfiles[i], monitor); monitor.worked(1); } break; } monitor.done(); } private void processCompilationUnit(ITypeVisitor visitor, ICompilationUnit unit, IProgressMonitor monitor) throws JavaModelException { IType[] types = unit.getTypes(); for (int i = 0; i < types.length && !monitor.isCanceled(); i++) { IType type = types[i]; processType(visitor, new BinaryTypeName(type), type, monitor); } } private void processClassFile(ITypeVisitor visitor, IClassFile file, IProgressMonitor monitor) throws JavaModelException { IType type = file.getType(); processType(visitor, new BinaryTypeName(type), type, monitor); } private void processType(ITypeVisitor visitor, BinaryTypeName btn, IType type, IProgressMonitor monitor) throws JavaModelException { String binaryname = btn.toString(); monitor.subTask(binaryname); visitor.visit(type, binaryname); IJavaElement[] children = type.getChildren(); for (int i = 0; i < children.length && !monitor.isCanceled(); i++) { IJavaElement child = children[i]; switch (child.getElementType()) { case IJavaElement.TYPE: IType nestedtype = (IType) child; processType(visitor, btn.nest(nestedtype), nestedtype, monitor); break; case IJavaElement.METHOD: case IJavaElement.INITIALIZER: case IJavaElement.FIELD: processAnonymousInnerTypes(visitor, btn, (IMember) child, monitor); break; } } } private void processAnonymousInnerTypes(ITypeVisitor visitor, BinaryTypeName btn, IMember member, IProgressMonitor monitor) throws JavaModelException { IJavaElement[] types = member.getChildren(); for (int i = 0; i < types.length && !monitor.isCanceled(); i++) { IType type = (IType) types[i]; processType(visitor, btn.nest(type), type, monitor); } } /** * Internal utility to calculate binary names of nested classes. */ private static class BinaryTypeName { private static class Ctr { private int i = 0; public int inc() { return ++i; } public String toString() { return Integer.toString(i); } } private final String rootname; private final String typename; private final Ctr ctr = new Ctr(); private BinaryTypeName(String rootname, String typename) { this.rootname = rootname; this.typename = typename; } public BinaryTypeName(IType roottype) { this.rootname = roottype.getFullyQualifiedName().replace('.', '/'); this.typename = this.rootname; } public BinaryTypeName nest(IType type) throws JavaModelException { if (type.isAnonymous()) { return new BinaryTypeName(rootname, typename + '$' + ctr.inc()); } else { return new BinaryTypeName(rootname, typename + '$' + type.getElementName()); } } public String toString() { return typename; } } }