package org.objectstyle.wolips.bindings.utils; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.ui.search.JavaSearchScopeFactory; import org.objectstyle.wolips.bindings.wod.BindingValueKey; import org.objectstyle.wolips.bindings.wod.BindingValueKeyPath; import org.objectstyle.wolips.bindings.wod.TypeCache; import org.objectstyle.wolips.core.resources.types.TypeNameCollector; import org.objectstyle.wolips.core.resources.types.WOHierarchyScope; public class BindingReflectionUtils { public static final String[] FIELD_PREFIXES = { "", "_" }; public static final String[] SET_METHOD_PREFIXES = { "set", "_set" }; public static final String[] GET_METHOD_PREFIXES = { "get", "", "_get", "is", "_is", "_" }; public static final int ACCESSORS_ONLY = 0; public static final int MUTATORS_ONLY = 1; public static final int ACCESSORS_OR_VOID = 2; public static final int VOID_ONLY = 3; public static boolean isBoolean(String typeName) { return "boolean".equals(typeName) || "Boolean".equals(typeName) || "java.lang.Boolean".equals(typeName); } public static boolean isImportRequired(String typeName) { return typeName != null && !isPrimitive(typeName) && !typeName.equals("java.lang." + Signature.getSimpleName(typeName)); } public static boolean isPrimitive(String typeName) { return ("boolean".equals(typeName) || "byte".equals(typeName) || "char".equals(typeName) || "int".equals(typeName) || "short".equals(typeName) || "float".equals(typeName) || "double".equals(typeName)); } public static String getFullClassName(IJavaProject javaProject, String shortClassName) throws JavaModelException { String expandedClassName = shortClassName; if (expandedClassName != null && expandedClassName.indexOf('.') == -1) { if ("String".equals(shortClassName)) { expandedClassName = "java.lang.String"; } else if ("Object".equals(shortClassName)) { expandedClassName = "java.lang.Object"; } else if (isPrimitive(expandedClassName)) { // ignore primitives } else { SearchEngine searchEngine = new SearchEngine(); IJavaSearchScope searchScope = JavaSearchScopeFactory.getInstance().createJavaProjectSearchScope(javaProject, JavaSearchScopeFactory.ALL); NullProgressMonitor progressMonitor = new NullProgressMonitor(); TypeNameCollector typeNameCollector = new TypeNameCollector(javaProject, false); searchEngine.searchAllTypeNames(null, SearchPattern.R_EXACT_MATCH, shortClassName.toCharArray(), IJavaSearchConstants.TYPE, IJavaSearchConstants.TYPE, searchScope, typeNameCollector, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, progressMonitor); Set<String> typeNames = typeNameCollector.getTypeNames(); if (typeNames.size() == 1) { expandedClassName = typeNames.iterator().next(); } else if (typeNames.size() == 0) { System.out.println("BindingReflectionUtils.getFullClassName: Unknown type name " + shortClassName); } else { System.out.println("BindingReflectionUtils.getFullClassName: Ambiguous type name " + shortClassName + " (" + typeNames + ")"); } } } return expandedClassName; } public static String getShortClassName(String fullClassName) { String shortClassName; int lastDotIndex = fullClassName.lastIndexOf('.'); if (lastDotIndex == -1) { shortClassName = fullClassName; } else { shortClassName = fullClassName.substring(lastDotIndex + 1); } return shortClassName; } public static IType findElementType(IJavaProject javaProject, String elementTypeName, boolean requireTypeInProject, TypeCache cache) throws JavaModelException { // Search the current project for the given element type name String typeName = cache.getApiCache(javaProject).getElementTypeNamed(elementTypeName); IType type = null; if (typeName != null) { type = javaProject.findType(typeName); } else { //long a = System.currentTimeMillis(); NullProgressMonitor progressMonitor = new NullProgressMonitor(); TypeNameCollector typeNameCollector = new TypeNameCollector(javaProject, requireTypeInProject); //System.out.println("BindingReflectionUtils.findElementType: start " + System.currentTimeMillis()); BindingReflectionUtils.findMatchingElementClassNames(elementTypeName, SearchPattern.R_EXACT_MATCH, typeNameCollector, progressMonitor); //System.out.println("BindingReflectionUtils.findElementType: " + (System.currentTimeMillis() - a)); if (typeNameCollector.isExactMatch()) { String matchingElementClassName = typeNameCollector.firstTypeName(); type = typeNameCollector.getTypeForClassName(matchingElementClassName); } else if (!typeNameCollector.isEmpty()) { // there was more than one matching class! crap! String matchingElementClassName = typeNameCollector.firstTypeName(); type = typeNameCollector.getTypeForClassName(matchingElementClassName); } if (type != null) { cache.getApiCache(javaProject).setElementTypeForName(type, elementTypeName); } } return type; } public static void findMatchingElementClassNames(String elementTypeName, int matchType, TypeNameCollector typeNameCollector, IProgressMonitor progressMonitor) throws JavaModelException { if (elementTypeName != null) { SearchEngine searchEngine = new SearchEngine(); //IJavaSearchScope searchScope = new WOHierarchyScope(typeNameCollector.getSuperclassType(), typeNameCollector.getProject()); IJavaSearchScope searchScope = WOHierarchyScope.hierarchyScope(typeNameCollector.getSuperclassType(), typeNameCollector.getProject().getProject()); int lastDotIndex = elementTypeName.lastIndexOf('.'); char[] packageName; char[] typeName; if (lastDotIndex == -1) { packageName = null; typeName = elementTypeName.toCharArray(); } else { packageName = elementTypeName.substring(0, lastDotIndex).toCharArray(); typeName = elementTypeName.substring(lastDotIndex + 1).toCharArray(); } searchEngine.searchAllTypeNames(packageName, SearchPattern.R_EXACT_MATCH, typeName, matchType, IJavaSearchConstants.CLASS, searchScope, typeNameCollector, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, progressMonitor); } } public static boolean isWOComponent(IType type, TypeCache cache) throws JavaModelException { return BindingReflectionUtils.isType(type, new String[] { "com.webobjects.appserver.WOComponent" }, cache); } public static boolean isNSKeyValueCoding(IType type, TypeCache cache) throws JavaModelException { return BindingReflectionUtils.isType(type, new String[] { "com.webobjects.foundation.NSKeyValueCoding" }, cache); } public static boolean isNSCollection(IType type, TypeCache cache) throws JavaModelException { return BindingReflectionUtils.isType(type, new String[] { "com.webobjects.foundation.NSDictionary", "com.webobjects.foundation.NSArray", "com.webobjects.foundation.NSSet", "er.extensions.TBWLocalizer" }, cache); } public static boolean isType(IType type, String[] possibleTypes, TypeCache cache) throws JavaModelException { boolean isType = false; //ITypeHierarchy typeHierarchy = SuperTypeHierarchyCache.getTypeHierarchy(type); List<IType> types = cache.getSupertypesOf(type); for (int typeNum = 0; !isType && typeNum < types.size(); typeNum++) { String name = types.get(typeNum).getFullyQualifiedName(); for (int possibleTypeNum = 0; !isType && possibleTypeNum < possibleTypes.length; possibleTypeNum++) { if (possibleTypes[possibleTypeNum].equals(name)) { isType = true; } } } return isType; } public static Set<String> getArrayOperators() { Set<String> operators = new HashSet<String>(); operators.add("avg"); operators.add("count"); operators.add("min"); operators.add("max"); operators.add("sum"); operators.add("sort"); operators.add("sortAsc"); operators.add("sortDesc"); return operators; } public static List<BindingValueKey> getBindingKeys(IJavaProject javaProject, IType type, String nameStartingWith, boolean requireExactNameMatch, int accessorsOrMutators, boolean allowInheritanceDuplicates, TypeCache cache) throws JavaModelException { List<BindingValueKey> bindingKeys = new LinkedList<BindingValueKey>(); // if (_requireExactNameMatch && BindingReflectionUtils.isBooleanValue(_nameStartingWith)) { // return bindingKeys; // } //System.out.println("BindingReflectionUtils.getBindingKeys: " + _type.getElementName() + ", " + _nameStartingWith + ", " + _requireExactNameMatch + ", " + _accessorsOrMutators); if (type != null) { String lowercaseNameStartingWith = nameStartingWith.toLowerCase(); Set<String> additionalProposals = new HashSet<String>(); // We want to show fields from your WOApplication, WOSession, and // WODirectAction subclasses ... //ITypeHierarchy typeHierarchy = SuperTypeHierarchyCache.getTypeHierarchy(_type); List<IType> types = cache.getSupertypesOf(type); if (types != null) { IType usuallySubclassedSupertype = null; IType nextType = null; for (int typeNum = 0; usuallySubclassedSupertype == null && typeNum < types.size(); typeNum++) { String typeName = types.get(typeNum).getElementName(); if ("WOApplication".equals(typeName) || "WOSession".equals(typeName) || "WODirectAction".equals(typeName)) { usuallySubclassedSupertype = types.get(typeNum); } else if ("NSArray".equals(typeName)) { for (String operator : BindingReflectionUtils.getArrayOperators()) { additionalProposals.add("@" + operator); } nextType = types.get(typeNum); } } for (String additionalProposal : additionalProposals) { if (additionalProposal.startsWith(nameStartingWith)) { BindingValueKey additionalKey = new BindingValueKey(additionalProposal, null, null, javaProject, cache); // MS: this is a hack to prevent NPE's because we don't know the next type right now ... //additionalKey.setNextType(nextType); bindingKeys.add(additionalKey); } } if (usuallySubclassedSupertype != null) { //typeHierarchy = _type.newTypeHierarchy(_javaProject, null); //typeHierarchy = SubTypeHierachyCache.getTypeHierarchy(_type); types = cache.getSubtypesOfInProject(usuallySubclassedSupertype, javaProject); } } if (types != null) { for (int typeNum = 0; (!requireExactNameMatch || bindingKeys.size() == 0) && typeNum < types.size(); typeNum++) { //for (int typeNum = types.length - 1; (!_requireExactNameMatch || bindingKeys.size() == 0) && typeNum >= 0; typeNum --) { BindingReflectionUtils.fillInBindingKeys(type, types.get(typeNum), lowercaseNameStartingWith, requireExactNameMatch, accessorsOrMutators, allowInheritanceDuplicates, javaProject, bindingKeys, cache); } } } return bindingKeys; } protected static void fillInBindingKeys(IType declaringType, IType type, String lowercaseNameStartingWith, boolean requireExactNameMatch, int accessorsOrMutators, boolean allowInheritanceDuplicates, IJavaProject javaProject, List<BindingValueKey> bindingKeys, TypeCache cache) throws JavaModelException { //System.out.println("BindingReflectionUtils.getBindingKeys: a " + type.getFullyQualifiedName()); IField[] fields = type.getFields(); for (int fieldNum = 0; (!requireExactNameMatch || bindingKeys.size() == 0) && fieldNum < fields.length; fieldNum++) { for (String prefix : BindingReflectionUtils.FIELD_PREFIXES) { BindingValueKey bindingKey = BindingReflectionUtils.getBindingKeyIfMatches(javaProject, declaringType, fields[fieldNum], prefix + lowercaseNameStartingWith, prefix, requireExactNameMatch, accessorsOrMutators, cache); if (bindingKey != null) { bindingKeys.add(bindingKey); break; } } } if (!requireExactNameMatch || bindingKeys.size() == 0) { IMethod[] methods = type.getMethods(); String[] prefixes; if (accessorsOrMutators == BindingReflectionUtils.ACCESSORS_ONLY) { prefixes = BindingReflectionUtils.GET_METHOD_PREFIXES; } else if (accessorsOrMutators == BindingReflectionUtils.ACCESSORS_OR_VOID) { prefixes = BindingReflectionUtils.GET_METHOD_PREFIXES; } else if (accessorsOrMutators == BindingReflectionUtils.MUTATORS_ONLY) { prefixes = BindingReflectionUtils.SET_METHOD_PREFIXES; } else { prefixes = new String[0]; } for (int methodNum = 0; (!requireExactNameMatch || bindingKeys.size() == 0) && methodNum < methods.length; methodNum++) { for (String prefix : prefixes) { //System.out.println("BindingReflectionUtils.getBindingKeys: checking for " + prefix + methods[methodNum].getElementName()); BindingValueKey bindingKey = BindingReflectionUtils.getBindingKeyIfMatches(javaProject, declaringType, methods[methodNum], prefix + lowercaseNameStartingWith, prefix, requireExactNameMatch, accessorsOrMutators, cache); if (bindingKey != null) { if (allowInheritanceDuplicates || !bindingKeys.contains(bindingKey)) { bindingKeys.add(bindingKey); } break; } } } } } public static boolean isDefaultPackage(IMember member) { IType declaringType = member.getDeclaringType(); String declaringTypePackageName = declaringType.getPackageFragment().getElementName(); return declaringTypePackageName == null || declaringTypePackageName.length() == 0; } protected static enum Visibility { Hidden, Visible, MaybeVisible } public static BindingValueKey getBindingKeyIfMatches(IJavaProject javaProject, IType type, IMember member, String nameStartingWith, String prefix, boolean requireExactNameMatch, int accessorsOrMutators, TypeCache cache) throws JavaModelException { //System.out.println("BindingReflectionUtils.getBindingKeyIfMatches: " + member.getElementName() + " starts with " + nameStartingWith); BindingValueKey bindingKey = null; int flags = member.getFlags(); Visibility visible = Visibility.Hidden; // Private is never an option if (Flags.isPrivate(flags)) { visible = Visibility.Hidden; } // Don't show static methods and fields else if (Flags.isStatic(flags)) { visible = Visibility.Hidden; } // Public bindings are always visible else if (Flags.isPublic(flags) || type.isInterface()) { visible = Visibility.Visible; } // Components that are not in a package can have bindings to protected fields //else if ((Flags.isProtected(flags) || Flags.isPackageDefault(flags)) && BindingReflectionUtils.isDefaultPackage(member)) { else if (Flags.isProtected(flags) || Flags.isPackageDefault(flags)) { if (BindingReflectionUtils.isDefaultPackage(member)) { visible = Visibility.Visible; } else { visible = Visibility.MaybeVisible; } } if (visible != Visibility.Hidden) { boolean memberSignatureMatches; if (member instanceof IMethod) { IMethod method = (IMethod) member; if (method.isConstructor()) { memberSignatureMatches = false; } else { int parameterCount = method.getParameterTypes().length; String returnType = method.getReturnType(); if (accessorsOrMutators == BindingReflectionUtils.ACCESSORS_ONLY) { memberSignatureMatches = (parameterCount == 0 && !"V".equals(returnType)); } else if (accessorsOrMutators == BindingReflectionUtils.ACCESSORS_OR_VOID) { memberSignatureMatches = (parameterCount == 0); } else if (accessorsOrMutators == BindingReflectionUtils.VOID_ONLY) { memberSignatureMatches = (parameterCount == 0 && "V".equals(returnType)); } else { memberSignatureMatches = (parameterCount == 1 && "V".equals(returnType)); } } } else { memberSignatureMatches = true; } if (memberSignatureMatches) { String memberName = member.getElementName(); String lowercaseMemberName = memberName.toLowerCase(); int prefixLength = prefix.length(); if ((requireExactNameMatch && lowercaseMemberName.equals(nameStartingWith)) || (!requireExactNameMatch && lowercaseMemberName.startsWith(nameStartingWith))) { String bindingName = BindingReflectionUtils.toLowercaseFirstLetter(memberName.substring(prefixLength)); //System.out.println("BindingReflectionUtils.getBindingKeyIfMatches: bindingName = " + bindingName); if (nameStartingWith.length() > 0 || !bindingName.startsWith("_")) { if (visible == Visibility.MaybeVisible) { String packageName = type.getPackageFragment().getElementName(); String kvcProtectedAccessorClassName = packageName.length() == 0 ? "KeyValueCodingProtectedAccessor" : (packageName + ".KeyValueCodingProtectedAccessor"); IType kvcProtectedAccessor = type.getJavaProject().findType(kvcProtectedAccessorClassName); if (kvcProtectedAccessor != null) { visible = Visibility.Visible; } } if (visible == Visibility.Visible) { bindingKey = new BindingValueKey(bindingName, type, member, javaProject, cache); } } } } } return bindingKey; } public static String toLowercaseFirstLetter(String _memberName) { String lowercaseFirstLetterMemberName; if (_memberName.length() > 0) { char firstChar = _memberName.charAt(0); if (Character.isUpperCase(firstChar)) { lowercaseFirstLetterMemberName = Character.toLowerCase(firstChar) + _memberName.substring(1); } else { lowercaseFirstLetterMemberName = _memberName; } } else { lowercaseFirstLetterMemberName = _memberName; } return lowercaseFirstLetterMemberName; } public static Set<String> _systemTypeNames; public static Set<String> _uselessSystemBindings; public static Set<String> _usefulSystemBindings; static { _systemTypeNames = new HashSet<String>(); _systemTypeNames.add("Object"); _systemTypeNames.add("WOElement"); _systemTypeNames.add("WOActionResults"); _systemTypeNames.add("WOComponent"); _uselessSystemBindings = new HashSet<String>(); _uselessSystemBindings.add("baseURL"); _uselessSystemBindings.add("bindingKeys"); _uselessSystemBindings.add("cachingEnabled"); _uselessSystemBindings.add("childTemplate"); _uselessSystemBindings.add("getClass"); _uselessSystemBindings.add("class"); _uselessSystemBindings.add("clone"); _uselessSystemBindings.add("componentDefinition"); _uselessSystemBindings.add("componentUnroll"); _uselessSystemBindings.add("frameworkName"); _uselessSystemBindings.add("generateResponse"); _uselessSystemBindings.add("hashCode"); _uselessSystemBindings.add("isCachingEnabled"); _uselessSystemBindings.add("eventLoggingEnabled"); _uselessSystemBindings.add("isEventLoggingEnabled"); _uselessSystemBindings.add("isPage"); _uselessSystemBindings.add("isStateless"); _uselessSystemBindings.add("stateless"); _uselessSystemBindings.add("keyAssociations"); _uselessSystemBindings.add("name"); _uselessSystemBindings.add("page"); _uselessSystemBindings.add("parent"); _uselessSystemBindings.add("path"); _uselessSystemBindings.add("pathURL"); _uselessSystemBindings.add("synchronizesVariablesWithBindings"); _uselessSystemBindings.add("template"); _uselessSystemBindings.add("toString"); _uselessSystemBindings.add("unroll"); _usefulSystemBindings = new HashSet<String>(); _usefulSystemBindings.add("application"); _usefulSystemBindings.add("context"); _usefulSystemBindings.add("hasSession"); _usefulSystemBindings.add("session"); } public static boolean isSystemBindingValueKey(BindingValueKey bindingValueKey, boolean showUsefulSystemBindings) { boolean isSystemBinding = false; if (bindingValueKey != null && bindingValueKey.getDeclaringType() != null) { String declaringTypeName = bindingValueKey.getDeclaringType().getElementName(); String bindingName = bindingValueKey.getBindingName(); if (BindingReflectionUtils._systemTypeNames.contains(declaringTypeName)) { if (!showUsefulSystemBindings && BindingReflectionUtils._usefulSystemBindings.contains(bindingName)) { isSystemBinding = true; } else if (BindingReflectionUtils._uselessSystemBindings.contains(bindingName)) { isSystemBinding = true; } } else if (bindingName.startsWith("_")) { isSystemBinding = true; } } return isSystemBinding; } public static boolean isBooleanValue(String keyPath) { return "true".equalsIgnoreCase(keyPath) || "false".equalsIgnoreCase(keyPath) || "yes".equalsIgnoreCase(keyPath) || "no".equalsIgnoreCase(keyPath); } public static List<BindingValueKey> filterSystemBindingValueKeys(List<BindingValueKey> bindingKeys, boolean showUsefulSystemBindings) { Set<BindingValueKey> systemBindingValueKeys = new HashSet<BindingValueKey>(); for (BindingValueKey bindingKey : bindingKeys) { if (BindingReflectionUtils.isSystemBindingValueKey(bindingKey, showUsefulSystemBindings)) { systemBindingValueKeys.add(bindingKey); } } List<BindingValueKey> filteredBindingValueKeys; if (systemBindingValueKeys.isEmpty()) { filteredBindingValueKeys = bindingKeys; } else { filteredBindingValueKeys = new LinkedList<BindingValueKey>(); for (BindingValueKey bindingKey : bindingKeys) { // System.out.println("BindingReflectionUtils.filterSystemBindingValueKeys: FILTERING? " + bindingKey.getBindingName()); if (!systemBindingValueKeys.contains(bindingKey)) { filteredBindingValueKeys.add(bindingKey); } // else { // System.out.println("BindingReflectionUtils.filterSystemBindingValueKeys: SKIPPED"); // } } } return filteredBindingValueKeys; } /** * Returns the BindingValueKeys for the given type grouped by which class the * keys were contributed from. * * @param startingWith a partial keypath or "" for all * @param type the starting type * @param cache the TypeCache * @return a map of supertypes to keys * @throws JavaModelException if the type information cannot be loaded */ public static Map<IType, Set<BindingValueKey>> getGroupedBindingValueKeys(String startingWith, IType type, TypeCache cache) throws JavaModelException { BindingValueKeyPath bindingValueKeyPath = new BindingValueKeyPath(startingWith, type, type.getJavaProject(), cache); List<BindingValueKey> bindingValueKeys = bindingValueKeyPath.getPartialMatchesForLastBindingKey(true); List<BindingValueKey> filteredBindingValueKeys = BindingReflectionUtils.filterSystemBindingValueKeys(bindingValueKeys, true); Set<BindingValueKey> uniqueBingingValueKeys = new TreeSet<BindingValueKey>(filteredBindingValueKeys); Map<IType, Set<BindingValueKey>> typeKeys = new TreeMap<IType, Set<BindingValueKey>>(new TypeDepthComparator(cache)); for (BindingValueKey key : uniqueBingingValueKeys) { IType declaringType = key.getDeclaringType(); Set<BindingValueKey> typeKeysSet = typeKeys.get(declaringType); if (typeKeysSet == null) { typeKeysSet = new TreeSet<BindingValueKey>(); typeKeys.put(declaringType, typeKeysSet); } typeKeysSet.add(key); } return typeKeys; } /** * Check if the member the given binding key points to is deprecated. * * @param bindingKey the binding * @return <code>true</code> if binding points to something deprecated */ public static boolean bindingPointsToDeprecatedValue(BindingValueKey bindingKey) { return memberIsDeprecated(bindingKey.getBindingMember()); } /** * Check if the given member is deprecated. * * @param member the member * @return <code>true</code> if member is deprecated */ public static boolean memberIsDeprecated(IMember member) { boolean isDeprecated = false; if (member != null) { try { isDeprecated = ((member.getFlags() & Flags.AccDeprecated) == Flags.AccDeprecated); } catch (JavaModelException e) { // ignore } } return isDeprecated; } }