/******************************************************************************* * Copyright (c) 2010 Thiago Tonelli Bartolomei. * 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: * Thiago Tonelli Bartolomei - initial API and implementation ******************************************************************************/ package ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.BindingKey; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.search.FieldReferenceMatch; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.MethodReferenceMatch; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.SuperHierarchyScope; import ca.uwaterloo.gsd.fsml.stats.Stats; /** * Utility methods for manipulating the Java Model. * * @author Thiago Tonelli Bartolomei <ttonelli@gsd.uwaterloo.ca> */ public class JavaModelUtils { /** * Gets the reference to the default constructor of this type, if it declares one. * * @param type the type to search for the default constructor * @return the default constructor, or null if not found */ public static IMethod getDefaultConstructor(IType type) { if (type == null) return null; try { for (IMethod method : type.getMethods()) { if (method.isConstructor() && method.getParameterTypes().length == 0) { return method; } } } catch (JavaModelException e) { e.printStackTrace(); } return null; } /** * Checks if this type has an implicit default constructor, i.e., it does not * declare the default constructor nor another constructor with parameters * * @param type * @return */ public static boolean hasImplicitDefaultConstructor(IType type) { if (type == null) return false; boolean hasConcreteDefault = false; boolean hasOtherConcrete = false; try { for (IMethod method : type.getMethods()) { if (method.isConstructor()) { if (method.getParameterTypes().length == 0) { hasConcreteDefault = true; } else { hasOtherConcrete = true; } } } } catch (JavaModelException e) { e.printStackTrace(); } return ! hasConcreteDefault && ! hasOtherConcrete; } /** * Gets the first occurrence of a concrete default constructor in the hierarchy of this type. * * This method will search for a default constructor somewhere, eventually getting to Object. * * @param hierarchy * @param type * @return the concrete default constructor that will be executed if an object of type is created * with a default constructor. Null if there is no such constructor. */ public static IMethod getConcreteDefaultConstructor(ITypeHierarchy hierarchy, IType type) { if (type == null) return null; // check if the type itself declares the constructor IMethod constructor = getDefaultConstructor(type); if (constructor != null) { return constructor; } // if it does not have an implicit, there is no way to get a default if (! hasImplicitDefaultConstructor(type)) return null; // get it from the super class return getConcreteDefaultConstructor(hierarchy, hierarchy.getSuperclass(type)); } /** * Returns the type root of this element (a compilation unit or a class file), or null * if cannot get it. * * This method should be used instead of calling IJavaElement.getTypeRoot() because we * get the right class file for nested types. * * @param e * @return */ public static ITypeRoot getTypeRoot(IJavaElement e) { if (e == null) return null; try { // the element we will really search for IJavaElement target = getTopLevelType(e); // could not find a type, check the element directly if (target == null) { target = e; } IJavaElement root = target.getAncestor(IJavaElement.COMPILATION_UNIT); if (root != null) { return (ITypeRoot) root; } root = target.getAncestor(IJavaElement.CLASS_FILE); if (root != null) { return (ITypeRoot) root; } } catch (JavaModelException ex) { ex.printStackTrace(); } return null; } /** * Gets the top level type declaring this element. * * The top level type is a type that is not nested in other types. * * @param e * @return */ public static IType getTopLevelType(IJavaElement e) throws JavaModelException { if (e == null) return null; IType type = (IType) e.getAncestor(IJavaElement.TYPE); IType declaringType = type; if (type != null) { while(true) { IType auxDeclaringType = getDeclaringType(declaringType); if (auxDeclaringType == null) break; declaringType = auxDeclaringType; } } return declaringType; } /** * Finds the type on the top of the hierarchy * * @param h * @return */ public static IType getTopType(ITypeHierarchy h) { IType top = h.getType(); if (top == null) return null; while(h.getSuperclass(top) != null) top = h.getSuperclass(top); return top; } /** * Gets the declaring type of this type. * * The declaring type is the outer type. If this is a top level type, this method will return null. * Otherwise, it will return the IType representing the type where this type was declared. * * This implementation should be used instead of IType.getDeclaringType() because it fixes Bug 8716 * that prevented getting the declaring type of anonymous binary nested types. If the type is * not anonymous binary, this implementation just calls the regular IType.getDeclaringType method. * Otherwise, we parse the type's name and try to resolve the outer type using the package fragment. * * @param type * @return * @throws JavaModelException */ public static IType getDeclaringType(IType type) throws JavaModelException { if (type == null) return null; // do our magic for anonymous binary types if (type.isAnonymous() && type.isBinary()) { // retrieve the top level type String topLevelName = type.getTypeQualifiedName(); topLevelName = topLevelName.substring(0, topLevelName.lastIndexOf('$')) + ".class"; IType topLevelType = type.getPackageFragment().getClassFile(topLevelName).getType(); // now get the type exactly in that place of the source (could be nested type) IJavaElement spot = topLevelType.getClassFile().getElementAt(type.getSourceRange().getOffset()); if (spot == null) { IType declaringType = type.getDeclaringType(); if (declaringType != null) return declaringType; IJavaElement parent = type.getParent(); if (parent instanceof IType) return (IType) parent; String key = type.getKey(); BindingKey bindingKey = new BindingKey(key); String signature = bindingKey.toSignature(); signature = signature.substring(0, signature.indexOf('$')) + ";"; String fqName = Signature.toString(signature); return type.getJavaProject().findType(fqName); } return (IType) spot.getAncestor(IJavaElement.TYPE); } return type.getDeclaringType(); } public static IType getDeclaringType(IJavaElement e) throws JavaModelException { if (e == null) return null; if (e instanceof IType) { return JavaModelUtils.getDeclaringType((IType) e); } return (IType) e.getAncestor(IJavaElement.TYPE); } public static Collection<SearchMatch> getMethodCallsInProject(IJavaProject project, IMethod method, IProgressMonitor progressMonitor) throws JavaModelException { return getMethodCalls(project, null, null, null, method, Scope.PROJECT, progressMonitor); } public static Collection<SearchMatch> getMethodCalls(IJavaProject project, IIncrementalTypeHierarchy hierarchy, IMember member, IType destType, IMethod method, Scope searchScope, IProgressMonitor progressMonitor) throws JavaModelException { // stats final String searchSuccessful = "method reference found to '" + method.getElementName() + "'"; final String searchUnsuccessful = "callsMethod: Unable to perform method reference search"; SearchPattern pattern = SearchPattern.createPattern(method, IJavaSearchConstants.REFERENCES); final ArrayList<SearchMatch> found = new ArrayList<SearchMatch>(); IJavaSearchScope scope = null; switch (searchScope) { case NO_HIERARCHY: scope = SearchEngine.createJavaSearchScope(new IJavaElement[] {member} ); break; case SOURCE_HIERARCHY: if (member instanceof IType) // Now using our own scope that gets only super types (and the type itself) //scope = SearchEngine.createHierarchyScope((IType) member); scope = new SuperHierarchyScope((IType) member, hierarchy); break; case TARGET_HIERARCHY: scope = SearchEngine.createHierarchyScope(destType); break; case PROJECT: scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { project } ); break; default: return found; } // Get the search requestor SearchRequestor requestor = new SearchRequestor() { @Override public void acceptSearchMatch(SearchMatch match) throws CoreException { if (match instanceof MethodReferenceMatch) found.add(match); } }; // Search SearchEngine searchEngine = new SearchEngine(); try { searchEngine.search(pattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, scope, requestor, progressMonitor); } catch (CoreException e) { // stats Stats.INSTANCE.logMessage(searchUnsuccessful); e.printStackTrace(); } catch (OutOfMemoryError e) { e.printStackTrace(); } for (Iterator<SearchMatch> i = found.iterator(); i.hasNext(); ) { SearchMatch match = i.next(); int accuracy = match.getAccuracy(); Stats.INSTANCE.logMessage("callsMethod: " +(accuracy == SearchMatch.A_ACCURATE ? "Accurate " : "Inaccurate ") + searchSuccessful); /*if (accuracy == SearchMatch.A_INACCURATE) Stats.INSTANCE.log("Inaccurate method call match: " + match);*/ // filter out inaccurate matches if (accuracy == SearchMatch.A_INACCURATE) i.remove(); } // stats return found; } public static Collection<FieldReferenceMatch> writesToField(IJavaProject project, IField field, IProgressMonitor progressMonitor) throws JavaModelException { // stats final String searchSuccessful = "field write access found to '" + field.getElementName() +"'"; final String searchUnsuccessful = "callsMethod: Unable to perform field write access search"; SearchPattern pattern = SearchPattern.createPattern(field, IJavaSearchConstants.WRITE_ACCESSES); // get all superclasses of type IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { project }); final ArrayList<FieldReferenceMatch> found = new ArrayList<FieldReferenceMatch>(); // Get the search requestor SearchRequestor requestor = new SearchRequestor() { @Override public void acceptSearchMatch(SearchMatch match) throws CoreException { if (match instanceof FieldReferenceMatch) found.add((FieldReferenceMatch) match); } }; // Search SearchEngine searchEngine = new SearchEngine(); try { searchEngine.search(pattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, scope, requestor, progressMonitor); } catch (CoreException e) { // stats Stats.INSTANCE.logMessage(searchUnsuccessful); e.printStackTrace(); } for (SearchMatch match : found) { int accuracy = match.getAccuracy(); Stats.INSTANCE.logMessage("writesToField: " +(accuracy == SearchMatch.A_ACCURATE ? "Accurate " : "Inaccurate ") + searchSuccessful); } return found; } public static List<IType> getSubtypesOfType(IType iType, IJavaProject javaProject, boolean localOnly, boolean concreteOnly, boolean abstractOnly, IProgressMonitor progressMonitor) throws JavaModelException { ITypeHierarchy iTypeHierarchy = iType.newTypeHierarchy(javaProject, progressMonitor); List<IType> subtypes = new ArrayList<IType>(); String queriesProject = javaProject.getHandleIdentifier(); IType[] alltypes = iTypeHierarchy.getAllSubtypes(iType); for (IType aux : alltypes) { if (localOnly) { if (aux.isBinary()) continue; String elementsProject = aux.getJavaProject().getHandleIdentifier(); if (!elementsProject.equals(queriesProject)) continue; } if (concreteOnly && abstractOnly) { Stats.INSTANCE.logBug("getSubtypesOfType(): concreteOnly && abstractOnly"); continue; } if (concreteOnly) { if (aux.isInterface() || Flags.isAbstract(aux.getFlags())) continue; } if (abstractOnly) { if (!aux.isInterface() && !Flags.isAbstract(aux.getFlags())) continue; } if (!subtypes.contains(aux)) subtypes.add(aux); } return subtypes; } /** * Locates a method in the given IType. * The returned method is guaranteed to exist. * @param project TODO * @param lookupName * @param lookupSignature * @param type * @param includeLocal * @param includeInherited * @param progressMonitor * @return * @throws JavaModelException */ public static IMethod findMethod(IJavaProject project, String lookupName, String lookupSignature, IType type, boolean includeLocal, boolean includeInherited, IProgressMonitor progressMonitor) throws JavaModelException { String[] lookupParameterNames = Signature.getParameterTypes(lookupSignature); String[] lookupParameterSimpleNames = new String[lookupParameterNames.length]; for (int i = 0; i < lookupParameterNames.length; i++) { String erasure = Signature.getTypeErasure(lookupParameterNames[i]); lookupParameterSimpleNames[i] = Signature.getSimpleName(Signature.toString(erasure)); } if (includeLocal) { IMethod result = type.getMethod(lookupName, lookupParameterNames); if (result != null && result.exists()) return result; for (IMethod iMethod : type.getMethods()) if (iMethod.getElementName().equals(lookupName)) if (iMethod.isSimilar(result)) return iMethod; } if (includeInherited) { // look in supertype IType superIType = (IType) project.findType(type.getSuperclassName()); if (superIType != null && superIType.exists()) return findMethod(project, lookupName, lookupSignature, superIType, true, includeInherited, progressMonitor); } return null; } /** * A Range to compare for an unknown range */ public static final ISourceRange UNKNOWN_RANGE = new ISourceRange() { public int getLength() { return 0; } public int getOffset() { return -1; } }; /** * Checks if this source reference has a valid range. * * This is useful to check is a certain method that is in the Java Model really exists in * the source. This is the case, for example, for implicit constructors. If the constructor * source range is unknown, this means we cannot parse it (it has no source!) * * @param ref * @return */ public static boolean hasSourceRange(ISourceReference ref) { try { ISourceRange refRange = ref.getSourceRange(); return refRange != null && ! refRange.equals(JavaModelUtils.UNKNOWN_RANGE); } catch (JavaModelException e) { e.printStackTrace(); return false; } } /** * Formats the method to be printed * * @param method * @return */ public static StringBuffer formatMethod(IMethod method) { StringBuffer buf = new StringBuffer(); buf.append(method.getDeclaringType().getElementName()); buf.append('.'); buf.append(method.getElementName()); buf.append("(...)"); return buf; } }