/******************************************************************************* * Copyright (c) 2007, 2015 Spring IDE Developers * 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: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.core.java; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.springframework.ide.eclipse.core.SpringCore; import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyEngine; /** * Helper methods for examining a Java {@link IType}. * @author Christian Dupuis * @author Torsten Juergeleit * @author Pierre-Antoine Gregoire * @author Martin Lippert */ public final class Introspector { public enum Public { YES, NO, DONT_CARE } public enum Static { YES, NO, DONT_CARE } /** * Utility method that handles property names. * <p> * See {@link java.beans.Introspector#decapitalize(String)} for the reverse operation done in the Java SDK. * @see java.beans.Introspector#decapitalize(String) */ private static String capitalize(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(0))) { return name; } char chars[] = name.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } /** * Returns <code>true</code> if the given Java type extends or is the specified class. * @param type the Java type to be examined * @param className the full qualified name of the class we are looking for */ public static boolean doesExtend(IType type, String className) { if (System.getProperty(TypeHierarchyEngine.ENABLE_PROPERTY, "true").equals("true")) { return SpringCore.getTypeHierarchyEngine().doesExtend(type, className); } else { return hasSuperType(type, className, false); } } /** * Returns <code>true</code> if the given Java type implements the specified interface. * @param type the Java type to be examined * @param interfaceName the full qualified name of the interface we are looking for */ public static boolean doesImplement(IType type, String interfaceName) { if (System.getProperty(TypeHierarchyEngine.ENABLE_PROPERTY, "true").equals("true")) { return SpringCore.getTypeHierarchyEngine().doesImplement(type, interfaceName); } else { return hasSuperType(type, interfaceName, true); } } /** * Returns a list of all constructors from given type. */ public static Set<IMethod> findAllConstructors(IType type) throws JavaModelException { Map<String, IMethod> allConstructors = new HashMap<String, IMethod>(); while (type != null) { for (IMethod method : getMethods(type)) { String key = method.getElementName() + method.getSignature(); if (!allConstructors.containsKey(key) && method.isConstructor()) { allConstructors.put(key, method); } } type = getSuperType(type); } return new HashSet<IMethod>(allConstructors.values()); } /** * Finds all {@link IMethod}s in the given {@link IType}'s hierarchy that match the given filter. * <p> * Note: calling this method is equivalent to calling {@link #findAllMethods(IType, String, IMethodFilter)}. * @since 2.0.2 */ public static Set<IMethod> findAllMethods(IType type, IMethodFilter filter) { return findAllMethods(type, "", filter); } /** * Finds all {@link IMethod}s in the given {@link IType}'s hierarchy that match the given filter, applying the * prefix. * @since 2.0.2 */ public static Set<IMethod> findAllMethods(IType type, String prefix, IMethodFilter filter) { Set<IMethod> methods = new LinkedHashSet<IMethod>(); try { if (type != null && type.isInterface()) { Set<IType> types = new HashSet<IType>(); types.add(type); for (IMethod method : getMethods(type)) { if (!method.isConstructor() && filter.matches(method, prefix)) { methods.add(method); } } for (IType interfaceType : getAllImplementedInterfaces(type)) { methods.addAll(findAllMethods(interfaceType, prefix, filter)); } } while (type != null) { for (IMethod method : getMethods(type)) { if (!method.isConstructor() && filter.matches(method, prefix)) { methods.add(method); } } type = getSuperType(type); } } catch (JavaModelException e) { // don't do anything here } return methods; } public static Set<IAnnotation> getAllAnnotations(IType type) throws JavaModelException { Set<IAnnotation> annotations = new LinkedHashSet<IAnnotation>(); while (type != null && !type.isInterface()) { annotations.addAll(Arrays.asList(type.getAnnotations())); type = getSuperType(type); } return annotations; } /** * Returns a list of all methods from given type with specified features. */ public static Set<IMethod> findAllMethods(IType type, String methodPrefix, int argCount, Public publics, Static statics) throws JavaModelException { return findAllMethods(type, methodPrefix, argCount, publics, statics, false); } /** * Returns a list of all methods from given type with specified features. */ public static Set<IMethod> findAllMethods(IType type, String methodPrefix, int argCount, Public publics, Static statics, boolean ignoreCase) throws JavaModelException { Map<String, IMethod> allMethods = new HashMap<String, IMethod>(); while (type != null) { for (IMethod method : getMethods(type)) { checkMethod(type, methodPrefix, argCount, publics, statics, ignoreCase, allMethods, method); } type = getSuperType(type); } return new HashSet<IMethod>(allMethods.values()); } private static void checkMethod(IType type, String methodPrefix, int argCount, Public publics, Static statics, boolean ignoreCase, Map<String, IMethod> allMethods, IMethod method) throws JavaModelException { int flags = method.getFlags(); String key = method.getElementName() + method.getSignature(); if (!allMethods.containsKey(key) && !method.isConstructor() && (publics == Public.DONT_CARE || (publics == Public.YES && (Flags.isPublic(flags) || Flags.isInterface(type.getFlags()))) || (publics == Public.NO && (!Flags .isPublic(flags) && !Flags.isInterface(type.getFlags())))) && (statics == Static.DONT_CARE || (statics == Static.YES && Flags.isStatic(flags)) || (statics == Static.NO && !Flags .isStatic(flags))) && (argCount == -1 || method.getNumberOfParameters() == argCount) && checkMethodNamePrefix(method, type, methodPrefix, ignoreCase)) { allMethods.put(key, method); } } private static boolean checkMethodNamePrefix(IMethod method, IType type, String methodPrefix, boolean ignoreCase) { String methodName = JdtUtils.getMethodName(method); return ((!ignoreCase && methodName.startsWith(methodPrefix))) || (ignoreCase && methodName.toLowerCase().startsWith(methodPrefix.toLowerCase())); } private static boolean checkMethodName(IMethod method, IType type, String methodName, boolean ignoreCase) { String realMethodName = JdtUtils.getMethodName(method); return ((!ignoreCase && realMethodName.equals(methodName))) || (ignoreCase && realMethodName.toLowerCase().startsWith(methodName.toLowerCase())); } /** * Returns a list of all methods from given type with given prefix and no arguments. */ public static Set<IMethod> findAllNoParameterMethods(IType type, String prefix) throws JavaModelException { if (prefix == null) { prefix = ""; } return findAllMethods(type, prefix, 0, Public.DONT_CARE, Static.DONT_CARE); } /** * Returns a list of all setters. */ public static Set<IMethod> findAllWritableProperties(IType type) throws JavaModelException { return findAllMethods(type, "set", 1, Public.YES, Static.NO); } /** * Finds a target methodName with specific number of arguments on the type hierarchy of given type. * @param type the Java type object on which to retrieve the method * @param methodName the name of the method * @param argCount the number of arguments for the desired method * @param isPublic <code>true</code> if public method is requested * @param statics one of the <code>Statics</code> constants */ public static IMethod findMethod(IType type, String methodName, int argCount, Public publics, Static statics) throws JavaModelException { return findMethod(type, methodName, argCount, publics, statics, null); } /** * Finds a target methodName with specific number of arguments on the type hierarchy of given type. * @param type the Java type object on which to retrieve the method * @param methodName the name of the method * @param argCount the number of arguments for the desired method * @param isPublic <code>true</code> if public method is requested * @param statics one of the <code>Statics</code> constants */ public static IMethod findMethod(IType type, String methodName, int argCount, Public publics, Static statics, TypeHierarchyEngine typeHierarchyEngine) throws JavaModelException { for (IType itrType = type; itrType != null; itrType = getSuperType(itrType, typeHierarchyEngine)) { IMethod method = findMethodOnType(itrType, methodName, argCount, publics, statics); if (method != null) { return method; } } for (IType interfaceType : getAllImplementedInterfaces(type, typeHierarchyEngine)) { IMethod method = findMethod(interfaceType, methodName, argCount, publics, statics); if (method != null) { return method; } } return null; } private static IMethod findMethodOnType(IType type, String methodName, int argCount, Public publics, Static statics) throws JavaModelException { for (IMethod method : getMethods(type)) { int flags = method.getFlags(); if ((publics == Public.DONT_CARE || (publics == Public.YES && (Flags.isPublic(flags) || Flags.isInterface(type.getFlags()))) || (publics == Public.NO && (!Flags .isPublic(flags) && !Flags.isInterface(type.getFlags())))) && (statics == Static.DONT_CARE || (statics == Static.YES && Flags.isStatic(flags)) || (statics == Static.NO && !Flags .isStatic(flags))) && (argCount == -1 || method.getNumberOfParameters() == argCount) && checkMethodName(method, type, methodName, false)) { return method; } } return null; } /** * Returns a list of all getters with the given prefix. */ public static Set<IMethod> findReadableProperties(IType type, String methodPrefix) throws JavaModelException { String base = capitalize(methodPrefix); return findAllMethods(type, "get" + base, 0, Public.YES, Static.NO); } /** * Returns a list of all getters with the given prefix. */ public static Set<IMethod> findReadableProperties(IType type, String methodPrefix, boolean ignoreCase) throws JavaModelException { String base = capitalize(methodPrefix); return findAllMethods(type, "get" + base, 0, Public.YES, Static.NO, ignoreCase); } /** * Returns a list of all setters with the given prefix. */ public static Set<IMethod> findWritableProperties(IType type, String methodPrefix) throws JavaModelException { String base = capitalize(methodPrefix); return findAllMethods(type, "set" + base, 1, Public.YES, Static.NO); } /** * Returns a list of all setters with the given prefix. */ public static Set<IMethod> findWritableProperties(IType type, String methodPrefix, boolean ignoreCase) throws JavaModelException { String base = capitalize(methodPrefix); return findAllMethods(type, "set" + base, 1, Public.YES, Static.NO, ignoreCase); } public static Set<IType> getAllImplementedInterfaces(IType type) { return getAllImplementedInterfaces(type, null); } public static Set<IType> getAllImplementedInterfaces(IType type, TypeHierarchyEngine typeHierarchyEngine) { Set<IType> allInterfaces = new HashSet<IType>(); try { while (type != null) { String[] interfaces = type.getSuperInterfaceTypeSignatures(); if (interfaces != null) { for (String iface : interfaces) { String fqin = JdtUtils.resolveClassNameBySignature(iface, type); IType interfaceType = type.getJavaProject().findType(fqin); if (interfaceType != null) { allInterfaces.add(interfaceType); } } } type = getSuperType(type, typeHierarchyEngine); } } catch (JavaModelException e) { // BeansCorePlugin.log(e); } return allInterfaces; } private static boolean implementsInterface(IType type, String className) { try { while (type != null) { String[] interfaces = type.getSuperInterfaceTypeSignatures(); if (interfaces != null) { for (String iface : interfaces) { String fqin = JdtUtils.resolveClassNameBySignature(iface, type); IType interfaceType = type.getJavaProject().findType(fqin); if (interfaceType != null && interfaceType.getFullyQualifiedName().equals(className)) { return true; } } } type = getSuperType(type); } } catch (JavaModelException e) { // BeansCorePlugin.log(e); } return false; } /** * Returns <strong>all</strong> methods of the given {@link IType} instance. * @param type the type * @return set of {@link IMethod} * @throws JavaModelException */ public static Set<IMethod> getAllMethods(IType type) throws JavaModelException { return getAllMethods(type, true); } /** * Returns <strong>all</strong> methods of the given {@link IType} instance. * @param type the type * @param includeHierarchy indicates if methods from superclasses should be included * @return set of {@link IMethod} * @throws JavaModelException * @since 3.2.0 */ public static Set<IMethod> getAllMethods(IType type, boolean includeHierarchy) throws JavaModelException { Map<String, IMethod> allMethods = new HashMap<String, IMethod>(); while (type != null) { for (IMethod method : getMethods(type)) { String key = method.getElementName() + method.getSignature(); if (!allMethods.containsKey(key) && !method.isConstructor()) { allMethods.put(key, method); } } if (!includeHierarchy) break; type = getSuperType(type); } return new HashSet<IMethod>(allMethods.values()); } /** * Returns <strong>all</strong> constructors of the given {@link IType} instance. * @param type the type * @return set of {@link IMethod} * @throws JavaModelException */ public static Set<IMethod> getAllConstructors(IType type) throws JavaModelException { Map<String, IMethod> allMethods = new HashMap<String, IMethod>(); for (IMethod method : getMethods(type)) { String key = method.getElementName() + method.getSignature(); if (!allMethods.containsKey(key) && method.isConstructor()) { allMethods.put(key, method); } } return new HashSet<IMethod>(allMethods.values()); } /** * Returns <strong>all</strong> fields of the given {@link IType} instance. * @param type the type * @return set of {@link IMethod} * @throws JavaModelException */ public static Set<IField> getAllFields(IType type) throws JavaModelException { return getAllFields(type, true); } /** * Returns <strong>all</strong> fields of the given {@link IType} instance. * @param type the type * @param includeHierarchy should include fields from superclasses or not * @return set of {@link IMethod} * @throws JavaModelException * @since 3.2.0 */ public static Set<IField> getAllFields(IType type, boolean includeHierarchy) throws JavaModelException { Map<String, IField> allFields = new HashMap<String, IField>(); while (type != null) { for (IField field : type.getFields()) { String key = field.getHandleIdentifier(); if (!allFields.containsKey(key)) { allFields.put(key, field); } } if (!includeHierarchy) break; type = getSuperType(type); } return new HashSet<IField>(allFields.values()); } public static IMethod getReadableProperty(IType type, String propertyName) throws JavaModelException { String base = capitalize(propertyName); return findMethod(type, "get" + base, 0, Public.YES, Static.NO); } /** * Returns the super type of the given type. */ public static IType getSuperType(IType type) throws JavaModelException { TypeHierarchyEngine typeHierarchyEngine = System.getProperty(TypeHierarchyEngine.ENABLE_PROPERTY, "true").equals("true") ? SpringCore.getTypeHierarchyEngine() : null; return getSuperType(type, typeHierarchyEngine); } /** * Returns the super type of the given type. * This is using the type hierarchy engine that is passed as parameter, if not null */ public static IType getSuperType(IType type, TypeHierarchyEngine typeHierarchyEngine) throws JavaModelException { if (type == null) { return null; } String name = type.getSuperclassName(); if (name == null && !type.getFullyQualifiedName().equals(Object.class.getName())) { name = Object.class.getName(); } if (name != null) { if (type.isBinary()) { return type.getJavaProject().findType(name); } else if (typeHierarchyEngine != null) { String supertype = typeHierarchyEngine.getSupertype(type); if (supertype != null) { return type.getJavaProject().findType(supertype); } } else { String resolvedName = JdtUtils.resolveClassName(name, type); if (resolvedName != null) { return type.getJavaProject().findType(resolvedName); } } } return null; } public static IMethod getWritableProperty(IType type, String propertyName) throws JavaModelException { return getWritableProperty(type, propertyName, null); } public static IMethod getWritableProperty(IType type, String propertyName, TypeHierarchyEngine typeHierarchyEngine) throws JavaModelException { String base = capitalize(propertyName); return findMethod(type, "set" + base, 1, Public.YES, Static.NO, typeHierarchyEngine); } public static Set<IMethod> getConstructors(IType type, int argCount, boolean isNonPublicAllowed) throws JavaModelException { IMethod[] methods = getMethods(type); Set<IMethod> ctors = new LinkedHashSet<IMethod>(); if (argCount > 0) { for (IMethod method : methods) { if (method.isConstructor()) { if (method.getNumberOfParameters() == argCount) { if (isNonPublicAllowed || Flags.isPublic(method.getFlags())) { ctors.add(method); } } } } } return ctors; } /** * Returns <code>true</code> if the given type has a public constructor with the specified number of arguments. If a * constructor with no arguments is requested then the absence of a constructor (the JVM adds an implicit * constructor here) results in <code>true</code>. * @param type the Java type object on which to retrieve the method * @param argCount the number of arguments for the constructor * @param isNonPublicAllowed <code>true</code> if non-public constructurs are recognized too */ public static boolean hasConstructor(IType type, int argCount, boolean isNonPublicAllowed) throws JavaModelException { IMethod[] methods = getMethods(type); // First check for implicit constructor if (argCount == 0) { // Check if the methods do contain constuctors boolean hasExplicitConstructor = false; for (IMethod method : methods) { if (method.isConstructor()) { hasExplicitConstructor = true; } } if (!hasExplicitConstructor) { return true; } } // Now look for appropriate constructor for (IMethod method : methods) { if (method.isConstructor()) { if (method.getNumberOfParameters() == argCount) { if (isNonPublicAllowed || Flags.isPublic(method.getFlags())) { return true; } } } } return false; } private static boolean hasSuperType(IType type, String className, boolean isInterface) { if (type != null && type.exists() && className != null && className.length() > 0) { try { if (!isInterface) { while (type != null) { if (className.equals(type.getFullyQualifiedName())) { return true; } type = getSuperType(type); } } else { if (implementsInterface(type, className)) { return true; } } } catch (JavaModelException e) { SpringCore.log(e); } } return false; } public static boolean hasSuperType(IType type, String className) { return hasSuperType(type, className, false); } /** * Returns true if the given type has a public setter (one-argument method named "set" + property name with an * uppercase first character) for the specified property. * @param type the Java type object on which to retrieve the method * @param propertyName the name of the property */ public static boolean hasWritableProperty(IType type, String propertyName) throws JavaModelException { return hasWritableProperty(type, propertyName, null); } /** * Returns true if the given type has a public setter (one-argument method named "set" + property name with an * uppercase first character) for the specified property. * @param type the Java type object on which to retrieve the method * @param propertyName the name of the property */ public static boolean hasWritableProperty(IType type, String propertyName, TypeHierarchyEngine typeHierarchyEngine) throws JavaModelException { String base = capitalize(propertyName); return (findMethod(type, "set" + base, 1, Public.YES, Static.NO, typeHierarchyEngine) != null); } /** * Returns <code>true</code> if the given name is a valid JavaBeans property name. This normally means that a * property name starts with a lower case character, but in the (unusual) special case when there is more than one * character and both the first and second characters are upper case, then an upper case character is valid too. * <p> * Thus "fooBah" corresponds to "FooBah" and "x" to "X", but "URL" stays the same as "URL". * <p> * This conforms to section "8.8 Capitalization of inferred names" of the JavaBeans specs. * @param name the name to be checked */ public static boolean isValidPropertyName(String name) { if (name == null || name.length() == 0) { return false; } if (name.length() == 1 && Character.isUpperCase(name.charAt(0))) { return false; } if (name.length() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isLowerCase(name.charAt(1))) { return false; } return true; } public static IMethod[] getMethods(IType type) throws JavaModelException { if (type == null) { return new IMethod[0]; } if (type.isStructureKnown()) { IMethod[] methods = type.getMethods(); if (JdtUtils.isAjdtProject(type.getResource())) { Set<IMethod> itdMethods = AjdtUtils.getDeclaredMethods(type); if (itdMethods.size() > 0) { int i = methods.length; IMethod[] allMethods = new IMethod[methods.length + itdMethods.size()]; System.arraycopy(methods, 0, allMethods, 0, methods.length); for (IMethod method : itdMethods) { allMethods[i++] = method; } methods = allMethods; } } return methods; } return new IMethod[0]; } }