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;
}
}