blob: ce020d29db37f32cc9b82d233ecd088a5fd88f82 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.sdk.util.type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IImportDeclaration;
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.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IRegion;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.scout.commons.CollectionUtility;
import org.eclipse.scout.commons.CompareUtility;
import org.eclipse.scout.commons.holders.Holder;
import org.eclipse.scout.sdk.util.NamingUtility;
import org.eclipse.scout.sdk.util.ScoutSdkUtilCore;
import org.eclipse.scout.sdk.util.ast.AstUtility;
import org.eclipse.scout.sdk.util.internal.SdkUtilActivator;
import org.eclipse.scout.sdk.util.internal.typecache.HierarchyCache;
import org.eclipse.scout.sdk.util.internal.typecache.TypeCache;
import org.eclipse.scout.sdk.util.signature.SignatureUtility;
import org.eclipse.scout.sdk.util.typecache.ICachedTypeHierarchy;
import org.eclipse.scout.sdk.util.typecache.ICachedTypeHierarchyResult;
import org.eclipse.scout.sdk.util.typecache.ITypeCache;
import org.eclipse.scout.sdk.util.typecache.ITypeHierarchy;
import org.eclipse.scout.sdk.util.typecache.TypeHierarchyConstraints;
public class TypeUtility {
public static final String DEFAULT_SOURCE_FOLDER_NAME = "src";
public static final Pattern BEAN_METHOD_NAME = Pattern.compile("(get|set|is)([A-Z].*)");
protected TypeUtility() {
}
/**
* @see ITypeCache#getType(String)
*/
public static IType getType(String typeName) {
return TypeCache.getInstance().getType(typeName);
}
/**
* @see ITypeCache#getTypes(String)
*/
public static Set<IType> getTypes(String typeName) {
return TypeCache.getInstance().getTypes(typeName);
}
/**
* Gets all types of the given {@link ICompilationUnit} that match the given filter recursively.
*
* @param icu
* The {@link ICompilationUnit} for which all types should be returned.
* @param filter
* The filter or null if not filtering is desired.
* @return A {@link List} containing all types recursively within the given {@link ICompilationUnit}.
* @throws JavaModelException
*/
public static List<IType> getAllTypes(ICompilationUnit icu, ITypeFilter filter) throws JavaModelException {
List<IType> result = new ArrayList<IType>();
for (IType t : icu.getTypes()) {
collectTypesInType(t, result, filter);
}
return result;
}
private static void collectTypesInType(IType type, Collection<IType> result, ITypeFilter filter) throws JavaModelException {
if (filter == null || filter.accept(type)) {
result.add(type);
}
for (IType t : type.getTypes()) {
collectTypesInType(t, result, filter);
}
}
public static boolean existsType(String typeName) {
return exists(getType(typeName));
}
public static IPackageFragmentRoot getSrcPackageFragmentRoot(IJavaProject project) throws JavaModelException {
return project.findPackageFragmentRoot(new Path(IPath.SEPARATOR + project.getElementName() + IPath.SEPARATOR + DEFAULT_SOURCE_FOLDER_NAME));
}
public static IPackageFragment getPackage(IJavaElement element) {
IPackageFragment packageFragment = (IPackageFragment) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
return packageFragment;
}
public static IPackageFragment getPackage(IJavaProject project, String packageName) throws JavaModelException {
return getSrcPackageFragmentRoot(project).getPackageFragment(packageName);
}
public static Set<IType> getInnerTypes(IType type) {
return getInnerTypes(type, null);
}
public static Set<IType> getInnerTypes(IType type, ITypeFilter filter) {
return getInnerTypes(type, filter, null);
}
/**
* Returns the immediate member types declared by the given type. The results is filtered using the given filter and
* sorted using the given comparator.
*
* @param type
* The type whose immediate inner types should be returned.
* @param filter
* the filter to apply or null
* @param comparator
* the comparator to sort the result or null
* @return the immediate inner types declared in the given type.
*/
public static Set<IType> getInnerTypes(IType type, ITypeFilter filter, Comparator<IType> comparator) {
IType[] types = null;
try {
types = type.getTypes();
}
catch (JavaModelException e) {
SdkUtilActivator.logWarning("could not get inner types of '" + type.getFullyQualifiedName() + "'", e);
return CollectionUtility.hashSet();
}
Set<IType> result = null;
if (comparator == null) {
result = new HashSet<IType>(types.length);
}
else {
result = new TreeSet<IType>(comparator);
}
for (IType subtype : types) {
if (filter == null || filter.accept(subtype)) {
result.add(subtype);
}
}
return result;
}
/**
* Returns the immediate member types declared by the given type which are subtypes of the given supertype. The
* results is sorted using the given comparator.
*
* @param declaringType
* The type whose immediate inner types should be returned.
* @param supertype
* The supertype for which all returned types must be a subtype.
* @param comparator
* the comparator to sort the result.
* @return the immediate member types declared by the given type which are subtypes of the given supertype.
*/
public static Set<IType> getInnerTypesOrdered(IType declaringType, IType supertype, Comparator<IType> comparator) {
ITypeHierarchy localTypeHierarchy = getLocalTypeHierarchy(declaringType);
return getInnerTypesOrdered(declaringType, supertype, comparator, localTypeHierarchy);
}
/**
* Returns the immediate member types declared by the given type which are subtypes of the given supertype. The
* results is sorted using the given comparator.
*
* @param declaringType
* The type whose immediate inner types should be returned.
* @param supertype
* The supertype for which all returned types must be a subtype.
* @param comparator
* the comparator to sort the result.
* @param localTypeHierarchy
* The local type hierarchy to use.
* @return the immediate member types declared by the given type which are subtypes of the given supertype.
*/
public static Set<IType> getInnerTypesOrdered(IType declaringType, IType supertype, Comparator<IType> comparator, ITypeHierarchy localTypeHierarchy) {
Set<IType> allSubtypes = getInnerTypes(declaringType, TypeFilters.getSubtypeFilter(supertype, localTypeHierarchy), comparator);
return allSubtypes;
}
public static IType getTypeBySignature(String signature) {
if (signature == null) {
return null;
}
return getType(SignatureUtility.getFullyQualifiedName(signature));
}
/**
* To get a type hierarchy with the given elements as scope.
*
* @param elements
* @return
* @throws JavaModelException
*/
public static ITypeHierarchy getLocalTypeHierarchy(Collection<? extends IJavaElement> elements) {
IRegion region = JavaCore.newRegion();
if (elements != null) {
for (IJavaElement e : elements) {
if (exists(e)) {
if (e.getElementType() == IJavaElement.TYPE) {
IType t = (IType) e;
if (t.isBinary()) {
// binary types do not include their inner types because these inner types belong to their own class file
// solution: add them manually
addBinaryInnerTypesToRegionRec(t, region);
}
}
region.add(e);
}
}
}
return getLocalTypeHierarchy(region);
}
/**
* To get a type hierarchy with the given elements as scope.
*
* @param elements
* @return
* @throws JavaModelException
*/
public static ITypeHierarchy getLocalTypeHierarchy(IJavaElement... elements) {
return getLocalTypeHierarchy(CollectionUtility.hashSet(elements));
}
private static void addBinaryInnerTypesToRegionRec(IType declaringType, IRegion region) {
try {
for (IType child : declaringType.getTypes()) {
region.add(child);
addBinaryInnerTypesToRegionRec(child, region);
}
}
catch (JavaModelException e) {
SdkUtilActivator.logError("Unable to get inner types of type '" + declaringType.getFullyQualifiedName() + "'.", e);
}
}
public static ITypeHierarchy getLocalTypeHierarchy(IRegion region) {
return HierarchyCache.getInstance().getLocalTypeHierarchy(region);
}
public static ITypeHierarchy getSupertypeHierarchy(IType type) {
return HierarchyCache.getInstance().getSupertypeHierarchy(type);
}
/**
* To get a type hierarchy containing this type, all of its supertypes, and all its subtypes.<br>
*
* @param type
* the type to get the hierarchy for
* @return the cached type hierarchy or null if type does not exist or hierarchy could not be created.
*/
public static ICachedTypeHierarchy getTypeHierarchy(IType type) {
return HierarchyCache.getInstance().getTypeHierarchy(type);
}
/**
* Creates a primary type hierarchy only containing primary {@link IType}s.<br>
* Primary types are all except nested types. Or more formally: {@link IType}s for which
* <code>{@link IType#getDeclaringType()} == null</code>.
*
* @param type
* The base type of the primary type hierarchy.
* @return The primary type hierarchy. The hierarchy will only be initialized with values on first use and will be
* cached for later re-use.
* @throws if
* the given type is not a primary type.
*/
public static ICachedTypeHierarchy getPrimaryTypeHierarchy(IType type) {
return HierarchyCache.getInstance().getPrimaryTypeHierarchy(type);
}
public static boolean hasInnerType(IType declaringType, String typeName) {
IType candidate = declaringType.getType(typeName);
return candidate != null && candidate.exists();
}
public static IMethod getMethod(IType declaringType, String methodName, List<String> resolvedParameterSignatures) throws CoreException {
for (IMethod m : declaringType.getMethods()) {
if (CompareUtility.equals(m.getElementName(), methodName)) {
// signature compare
List<String> parameterSignatures = SignatureUtility.getMethodParameterSignatureResolved(m);
if (parameterSignatures.size() == resolvedParameterSignatures.size()) {
boolean signatureEquals = true;
for (int i = 0; i < parameterSignatures.size(); i++) {
if (!CompareUtility.equals(resolvedParameterSignatures.get(i), parameterSignatures.get(i))) {
signatureEquals = false;
break;
}
}
if (signatureEquals) {
return m;
}
}
}
}
return null;
}
public static IMethod findMethodInSuperclassHierarchy(IType type, IMethodFilter filter) {
return findMethodInSuperclassHierarchy(type, ScoutSdkUtilCore.getHierarchyCache().getSupertypeHierarchy(type), filter);
}
public static IMethod findMethodInSuperclassHierarchy(IType type, ITypeHierarchy hierarchy, IMethodFilter filter) {
if (exists(type)) {
IMethod method = getFirstMethod(type, filter);
if (exists(method)) {
return method;
}
else {
return findMethodInSuperclassHierarchy(hierarchy.getSuperclass(type), hierarchy, filter);
}
}
return null;
}
public static IMethod getFirstMethod(IType type, IMethodFilter filter) {
try {
for (IMethod method : type.getMethods()) {
if (filter == null || filter.accept(method)) {
return method;
}
}
}
catch (CoreException e) {
SdkUtilActivator.logWarning("could not get methods of '" + type.getFullyQualifiedName() + "' with filter '" + filter + "'.", e);
}
return null;
}
/**
* Searches and returns the first method with the given name in the given type.<br>
* If multiple methods with the same name exist (overloads), the returned method is undefined.
*
* @param type
* The type in which the method should be searched.
* @param methodName
* The name of the method.
* @return The first method found or null.
*/
public static IMethod getMethod(IType type, final String methodName) {
Set<IMethod> methods = getMethods(type, MethodFilters.getNameFilter(methodName));
if (CollectionUtility.hasElements(methods)) {
return CollectionUtility.firstElement(methods);
}
return null;
}
/**
* Gets all methods in the given type.<br>
* The methods are in no particular order.
*
* @param type
* The type to get all methods of.
* @return A {@link Set} of all methods of the given type. Never returns null.
*/
public static Set<IMethod> getMethods(IType type) {
return getMethods(type, null);
}
/**
* Gets all methods in the given type that match the given filter.<br>
* The methods are in no particular order.
*
* @param type
* The type to get all methods of.
* @param filter
* The filter.
* @return A {@link Set} of all methods of the given type matching the given filter. Never returns null.
*/
public static Set<IMethod> getMethods(IType type, IMethodFilter filter) {
return getMethods(type, filter, null);
}
/**
* Gets all methods in the given type (no methods of inner types) that match the given filter ordered by the given
* comparator.<br>
* If the given comparator is null, the order of the methods is undefined.
*
* @param type
* The type to get all methods of.
* @param filter
* The filter to use or null for no filtering.
* @param comparator
* The comparator to use or null to get the methods in undefined order.
* @return an {@link Set} of all methods of the given type matching the given filter. Never returns null.
*/
public static Set<IMethod> getMethods(IType type, IMethodFilter filter, Comparator<IMethod> comparator) {
try {
IMethod[] methods = type.getMethods();
if (filter == null && comparator == null) {
return CollectionUtility.hashSet(methods);
}
Set<IMethod> collector = null;
if (comparator == null) {
collector = new HashSet<IMethod>(methods.length);
}
else {
collector = new TreeSet<IMethod>(comparator);
}
for (IMethod method : methods) {
if (filter == null || filter.accept(method)) {
collector.add(method);
}
}
return collector;
}
catch (CoreException e) {
SdkUtilActivator.logWarning("could not get methods of '" + type.getFullyQualifiedName() + "' with filter '" + filter + "'.", e);
return CollectionUtility.hashSet();
}
}
public static ISourceRange getContentSourceRange(IMethod method) throws JavaModelException {
ASTParser parser = AstUtility.newParser();
parser.setCompilerOptions(method.getJavaProject().getOptions(true));
parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
parser.setSource(method.getSource().toCharArray());
parser.setResolveBindings(false);
parser.setBindingsRecovery(false);
ASTNode rootNode = parser.createAST(null);
final Holder<ISourceRange> rangeHolder = new Holder<ISourceRange>(ISourceRange.class);
rootNode.accept(new ASTVisitor() {
boolean a = false;
@Override
public boolean visit(MethodDeclaration node) {
a = true;
return true;
}
@Override
public boolean visit(Block node) {
if (a) {
rangeHolder.setValue(new SourceRange(node.getStartPosition(), node.getLength()));
return false;
}
return true;
}
});
ISourceRange methodRelativeRange = rangeHolder.getValue();
if (methodRelativeRange != null) {
return new SourceRange(methodRelativeRange.getOffset() + method.getSourceRange().getOffset() + 1, methodRelativeRange.getLength() - 2);
}
return null;
}
public static List<MethodParameter> getMethodParameters(IMethod method, IType contextType) throws CoreException {
String[] paramNames = method.getParameterNames();
List<String> resolvedParamSignatures = SignatureUtility.getMethodParameterSignatureResolved(method, contextType);
if (paramNames.length != resolvedParamSignatures.size()) {
throw new IllegalArgumentException("Could not resolve method parameters of '" + method.getElementName() + "' in '" + method.getDeclaringType().getFullyQualifiedName() + "'.");
}
List<MethodParameter> params = new ArrayList<MethodParameter>(paramNames.length);
for (int i = 0; i < paramNames.length; i++) {
params.add(new MethodParameter(paramNames[i], resolvedParamSignatures.get(i)));
}
return params;
}
public static List<MethodParameter> getMethodParameters(IMethod method, Map<String, String> generics) throws CoreException {
String[] paramNames = method.getParameterNames();
List<String> resolvedParamSignatures = SignatureUtility.getMethodParameterSignatureResolved(method, generics);
if (paramNames.length != resolvedParamSignatures.size()) {
throw new IllegalArgumentException("Could not resolve method parameters of '" + method.getElementName() + "' in '" + method.getDeclaringType().getFullyQualifiedName() + "'.");
}
List<MethodParameter> params = new ArrayList<MethodParameter>(paramNames.length);
for (int i = 0; i < paramNames.length; i++) {
params.add(new MethodParameter(paramNames[i], resolvedParamSignatures.get(i)));
}
return params;
}
public static IType getAncestor(IType type, ITypeFilter filter) {
IType ancestorType = type;
while (exists(ancestorType)) {
if (filter.accept(ancestorType)) {
return ancestorType;
}
ancestorType = ancestorType.getDeclaringType();
}
return null;
}
public static Set<IField> getFields(IType declaringType) {
return getFields(declaringType, null);
}
public static Set<IField> getFields(IType declaringType, IFieldFilter filter) {
return getFields(declaringType, filter, null);
}
public static Set<IField> getFields(IType declaringType, IFieldFilter filter, Comparator<IField> comparator) {
try {
IField[] fields = declaringType.getFields();
if (filter == null && comparator == null) {
return CollectionUtility.hashSet(fields);
}
Set<IField> collector = null;
if (comparator == null) {
collector = new HashSet<IField>(fields.length);
}
else {
collector = new TreeSet<IField>(comparator);
}
for (IField field : fields) {
if (filter == null || filter.accept(field)) {
collector.add(field);
}
}
return collector;
}
catch (JavaModelException e) {
SdkUtilActivator.logWarning("could not get fields of '" + declaringType.getElementName() + "'.", e);
return CollectionUtility.hashSet();
}
}
public static IField getFirstField(IType type, IFieldFilter filter) {
try {
for (IField method : type.getFields()) {
if (filter == null || filter.accept(method)) {
return method;
}
}
}
catch (JavaModelException e) {
SdkUtilActivator.logWarning("could not get methods of '" + type.getFullyQualifiedName() + "'.", e);
}
return null;
}
public static Object getFieldConstant(IField field) throws JavaModelException {
Object val = field.getConstant();
if (val instanceof String) {
String ret = (String) val;
ret = ret.trim();
if (ret.length() > 2 && ret.charAt(0) == '"' && ret.charAt(ret.length() - 1) == '"') {
// when scout runtime sources are present in the workspace -> field value is returned with double quotes
ret = ret.substring(1, ret.length() - 1);
}
return ret;
}
else {
return val;
}
}
public static IMethod getOverwrittenMethod(IMethod method, ITypeHierarchy supertypeHierarchy) {
IType supertype = supertypeHierarchy.getSuperclass(method.getDeclaringType());
IMethodFilter overrideFilter = MethodFilters.getSuperMethodFilter(method);
while (supertype != null) {
IMethod superMethod = getFirstMethod(supertype, overrideFilter);
if (superMethod != null) {
return superMethod;
}
supertype = supertypeHierarchy.getSuperclass(supertype);
}
return null;
}
/**
* <code>
* class A{<br>
* &nbsp;&nbsp;class B{<br>
* &nbsp;&nbsp;&nbsp;&nbsp;class C{<br>
* &nbsp;&nbsp;&nbsp;&nbsp;}<br>
* &nbsp;&nbsp;&nbsp;&nbsp;class D{<br>
* &nbsp;&nbsp;&nbsp;&nbsp;}<br>
* &nbsp;&nbsp;}<br>
* }<br>
* // A.getTopLevelType() returns A<br>
* // D.getTopLevelType() returns A
* </code>
*
* @return the primary type of the compilation unit this type is declared in.
*/
public static IType getToplevelType(IJavaElement e) {
if (e == null) {
return null;
}
IType surroundingType = (IType) e.getAncestor(IJavaElement.TYPE);
if (!exists(surroundingType)) {
return null; // element is not within a type.
}
IType result = null;
IType tmp = surroundingType;
while (exists(tmp)) {
result = tmp;
tmp = tmp.getDeclaringType();
}
return result;
}
public static boolean exists(IJavaElement element) {
return element != null && element.exists();
}
/**
* Searches for an {@link IType} with a specific name within the given type recursively checking all inner types. The
* given {@link IType} itself is checked as well.
*
* @param type
* The {@link IType} to start searching. All nested inner {@link IType}s are visited recursively.
* @param innerTypeName
* The simple name (case sensitive) to search for.
* @return The first {@link IType} found in the nested {@link IType} tree below the given start type that has the
* given simple name or <code>null</code> if nothing could be found.
* @throws JavaModelException
*/
public static IType findInnerType(IType type, String innerTypeName) throws JavaModelException {
if (!TypeUtility.exists(type)) {
return null;
}
else if (CompareUtility.equals(type.getElementName(), innerTypeName)) {
return type;
}
else {
for (IType innerType : type.getTypes()) {
IType found = findInnerType(innerType, innerTypeName);
if (found != null) {
return found;
}
}
}
return null;
}
/**
* Tries to find the fully qualified class name of the given simple name based on the context of the given type.
*
* @param declaringType
* The context type for which the simple name should be resolved.
* @param typeName
* The type name to resolve.
* @param searchOnClassPath
* specifies if the classpath of the {@link IJavaProject} of the declaringType should be searched as well.
* @return The fully qualified name of the given simple name for the given context or null.
* @throws JavaModelException
*/
public static String getReferencedTypeFqn(IType declaringType, String typeName, boolean searchOnClassPath) throws JavaModelException {
// 1. check the imports (performance improvement)
ICompilationUnit compilationUnit = declaringType.getCompilationUnit();
if (compilationUnit != null) {
String searchString = '.' + typeName;
IImportDeclaration[] imports = compilationUnit.getImports();
for (IImportDeclaration imp : imports) {
if (imp.getElementName().endsWith(searchString)) {
return imp.getElementName();
}
}
}
// 2. try to resolve
String[][] resolvedTypeName = declaringType.resolveType(typeName);
if (resolvedTypeName != null && resolvedTypeName.length == 1) {
String pck = resolvedTypeName[0][0];
StringBuilder fqName = new StringBuilder();
if (pck != null && pck.length() > 0) {
fqName.append(pck);
fqName.append('.');
}
fqName.append(resolvedTypeName[0][1]); // class simple name
return fqName.toString();
}
if (searchOnClassPath) {
// 3. try to find a matching type on the classpath
// some types may not be part of the compilation unit (e.g. declaringType is binary, then there is no compilation unit) and cannot be resolved in the class file.
// this can happen when e.g. only a reference to a static final field is in the class file and there is no other reference to the class.
// then the compiler removes this reference and directly puts the value of the field in the class file even though the reference remains in the source of the class.
// the originating class can then not be found anymore. This happens e.g. with the AbstractIcons reference in AbstractSmartField.
// to solve this, try to find a unique type in the workspace with the simple name. If there is only one match, we are happy.
IJavaProject javaProject = declaringType.getJavaProject();
if (exists(javaProject)) {
Set<IType> candidates = getTypes(typeName);
// remove all that are not on the requested classpath
Iterator<IType> iterator = candidates.iterator();
while (iterator.hasNext()) {
IType cur = iterator.next();
if (!isOnClasspath(cur, javaProject)) {
iterator.remove();
}
}
if (candidates.size() == 1) {
return CollectionUtility.firstElement(candidates).getFullyQualifiedName();
}
}
}
return null;
}
/**
* @return The {@link IType} of the fully qualified name found or null.
* @see TypeUtility#getReferencedTypeFqn(IType, String, boolean)
*/
public static IType getReferencedType(IType declaringType, String typeName, boolean searchOnClassPath) throws JavaModelException {
String referencedTypeFqn = getReferencedTypeFqn(declaringType, typeName, searchOnClassPath);
if (referencedTypeFqn != null) {
return getType(referencedTypeFqn);
}
return null;
}
public static boolean isGenericType(IType type) {
return getTypeParameters(type).size() > 0;
}
/**
* Gets the type that is more specific. This means:<br>
* If a is a subtype of b or b is null: a is returned.<br>
* If b is a subtype of a or a is null: b is returned.<br>
* If both are null or they have no common supertype: null is returned.
*
* @param a
* The first {@link IType}
* @param b
* The second {@link IType}
* @return The more specific type or null according to the rule described above.
*/
public static IType getMoreSpecificType(IType a, IType b) {
if (!exists(a) && !exists(b)) {
return null;
}
if (!exists(a)) {
return b;
}
if (!exists(b)) {
return a;
}
if (ScoutSdkUtilCore.getHierarchyCache().getSupertypeHierarchy(a).contains(b)) {
return a;
}
else if (ScoutSdkUtilCore.getHierarchyCache().getSupertypeHierarchy(b).contains(a)) {
return b;
}
else {
return null;
}
}
public static List<ITypeParameter> getTypeParameters(IType type) {
if (TypeUtility.exists(type)) {
try {
return CollectionUtility.arrayList(type.getTypeParameters());
}
catch (JavaModelException e) {
SdkUtilActivator.logWarning("could not get generic information of type: " + type.getFullyQualifiedName(), e);
}
}
return CollectionUtility.arrayList();
}
public static ICachedTypeHierarchyResult getAbstractTypesOnClasspathHierarchy(IType hierarchyBaseType, IJavaProject project) {
TypeHierarchyConstraints constraints = new TypeHierarchyConstraints(hierarchyBaseType, project);
constraints.modifiersNotSet(Flags.AccInterface, Flags.AccDeprecated).modifiersSet(Flags.AccAbstract, Flags.AccPublic);
ICachedTypeHierarchyResult h = HierarchyCache.getInstance().getProjectContextTypeHierarchy(constraints);
return h;
}
public static Set<IType> getAbstractTypesOnClasspath(IType hierarchyBaseType, IJavaProject project, ITypeFilter filter) {
ICachedTypeHierarchyResult h = getAbstractTypesOnClasspathHierarchy(hierarchyBaseType, project);
return h.getAllTypes(filter, TypeComparators.getTypeNameComparator());
}
public static Set<IType> getClassesOnClasspath(IType supertype, IJavaProject project, ITypeFilter filter) {
TypeHierarchyConstraints constraints = new TypeHierarchyConstraints(supertype, project);
constraints.modifiersNotSet(Flags.AccAbstract, Flags.AccDeprecated, Flags.AccInterface);
ICachedTypeHierarchyResult h = HierarchyCache.getInstance().getProjectContextTypeHierarchy(constraints);
return h.getAllTypes(filter, TypeComparators.getTypeNameComparator());
}
public static Set<IType> getInterfacesOnClasspath(IType supertype, IJavaProject project, ITypeFilter filter) {
TypeHierarchyConstraints constraints = new TypeHierarchyConstraints(supertype, project);
constraints.modifiersSet(Flags.AccInterface);
ICachedTypeHierarchyResult h = HierarchyCache.getInstance().getProjectContextTypeHierarchy(constraints);
return h.getAllTypes(filter, TypeComparators.getTypeNameComparator());
}
/**
* checks whether element is on the classpath of the given project
*
* @param element
* the element to search
* @param project
* the project classpath to search in
* @return true if element was found in the classpath of project
*/
public static boolean isOnClasspath(IJavaElement element, IJavaProject project) {
if (!exists(element)) {
return false;
}
if (!exists(project)) {
return false;
}
if (element instanceof IMember) {
IMember member = (IMember) element;
if (member.isBinary()) {
return project.isOnClasspath(member);
}
}
IJavaProject elemenProject = element.getJavaProject();
if (elemenProject != null) {
if (project.equals(elemenProject)) {
return true;
}
else {
return project.isOnClasspath(elemenProject);
}
}
return project.isOnClasspath(element);
}
/**
* Collects all property beans declared directly in the given type by search methods with the following naming
* convention:
*
* <pre>
* public <em>&lt;PropertyType&gt;</em> get<em>&lt;PropertyName&gt;</em>();
* public void set<em>&lt;PropertyName&gt;</em>(<em>&lt;PropertyType&gt;</em> a);
* </pre>
*
* If <code>PropertyType</code> is a boolean property, the following getter is expected
*
* <pre>
* public boolean is<em>&lt;PropertyName&gt;</em>();
* </pre>
* <p>
* This implementation tries to determine the field by using the JDT code style settings stored in the Eclipse
* preferences. Prefixes and suffixes used for fields must be declared. The default prefix Scout uses for fields (
* <code>m_</code>) is added by default.
*
* @param type
* the type within properties are searched
* @param propertyFilter
* optional property bean filter used to filter the result
* @param comparator
* optional property bean comparator used to sort the result
* @return Returns an array of property bean descriptions. The array is empty if the given class does not contain any
* bean properties.
* @see <a href="http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html">JavaBeans Spec</a>
*/
public static Set<? extends IPropertyBean> getPropertyBeans(IType type, IPropertyBeanFilter propertyFilter, Comparator<IPropertyBean> comparator) {
IMethodFilter filter = MethodFilters.getMultiMethodFilter(MethodFilters.getFlagsFilter(Flags.AccPublic), MethodFilters.getNameRegexFilter(BEAN_METHOD_NAME));
Set<IMethod> methods = getMethods(type, filter);
Map<String, PropertyBean> beans = new HashMap<String, PropertyBean>(methods.size());
for (IMethod m : methods) {
Matcher matcher = BEAN_METHOD_NAME.matcher(m.getElementName());
if (matcher.matches()) {
try {
String kind = matcher.group(1);
String name = matcher.group(2);
//
String[] parameterTypes = m.getParameterTypes();
String returnType = m.getReturnType();
if ("get".equals(kind) && parameterTypes.length == 0 && !returnType.equals(Signature.SIG_VOID)) {
PropertyBean desc = beans.get(name);
if (desc == null) {
desc = new PropertyBean(type, name);
beans.put(name, desc);
}
if (desc.getReadMethod() == null) {
desc.setReadMethod(m);
}
}
else if ("is".equals(kind) && parameterTypes.length == 0 && returnType.equals(Signature.SIG_BOOLEAN)) {
PropertyBean desc = beans.get(name);
if (desc == null) {
desc = new PropertyBean(type, name);
beans.put(name, desc);
}
if (desc.getReadMethod() == null) {
desc.setReadMethod(m);
}
}
else if ("set".equals(kind) && parameterTypes.length == 1 && returnType.equals(Signature.SIG_VOID)) {
PropertyBean desc = beans.get(name);
if (desc == null) {
desc = new PropertyBean(type, name);
beans.put(name, desc);
}
if (desc.getWriteMethod() == null) {
desc.setWriteMethod(m);
}
}
}
catch (JavaModelException e) {
SdkUtilActivator.logError("Error while collectiong property beans of type [" + type + "]", e);
}
}
}
// filter
Set<PropertyBean> filteredBeans = null;
if (comparator == null) {
filteredBeans = new HashSet<PropertyBean>(beans.size());
}
else {
filteredBeans = new TreeSet<PropertyBean>(comparator);
}
for (PropertyBean bean : beans.values()) {
if (propertyFilter == null || propertyFilter.accept(bean)) {
filteredBeans.add(bean);
}
}
// fields
Set<IField> fieldCandidates = getFields(type, FieldFilters.getPrivateNotStaticNotFinalNotAbstract(), null);
HashMap<String, IField> fields = new HashMap<String, IField>(fieldCandidates.size());
for (IField field : fieldCandidates) {
fields.put(field.getElementName(), field);
}
// get field pre- and suffixes
@SuppressWarnings("restriction")
org.eclipse.jdt.internal.codeassist.impl.AssistOptions assistOptions = new org.eclipse.jdt.internal.codeassist.impl.AssistOptions(type.getJavaProject().getOptions(true));
@SuppressWarnings("restriction")
Set<String> fieldPrefixes = toStringSet(assistOptions.fieldPrefixes, "m_", "");
@SuppressWarnings("restriction")
Set<String> fieldSuffixes = toStringSet(assistOptions.fieldSuffixes, "");
for (PropertyBean bean : filteredBeans) {
IField field = findFieldForPropertyBean(bean.getBeanName(), fields, fieldPrefixes, fieldSuffixes);
if (field != null) {
bean.setField(field);
}
else {
SdkUtilActivator.logWarning("Unable to find field for property bean [" + bean + "]");
}
}
return filteredBeans;
}
/**
* To find out if the given child element is an ancestor of the given parent element. If parent and child is the same
* element true is returned.
*
* @param parent
* @param child
* @return
*/
public static boolean isAncestor(IJavaElement parent, IJavaElement child) {
if (parent == null || child == null) {
return false;
}
if (parent.equals(child)) {
return true;
}
else if (child.getParent() != null && child.getParent().getElementType() >= parent.getElementType()) {
return isAncestor(parent, child.getParent().getAncestor(parent.getElementType()));
}
else {
return false;
}
}
public static int getIndent(IJavaElement element) {
int indent = 0;
IJavaElement visitedElement = element;
while (visitedElement.getElementType() != IJavaElement.COMPILATION_UNIT) {
indent++;
visitedElement = element.getParent();
}
return indent;
}
/**
* Tries to find a method in the given type and all supertypes and super interfaces.<br>
* If multiple methods with the same name exist in a type (overloads), the first is returned as they appear in the
* source or class file.
*
* @param methodName
* the name of the method
* @param type
* The start type in which (together with its supertypes and super interfaces) the given method should be
* searched.
* @param supertypeHierarchy
* The supertype hierarchy of the given type.
* @return The first method found in the type itself, its supertypes or super interfaces (searched in this order). If
* multiple methods with the same name exist in a type (overloads), the first is returned as they appear in
* the source or class file.
*/
public static IMethod findMethodInSupertypeHierarchy(String methodName, IType type, ITypeHierarchy supertypeHierarchy) {
return findMethodInSupertypeHierarchy(type, supertypeHierarchy, MethodFilters.getNameFilter(methodName));
}
public static IMethod findMethodInSupertypeHierarchy(IType type, ITypeHierarchy supertypeHierarchy, IMethodFilter filter) {
Set<IMethod> methods = getMethods(type, filter);
IMethod method = null;
IMethod first = CollectionUtility.firstElement(methods);
if (methods.size() == 1) {
return first;
}
else if (methods.size() > 1) {
StringBuilder sb = new StringBuilder(" [\n");
for (IMethod m : methods) {
if (m != first) {
sb.append(",\n");
}
sb.append("\t\t").append(m.toString());
}
sb.append("\n\t]");
SdkUtilActivator.logWarning("found more than one method in hierarchy" + sb.toString());
return first;
}
else {
// supertypes
IType supertype = supertypeHierarchy.getSuperclass(type);
if (exists(supertype) && !supertype.getElementName().equals(Object.class.getName())) {
method = findMethodInSupertypeHierarchy(supertype, supertypeHierarchy, filter);
}
if (exists(method)) {
return method;
}
// interfaces
for (IType intType : supertypeHierarchy.getSuperInterfaces(type)) {
if (exists(intType) && !intType.getElementName().equals(Object.class.getName())) {
method = findMethodInSupertypeHierarchy(intType, supertypeHierarchy, filter);
}
if (exists(method)) {
return method;
}
}
}
return null;
}
/**
* Tries to determine the field the given property is based on. All combinations of the given pre- and suffixes are
* used to find the field. If none of them matches with a method <code>null</code> is returned.
*
* @param beanName
* @param fields
* @param fieldPrefixes
* @param fieldSuffixes
* @return Returns the field the property is based on or <code>null</code>.
*/
private static IField findFieldForPropertyBean(String beanName, HashMap<String, IField> fields, Set<String> fieldPrefixes, Set<String> fieldSuffixes) {
for (String prefix : fieldPrefixes) {
for (String suffix : fieldSuffixes) {
IField field = fields.get(prefix + NamingUtility.ensureStartWithLowerCase(beanName) + suffix);
if (TypeUtility.exists(field)) {
return field;
}
}
}
return null;
}
/**
* Converts the given two-dimensional array of chars into an array of strings and adds the given additional values.
*
* @param arrayOfChars
* @param additionalValues
* @return
*/
private static Set<String> toStringSet(char[][] arrayOfChars, String... additionalValues) {
HashSet<String> result = new HashSet<String>();
if (additionalValues != null) {
for (String s : additionalValues) {
result.add(s);
}
}
if (arrayOfChars != null) {
for (char[] cs : arrayOfChars) {
if (cs != null && cs.length > 0) {
result.add(String.valueOf(cs));
}
}
}
return result;
}
}