Java tutorial
/******************************************************************************* * Copyright (c) 2008, 2010 SpringSource 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: * SpringSource * Andrew Eisenberg (initial implementation) *******************************************************************************/ package org.eclipse.ajdt.core.model; import static org.eclipse.ajdt.core.javaelements.AspectElement.JEM_ASPECT_TYPE; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.aspectj.ajde.core.AjCompiler; import org.aspectj.asm.AsmManager; import org.aspectj.asm.HierarchyWalker; import org.aspectj.asm.IHierarchy; import org.aspectj.asm.IProgramElement; import org.aspectj.asm.IRelationship; import org.aspectj.asm.IRelationshipMap; import org.aspectj.asm.internal.RelationshipMap; import org.aspectj.bridge.ISourceLocation; import org.eclipse.ajdt.core.AspectJCore; import org.eclipse.ajdt.core.AspectJPlugin; import org.eclipse.ajdt.core.CoreUtils; import org.eclipse.ajdt.core.builder.AJBuilder; import org.eclipse.ajdt.core.builder.IAJBuildListener; import org.eclipse.ajdt.core.javaelements.AJCodeElement; import org.eclipse.ajdt.core.javaelements.AJCompilationUnit; import org.eclipse.ajdt.core.javaelements.AspectElement; import org.eclipse.ajdt.core.javaelements.AspectJMemberElement; import org.eclipse.ajdt.core.javaelements.CompilationUnitTools; import org.eclipse.ajdt.core.lazystart.IAdviceChangedListener; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IBuffer; 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.ILocalVariable; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IOpenable; import org.eclipse.jdt.core.IPackageDeclaration; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.core.CompilationUnit; import org.eclipse.jdt.internal.core.ImportContainer; import org.eclipse.jdt.internal.core.ImportDeclaration; import org.eclipse.jdt.internal.core.JavaElement; import org.eclipse.jdt.internal.core.JavaModelManager; /** * * @created Sep 8, 2008 * * This class is a facade for the AspectJ compiler structure model and relationship * map. * <p> * One object of this class exists for each AspectJ project. * It is created during a full build and lasts until a clean build or * another full build is performed. * <p> * Objects of this class should be instantiated using the * {@link AJProjectModelFactory} class. */ public class AJProjectModelFacade { public final static IJavaElement ERROR_JAVA_ELEMENT = new CompilationUnit(null, "ERROR_JAVA_ELEMENT", null); private static class ProjectModelBuildListener implements IAJBuildListener { private Set<IProject> beingBuilt = new HashSet<IProject>(); private Set<IProject> beingCleaned = new HashSet<IProject>(); public synchronized void postAJBuild(int kind, IProject project, boolean noSourceChanges, Map<IFile, List<CategorizedProblem>> newProblems) { beingBuilt.remove(project); } public synchronized void postAJClean(IProject project) { beingCleaned.remove(project); } public synchronized void preAJBuild(int kind, IProject project, IProject[] requiredProjects) { if (kind == IncrementalProjectBuilder.CLEAN_BUILD) { beingCleaned.add(project); } else { beingBuilt.add(project); } } public void addAdviceListener(IAdviceChangedListener adviceListener) { } public void removeAdviceListener(IAdviceChangedListener adviceListener) { } synchronized boolean isCurrentlyBuilding(IProject project) { return beingBuilt.contains(project) || beingCleaned.contains(project); } } /** * The aspectj program hierarchy */ IHierarchy structureModel; /** * stores crosscutting relationships between structure elements */ IRelationshipMap relationshipMap; /** * the java project that this project model is associated with */ private final IProject project; boolean isInitialized; boolean disposed; private static ProjectModelBuildListener buildListener = new ProjectModelBuildListener(); /** * creates a new project model facade for this project */ AJProjectModelFacade(IProject project) { this.project = project; this.disposed = false; this.isInitialized = false; } public static void installListener() { AJBuilder.addAJBuildListener(buildListener); } /** * grabs the structure and relationships for this project * <p> * called by the before advice in EnsureInitialized aspect */ synchronized void init() { if (!buildListener.isCurrentlyBuilding(project)) { AjCompiler compiler = AspectJPlugin.getDefault().getCompilerFactory().getCompilerForProject(project); AsmManager existingState = compiler.getModel(); if (existingState != null) { relationshipMap = existingState.getRelationshipMap(); structureModel = existingState.getHierarchy(); if (relationshipMap != null && structureModel != null) { isInitialized = true; } } } else { // can't initialize...building } } /** * @return true if the AspectJ project model has been found. false otherwise. */ public boolean hasModel() { return isInitialized && !disposed && !buildListener.isCurrentlyBuilding(project); } /** * @param handle an AspectJ program element handle * @return a program element for the handle, or an empty element * if the program element is not found */ public IProgramElement getProgramElement(String handle) { IProgramElement ipe = structureModel.findElementForHandleOrCreate(handle, false); if (ipe != null) { return ipe; } else { // occurs when the handles are not working properly // AspectJPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, AspectJPlugin.PLUGIN_ID, // "Could not find the AspectJ program element for handle: " + // handle, new RuntimeException())); return IHierarchy.NO_STRUCTURE; } } /** * @return the 1-based line number for the given java element */ public int getJavaElementLineNumber(IJavaElement je) { IProgramElement ipe = javaElementToProgramElement(je); return ipe.getSourceLocation() != null ? ipe.getSourceLocation().getLine() : 1; } /** * @return a human readable name for the given Java element that is * meant to be displayed on menus and labels. */ public String getJavaElementLinkName(IJavaElement je) { IProgramElement ipe = javaElementToProgramElement(je); if (ipe != IHierarchy.NO_STRUCTURE) { // null if model isn't initialized String name = ipe.toLinkLabelString(false); if ((name != null) && (name.length() > 0)) { return name; } } // use element name instead, qualified with parent String name = je.getElementName(); if (je instanceof ISourceReference && !(je instanceof ITypeRoot)) { IJavaElement parent = je.getParent(); while (parent != null && !(parent instanceof ITypeRoot)) { name = parent.getElementName() + "." + name; parent = parent.getParent(); } } return name; } /** * @return a program element that corresponds to the given java element. */ public IProgramElement javaElementToProgramElement(IJavaElement je) { if (!isInitialized) { return IHierarchy.NO_STRUCTURE; } String ajHandle = je.getHandleIdentifier(); boolean isBinary = false; if (isBinaryHandle(ajHandle) || je.isReadOnly()) { ajHandle = convertToAspectJBinaryHandle(ajHandle, false); isBinary = true; } else if (isFromExternalProject(je)) { ajHandle = convertToAspectJBinaryHandle(ajHandle, true); isBinary = true; } // check to see if we need to replace { (compilation unit) with * (aj compilation unit) // if using cuprovider, then aj compilation units have {, but needs to change to * ICompilationUnit cu = null; if (je instanceof IMember) { cu = ((IMember) je).getCompilationUnit(); } else if (je instanceof IPackageDeclaration) { IJavaElement parent = ((IPackageDeclaration) je).getParent(); if (parent instanceof ICompilationUnit) { cu = (ICompilationUnit) parent; } } else if (je instanceof AJCodeElement) { cu = ((AJCodeElement) je).getCompilationUnit(); // get the occurence count int count = ((AJCodeElement) je).occurrenceCount; // need the first bang after the last close paren int lastParen = ajHandle.lastIndexOf(')'); int firstBang = ajHandle.indexOf(JavaElement.JEM_COUNT, lastParen); if (firstBang > -1) { ajHandle = ajHandle.substring(0, firstBang); if (count > 1) { // there is more than one element // with this name ajHandle += "" + JavaElement.JEM_COUNT + count; } } } else if (je instanceof ILocalVariable) { IOpenable openable = ((ILocalVariable) je).getOpenable(); cu = openable instanceof ICompilationUnit ? (ICompilationUnit) openable : null; } else if (je instanceof ImportDeclaration) { cu = ((ImportDeclaration) je).getCompilationUnit(); } else if (je instanceof ImportContainer) { cu = ((ImportContainer) je).getCompilationUnit(); } else if (je instanceof ICompilationUnit) { cu = (ICompilationUnit) je; } if (cu != null) { IResource resource = cu.getResource(); if (resource != null && resource.exists() && CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(resource.getName())) { ajHandle = ajHandle.replaceFirst("" + JavaElement.JEM_ESCAPE + JavaElement.JEM_COMPILATIONUNIT, Character.toString(AspectElement.JEM_ASPECT_CU)); } } IProgramElement ipe = structureModel.findElementForHandleOrCreate(ajHandle, false); if (ipe == null) { if (isBinary) { // might be an aspect in a class file. JDT doesn't know it is an aspect // try looking for handle again, but use an Aspect token // problem will be if this is an aspect contained in a class or vice versa ajHandle = ajHandle.replace(JavaElement.JEM_TYPE, AspectElement.JEM_ASPECT_TYPE); ipe = structureModel.findElementForHandleOrCreate(ajHandle, false); } if (ipe == null) { // occurs when the handles are not working properly return IHierarchy.NO_STRUCTURE; } } return ipe; } /** * @param je * @return */ private boolean isFromExternalProject(IJavaElement je) { return !je.getJavaProject().getProject().equals(project); } /** * This will return false in the cases where a java elt exists, but * a program elt does not. This may happen when there are some kinds * of errors in the file that prevent the aspectj compiler from running, but * the Java compiler can still run. * @param je * @return */ public boolean hasProgramElement(IJavaElement je) { return IHierarchy.NO_STRUCTURE != javaElementToProgramElement(je); } private String convertToAspectJBinaryHandle(String ajHandle, boolean isSourceFromDependingProject) { int packageIndex = ajHandle.indexOf(JavaElement.JEM_PACKAGEFRAGMENT); String newHandle = "" + JavaElement.JEM_JAVAPROJECT + project.getName() + JavaElement.JEM_PACKAGEFRAGMENTROOT + "binaries" + ajHandle.substring(packageIndex); if (isSourceFromDependingProject) { // also must convert from a source unit to a binary unit newHandle = newHandle.replace(".aj'", ".class'"); if (AspectJPlugin.USING_CU_PROVIDER) { newHandle = newHandle.replace(JavaElement.JEM_COMPILATIONUNIT, JavaElement.JEM_CLASSFILE); } else { newHandle = newHandle.replace(AspectElement.JEM_ASPECT_CU, JavaElement.JEM_CLASSFILE); } } return newHandle; } private boolean isBinaryHandle(String ajHandle) { int jemClassIndex = ajHandle.indexOf(JavaElement.JEM_CLASSFILE); if (jemClassIndex != -1) { int classFileIndex = ajHandle.indexOf(".class", jemClassIndex); if (classFileIndex != -1) { return true; } } return false; } public IJavaElement programElementToJavaElement(IProgramElement ipe) { return programElementToJavaElement(ipe.getHandleIdentifier()); } public IJavaElement programElementToJavaElement(String ajHandle) { // check to see if this is a spurious handle. // For ITDs, the aspectj compiler generates program elements before the // rest of the program is in place, and they therfore have no parent. // They should not exist and we can ignore them. if (ajHandle.length() == 0 || ajHandle.charAt(0) != JavaElement.JEM_JAVAPROJECT) { return ERROR_JAVA_ELEMENT; } String jHandle = ajHandle; // are we dealing with something inside of a classfile? // if so, then we have to handle it specially // because we want to convert this into a source reference if possible if (isBinaryAspectJHandle(jHandle)) { // Bug 274558 ADE HACK...fix aspect handles that are supposed to be binary, but are not. jHandle = jHandle.replace(AspectElement.JEM_ASPECT_CU, JavaElement.JEM_CLASSFILE); jHandle = jHandle.replace(".aj" + JEM_ASPECT_TYPE, ".class" + JEM_ASPECT_TYPE); return getElementFromClassFile(jHandle); } // if using cuprovider, then we don not use the '*' for Aspect compilation units, // it uses the '{' of Java Compilation Units if (AspectJPlugin.USING_CU_PROVIDER) { jHandle = jHandle.replaceFirst("" + JavaElement.JEM_ESCAPE + AspectElement.JEM_ASPECT_CU, Character.toString(JavaElement.JEM_COMPILATIONUNIT)); } int codeEltIndex = jHandle.indexOf(AspectElement.JEM_CODEELEMENT); if (codeEltIndex != -1) { // because code elements are sub classes of local variables // must make the code element's handle look like a local // variable's handle int countIndex = jHandle.lastIndexOf(JavaElement.JEM_COUNT); int count = 0; if (countIndex > codeEltIndex) { try { count = Integer.parseInt(jHandle.substring(countIndex + 1)); jHandle = jHandle.substring(0, countIndex); } catch (NumberFormatException e) { // if the count is not from the code element, but from one of its parents count = 0; } } jHandle += "!0!0!0!0!I!0!false"; if (count > 1) { jHandle += "" + JavaElement.JEM_COUNT + count; } } // add escapes to various sundries jHandle = jHandle.replaceFirst("declare @", "declare \\\\@"); // declare declarations jHandle = jHandle.replaceFirst("\\.\\*", ".\\\\*"); // on demand imports jHandle = jHandle.replaceAll("\\*>", "\\\\*>"); // wild card type parameters IJavaElement je = AspectJCore.create(jHandle); if (je == null) { // occurs when the handles are not working properly return ERROR_JAVA_ELEMENT; } return je; } private boolean isBinaryAspectJHandle(String ajHandle) { int classFileIndex = ajHandle.indexOf(JavaElement.JEM_CLASSFILE); boolean doIt = false; if (classFileIndex != -1) { int dotClassIndex = ajHandle.indexOf(".class") + ".class".length(); if (dotClassIndex >= ajHandle.length()) { // handle is for the class itself. doIt = true; } else if (dotClassIndex != -1) { // make sure this isn't a code element char typeChar = ajHandle.charAt(dotClassIndex); doIt = (typeChar == AspectElement.JEM_ASPECT_TYPE || typeChar == JavaElement.JEM_TYPE || typeChar == JavaElement.JEM_IMPORTDECLARATION || typeChar == JavaElement.JEM_PACKAGEDECLARATION); } } else { // Bug 274558 ADE HACK...handles are not always right for aspects on aspect path // sometimes the source handle is used, bug should be binary handle if (ajHandle.indexOf("/binaries<") != -1) { doIt = true; } } return doIt; } /** * Inner type for passing around a bunch of useful information for constructing * binary handles */ private class HandleInfo { public HandleInfo(String origAJHandle, String simpleName, String packageName, String qualName, String restHandle, boolean isFile, boolean isType, boolean isInAspect) { this.origAJHandle = origAJHandle; this.simpleName = simpleName; this.packageName = packageName; this.qualName = qualName; this.restHandle = restHandle; this.isFile = isFile; this.isType = isType; this.isInAspect = isInAspect; } final String origAJHandle; final String simpleName; final String packageName; final String qualName; final String restHandle; final boolean isFile; final boolean isType; final boolean isInAspect; boolean isPackageFragment() { return !(isFile || isType || isInAspect); } String sourceTypeQualName() { return qualName.replaceAll("\\$", "\\."); } } /** * In this method, we are not sure we can do better than * finding the Class file or compilation unit, even if * more information is supplied in the handle. * We do our best effort */ private IJavaElement getElementFromClassFile(String jHandle) { HandleInfo handleInfo = qualifiedNameFromBinaryHandle(jHandle); if (handleInfo == null) { AspectJPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, AspectJPlugin.PLUGIN_ID, "Could not find type root for " + jHandle)); return ERROR_JAVA_ELEMENT; } try { if (handleInfo.isPackageFragment()) { IPackageFragment[] frags = findFragment(JavaCore.create(project), handleInfo); if (frags != null && frags.length > 0) { // there may be multiple package fragments with the same name // in different roots. If so, always (arbitrarily) return the first return frags[0]; } } // this gives us the type in the current project. // However, the type may actually be source coming from another project ITypeRoot typeRoot = getCUFromQualifiedName(handleInfo); IJavaElement candidate; if (typeRoot instanceof ICompilationUnit) { // we're in luck... // all this work has taken us straight to the compilation unit candidate = findElementInCU(handleInfo, (ICompilationUnit) typeRoot); } else { // we have a class file. // search the rest of the workspace for this type IResource file = typeRoot.getResource(); IClassFile classFile = (IClassFile) typeRoot; // try to find the source if (file != null && !file.getFileExtension().equals("jar")) { candidate = findElementInBinaryFolder(handleInfo, classFile); } else { // we have a class file in a jar candidate = findElementInJar(handleInfo, classFile); } } if (candidate != null) { return candidate; } else { return ERROR_JAVA_ELEMENT; } } catch (JavaModelException e) { AspectJPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, AspectJPlugin.PLUGIN_ID, "Could not find type root for " + jHandle, e)); return ERROR_JAVA_ELEMENT; } catch (NullPointerException e) { AspectJPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, AspectJPlugin.PLUGIN_ID, "Could not find type root for " + jHandle, e)); return ERROR_JAVA_ELEMENT; } } /** * this type root exists in a jar file. Try to find the * java element specified in the handleInfo. We are not going to be * able to convert this to source */ private IJavaElement findElementInJar(HandleInfo handleInfo, IClassFile classFile) throws JavaModelException { IJavaElement candidate = classFile; if (handleInfo.isType && !handleInfo.isInAspect) { candidate = classFile.getType(); } else if (!handleInfo.isFile) { String newHandle = classFile.getHandleIdentifier() + handleInfo.restHandle; IJavaElement newElt = AspectJCore.create(newHandle); IProgramElement ipe; if (!handleInfo.isFile && (!handleInfo.isType || handleInfo.isInAspect)) { // program element will exist only if coming from aspect path ipe = getProgramElement(handleInfo.origAJHandle); } else { ipe = IHierarchy.NO_STRUCTURE; } if (newElt instanceof AspectJMemberElement && ipe != IHierarchy.NO_STRUCTURE) { JavaModelManager.getJavaModelManager().resetTemporaryCache(); AspectJMemberElement ajElt = (AspectJMemberElement) newElt; ajElt.setStartLocation(offsetFromLine(classFile, ipe.getSourceLocation())); } candidate = newElt; } return candidate; } /** * Find the java element corresponding to the handle. We know that it exists in * as a class file in a binary folder, but it may actually be * a source file in a different project */ private IJavaElement findElementInBinaryFolder(HandleInfo handleInfo, IClassFile classFile) throws JavaModelException { IJavaElement candidate = classFile; // we have a class file that is not in a jar. // can we find this as a source file in some project? IPath path = classFile.getPath(); IJavaProject otherProject = JavaCore.create(project).getJavaModel().getJavaProject(path.segment(0)); ITypeRoot typeRoot = classFile; if (otherProject.exists()) { IType type = otherProject.findType(handleInfo.sourceTypeQualName()); typeRoot = type.getTypeRoot(); if (typeRoot instanceof ICompilationUnit) { AJCompilationUnit newUnit = CompilationUnitTools .convertToAJCompilationUnit((ICompilationUnit) typeRoot); typeRoot = newUnit != null ? newUnit : typeRoot; } } if (handleInfo.isType) { switch (typeRoot.getElementType()) { case IJavaElement.CLASS_FILE: candidate = ((IClassFile) typeRoot).getType(); break; case IJavaElement.COMPILATION_UNIT: candidate = CompilationUnitTools.findType((ICompilationUnit) typeRoot, classFile.getType().getElementName(), true); break; default: // shouldn't happen break; } } else if (!handleInfo.isFile && !handleInfo.isType) { // program element will exist only if coming from aspect path IProgramElement ipe = getProgramElement(handleInfo.origAJHandle); if (ipe != IHierarchy.NO_STRUCTURE) { candidate = typeRoot.getElementAt(offsetFromLine(typeRoot, ipe.getSourceLocation())); } else { String newHandle = typeRoot.getHandleIdentifier() + handleInfo.restHandle; candidate = AspectJCore.create(newHandle); } } return candidate; } /** * extracts an IJavaElement from a compilation unit */ private IJavaElement findElementInCU(HandleInfo handleInfo, ICompilationUnit cunit) throws JavaModelException { IJavaElement candidate; // converts to AJ Unit if necessary AJCompilationUnit newUnit = CompilationUnitTools.convertToAJCompilationUnit(cunit); cunit = newUnit != null ? newUnit : cunit; IProgramElement ipe; if (!handleInfo.isFile && !handleInfo.isType) { // program element will exist only if coming from aspect path ipe = getProgramElement(handleInfo.origAJHandle); } else { ipe = IHierarchy.NO_STRUCTURE; } if (handleInfo.isFile) { // the original kind was a compilation unit, so just return that candidate = cunit; } else if (handleInfo.isType) { candidate = CompilationUnitTools.findType(cunit, handleInfo.simpleName, true); } else if (ipe != IHierarchy.NO_STRUCTURE) { candidate = cunit.getElementAt(offsetFromLine(cunit, ipe.getSourceLocation())); } else { // we have a non-type, non-file handle that is coming from // the in path. It has no program element associated with it String newHandle = newUnit.getHandleIdentifier() + handleInfo.restHandle; candidate = AspectJCore.create(newHandle); } return candidate; } private HandleInfo qualifiedNameFromBinaryHandle(String ajHandle) { int packageStart = ajHandle.indexOf(JavaElement.JEM_PACKAGEFRAGMENT); int packageEnd = ajHandle.indexOf(JavaElement.JEM_CLASSFILE, packageStart + 1); if (packageEnd < 0) { // this is a package fragment String packageName = ajHandle.substring(packageStart + 1); return new HandleInfo(ajHandle, "", packageName, "", "", false, false, false); } int typeNameEnd = ajHandle.indexOf(".class", packageEnd + 1); if (typeNameEnd < 0) { return null; } StringBuffer sb = new StringBuffer(); String packageName = ajHandle.substring(packageStart + 1, packageEnd); sb.append(packageName); if (sb.length() > 0) { sb.append("."); } String simpleName = ajHandle.substring(packageEnd + 1, typeNameEnd); sb.append(simpleName); int aspectStart = ajHandle.indexOf(AspectElement.JEM_ASPECT_TYPE, typeNameEnd); int classStart = ajHandle.indexOf(JavaElement.JEM_TYPE, typeNameEnd); int typeStart = classStart == -1 ? aspectStart : aspectStart == -1 ? classStart : Math.min(classStart, aspectStart); boolean isFile = typeStart == -1; boolean isType; if (!isFile) { isType = typeStart + simpleName.length() + 1 == ajHandle.length(); } else { isType = false; } boolean isInAspect = aspectStart >= 0; String restHandle = typeStart >= 0 ? ajHandle.substring(typeStart) : ""; return new HandleInfo(ajHandle, simpleName, packageName, sb.toString(), restHandle, isFile, isType, isInAspect); } private ITypeRoot getCUFromQualifiedName(HandleInfo handleInfo) throws JavaModelException { IJavaProject jproj = JavaCore.create(project); IType type = jproj.findType(handleInfo.qualName); // won't work if type is in a .aj file if (type != null) { return type.getTypeRoot(); } // try by looking for the package instead // will not work for inner types // but that's ok, because we are only working // top-level types IPackageFragment[] fragments = findFragment(jproj, handleInfo); if (fragments.length > 0) { for (IPackageFragment fragment : fragments) { ICompilationUnit[] cus = fragment.getCompilationUnits(); for (int j = 0; j < cus.length; j++) { IType maybeType = CompilationUnitTools.findType(cus[j], handleInfo.simpleName, true); if (maybeType != null) { return cus[j]; } } IClassFile[] cfs = fragment.getClassFiles(); for (int j = 0; j < cfs.length; j++) { IType cType = cfs[j].getType(); if (cType.getElementName().equals(handleInfo.simpleName)) { return cfs[j]; } } } } return (ICompilationUnit) ERROR_JAVA_ELEMENT; } private IPackageFragment[] findFragment(IJavaProject jproj, HandleInfo handleInfo) throws JavaModelException { IPackageFragmentRoot[] pkgRoots = jproj.getAllPackageFragmentRoots(); List<IPackageFragment> frags = new ArrayList<IPackageFragment>(); for (int i = 0; i < pkgRoots.length; i++) { IPackageFragment candidate = pkgRoots[i].getPackageFragment(handleInfo.packageName); if (candidate.exists()) { frags.add(candidate); } } return frags.toArray(new IPackageFragment[frags.size()]); } private Map<ISourceLocation, Integer> slocCache; /** * Open up the buffer to to convert from line number to offset * this is slow * We are always working in an AJCU with an aspect element * * cache the results since it is likely that we will be * calling this often for the same sloc */ private int offsetFromLine(ITypeRoot unit, ISourceLocation sloc) throws JavaModelException { if (sloc.getOffset() > 0) { return sloc.getOffset(); } if (slocCache != null && slocCache.containsKey(sloc)) { return slocCache.get(sloc).intValue(); } if (unit instanceof AJCompilationUnit) { AJCompilationUnit ajUnit = (AJCompilationUnit) unit; ajUnit.requestOriginalContentMode(); } IBuffer buf = unit.getBuffer(); if (unit instanceof AJCompilationUnit) { AJCompilationUnit ajUnit = (AJCompilationUnit) unit; ajUnit.discardOriginalContentMode(); } if (buf != null) { int requestedLine = sloc.getLine(); int currentLine = 1; int offset = 0; while (offset < buf.getLength() && currentLine < requestedLine) { if (buf.getChar(offset++) == '\n') { currentLine++; } } while (offset < buf.getLength() && Character.isWhitespace(buf.getChar(offset))) { offset++; } // cache if (slocCache == null) { slocCache = new HashMap<ISourceLocation, Integer>(); } slocCache.put(sloc, new Integer(offset)); return offset; } // no source code return 0; } public boolean hasRuntimeTest(IJavaElement je) { if (!isInitialized) { return false; } IProgramElement ipe = javaElementToProgramElement(je); List<IRelationship> relationships = relationshipMap.get(ipe); if (relationships != null) { for (IRelationship rel : relationships) { if (rel.hasRuntimeTest()) { return true; } } } return false; } /** * A hierarchy walker that trim off branches and cut its walk * short * */ class CancellableHierarchyWalker extends HierarchyWalker { private boolean cancelled = false; public IProgramElement process(IProgramElement node) { preProcess(node); if (!cancelled) { node.walk(this); } else { cancelled = false; } postProcess(node); return node; } protected void cancel() { cancelled = true; } } /** * find out what java elements are on a particular line */ public List/*IJavaElement*/<IJavaElement> getJavaElementsForLine(ICompilationUnit icu, final int line) { IProgramElement ipe = javaElementToProgramElement(icu); final List/*IProgramElement*/<IProgramElement> elementsOnLine = new LinkedList<IProgramElement>(); // walk the program element to get all ipes on the source line ipe.walk(new CancellableHierarchyWalker() { protected void preProcess(IProgramElement node) { ISourceLocation sourceLocation = node.getSourceLocation(); if (sourceLocation != null) { if (sourceLocation.getEndLine() < line) { // we don't need to explore the rest of this branch cancel(); } else if (sourceLocation.getLine() == line) { elementsOnLine.add(node); } } } }); // now convert to IJavaElements List /*IJavaElement*/<IJavaElement> javaElements = new ArrayList<IJavaElement>(elementsOnLine.size()); for (Iterator<IProgramElement> ipeIter = elementsOnLine.iterator(); ipeIter.hasNext();) { IProgramElement ipeOnLine = ipeIter.next(); javaElements.add(programElementToJavaElement(ipeOnLine)); } return javaElements; } /** * find the relationships of a particular kind for a java element */ public List<IJavaElement> getRelationshipsForElement(IJavaElement je, AJRelationshipType relType) { return getRelationshipsForElement(je, relType, false); } public List<IJavaElement> getRelationshipsForElement(IJavaElement je, AJRelationshipType relType, boolean includeChildren) { if (!isInitialized) { return Collections.emptyList(); } IProgramElement ipe = javaElementToProgramElement(je); List<IRelationship> relationships = relationshipMap.get(ipe); List<IJavaElement> relatedJavaElements = new ArrayList<IJavaElement>( relationships != null ? relationships.size() : 0); if (relationships != null) { for (IRelationship rel : relationships) { if (relType.getDisplayName().equals(rel.getName())) { for (String handle : rel.getTargets()) { IJavaElement targetJe = programElementToJavaElement(handle); if (targetJe != null && targetJe != ERROR_JAVA_ELEMENT) { relatedJavaElements.add(targetJe); } else { // ignore handles that start with * // these are handles from ITDs that are created early if (!handle.startsWith("*")) { AspectJPlugin.getDefault().getLog() .log(new Status(IStatus.WARNING, AspectJPlugin.PLUGIN_ID, "Could not create a Java element " + "with handle:\n" + handle, new RuntimeException())); } } } } } } if (includeChildren) { for (IProgramElement child : ipe.getChildren()) { relatedJavaElements .addAll(getRelationshipsForElement(programElementToJavaElement(child), relType, true)); } } return relatedJavaElements; } /** * walks the file and grabs all relationships for it * could cache this to go faster */ public Map<Integer, List<IRelationship>> getRelationshipsForFile(ICompilationUnit icu) { return getRelationshipsForFile(icu, null); } /** * walks the file and grabs all relationships for it. filter by relationship type * pass in null filter for all relationships */ public Map<Integer, List<IRelationship>> getRelationshipsForFile(ICompilationUnit icu, AJRelationshipType[] relType) { final Set<String> interesting; if (relType != null) { interesting = new HashSet<String>(); for (int i = 0; i < relType.length; i++) { interesting.add(relType[i].getDisplayName()); } } else { interesting = null; } // walk the hierarchy and get relationships for each node final Map<Integer, List<IRelationship>> allRelationshipsMap = new HashMap<Integer, List<IRelationship>>(); IProgramElement ipe = javaElementToProgramElement(icu); ipe.walk(new HierarchyWalker() { protected void preProcess(IProgramElement node) { List<IRelationship> orig = relationshipMap.get(node); if (orig == null) { return; } List<IRelationship> nodeRels = new ArrayList<IRelationship>(orig); if (interesting != null) { for (Iterator<IRelationship> relIter = nodeRels.iterator(); relIter.hasNext();) { IRelationship rel = (IRelationship) relIter.next(); if (!interesting.contains(rel.getName())) { relIter.remove(); } } } if (nodeRels.size() > 0) { List<IRelationship> allRelsForLine; Integer line = new Integer(node.getSourceLocation().getLine()); if (allRelationshipsMap.containsKey(line)) { allRelsForLine = allRelationshipsMap.get(line); } else { allRelsForLine = new LinkedList<IRelationship>(); allRelationshipsMap.put(line, allRelsForLine); } allRelsForLine.addAll(nodeRels); } } }); return allRelationshipsMap; } /** * I don't like how the 3 methods getRelationshipsForXXX return very different things. * I am trying to be efficient and not do too much processing on my end, but this leads * to having different return types. Maybe return each as an iterator. That would be nice. */ public List<IRelationship> getRelationshipsForProject(AJRelationshipType[] relType) { Set<String> interesting = new HashSet<String>(); for (int i = 0; i < relType.length; i++) { interesting.add(relType[i].getDisplayName()); } if (relationshipMap instanceof RelationshipMap) { RelationshipMap map = (RelationshipMap) relationshipMap; // flatten and filter the map List<IRelationship> allRels = new LinkedList<IRelationship>(); for (List<IRelationship> relList : map.values()) { for (IRelationship rel : relList) { if (interesting.contains(rel.getName())) { allRels.add(rel); } } } return allRels; } else { // shouldn't happen return Collections.emptyList(); } } public boolean isAdvised(IJavaElement elt) { if (!isInitialized) { return false; } IProgramElement ipe = javaElementToProgramElement(elt); if (ipe != IHierarchy.NO_STRUCTURE) { List<IRelationship> rels = relationshipMap.get(ipe); if (rels != null && rels.size() > 0) { for (IRelationship rel : rels) { if (!rel.isAffects()) { return true; } } } // check children if the children would not otherwise be in // outline view (ie- code elements) if (ipe.getKind() != IProgramElement.Kind.CLASS && ipe.getKind() != IProgramElement.Kind.ASPECT) { List<IProgramElement> ipeChildren = ipe.getChildren(); if (ipeChildren != null) { for (IProgramElement child : ipeChildren) { if (child.getKind() == IProgramElement.Kind.CODE) { rels = relationshipMap.get(child); for (IRelationship rel : rels) { if (!rel.isAffects()) { return true; } } } } } } } return false; } public Set<IType> aspectsForFile(ICompilationUnit cu) { IProgramElement ipe = javaElementToProgramElement(cu); // compiler should be able to do this for us, but functionality is // not exposed. so let's do it ourselves final Set<IType> aspects = new HashSet<IType>(); ipe.walk(new HierarchyWalker() { protected void preProcess(IProgramElement node) { if (node.getKind() == IProgramElement.Kind.ASPECT) { aspects.add((IType) programElementToJavaElement(node)); } } }); return aspects; } void dispose() { structureModel = null; relationshipMap = null; isInitialized = false; disposed = true; } public boolean isDisposed() { return disposed; } public IProject getProject() { return project; } /** * useful for testing */ public static String printHierarchy(IHierarchy h) { final StringBuffer sb = new StringBuffer(); HierarchyWalker walker = new HierarchyWalker() { int depth = 0; protected void preProcess(IProgramElement node) { sb.append(spaces(depth)); sb.append(node.getHandleIdentifier()); sb.append("\n"); depth += 2; } protected void postProcess(IProgramElement node) { depth -= 2; } String spaces(int depth) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < depth; i++) { sb.append(" "); } return sb.toString(); } }; h.getRoot().walk(walker); return sb.toString(); } /** * useful for testing */ public static String printRelationships(IRelationshipMap map) { StringBuffer sb = new StringBuffer(); RelationshipMap rmap = (RelationshipMap) map; for (Map.Entry<String, List<IRelationship>> entry : rmap.entrySet()) { String handle = entry.getKey(); sb.append(handle + " ::\n"); for (IRelationship rel : entry.getValue()) { String str = printRelationship(rel); sb.append("\t" + str + "\n"); } } return sb.toString(); } /** * useful for testing */ public static String printRelationship(IRelationship rel) { return rel.getSourceHandle() + " --" + rel.getName() + "--> " + rel.getTargets(); } IRelationshipMap getAllRelationships() { return relationshipMap; } public String getModelAsString() { if (hasModel()) { StringBuffer sb = new StringBuffer(); sb.append("Hierarchy:\n"); sb.append(printHierarchy(structureModel)); sb.append("\nRelationship map:\n"); sb.append(printRelationships(relationshipMap)); return sb.toString(); } else { return "No structure model available for " + project.getName(); } } }