/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.javaparser; import java.util.Iterator; import java.util.TreeSet; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.openflexo.foundation.dm.DMCardinality; import org.openflexo.foundation.dm.DMMethod; import org.openflexo.foundation.dm.DMMethod.DMMethodParameter; import org.openflexo.foundation.dm.DMModel; import org.openflexo.foundation.dm.DMProperty; import org.openflexo.foundation.dm.DMPropertyImplementationType; import org.openflexo.foundation.dm.DMType; import org.openflexo.foundation.dm.DMVisibilityType; import org.openflexo.foundation.dm.DuplicateMethodSignatureException; import org.openflexo.foundation.dm.javaparser.ParsedJavadocItem; import org.openflexo.foundation.dm.javaparser.ParserNotInstalledException; import org.openflexo.javaparser.FJPTypeResolver.CrossReferencedEntitiesException; import com.thoughtworks.qdox.model.Type; /** * Utility class used to perform mapping between FJP model and Flexo DM model (maps source code on DM-model, ie DMEntity, DMProperty, ...) * * @author sylvain * */ public class FJPDMMapper { private static final Logger logger = Logger.getLogger(FJPDMMapper.class.getPackage().getName()); public static Vector<DMMethod> searchForMethods(FJPJavaClass aClass, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean importReferencedEntities, Vector<String> excludedSignatures) { Vector<DMMethod> returned = new Vector<DMMethod>(); try { FJPJavaMethod[] declaredMethods = aClass.getMethods(); for (FJPJavaMethod method : declaredMethods) { DMMethod newMethod = makeMethod(method, dataModel, context, source, importReferencedEntities); if (newMethod != null && (excludedSignatures == null || !excludedSignatures.contains(newMethod.getSignature()))) { returned.add(newMethod); // logger.info("Add "+newMethod.getSignature()); } else { if (logger.isLoggable(Level.FINE)) { logger.fine("Exclude " + method.getCallSignature()); } } } } catch (NoClassDefFoundError e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not find class: " + e.getMessage()); } } catch (Throwable e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Unexpected exception raised " + e); } e.printStackTrace(); } if (logger.isLoggable(Level.FINE)) { logger.fine("searchForMethods() for " + aClass.getName()); logger.fine("Return " + returned); } return returned; } public static Vector<DMProperty> searchForProperties(FJPJavaClass aClass, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean includesGetOnlyProperties, boolean importReferencedEntities, Vector<String> excludedSignatures) { Vector<DMProperty> returned = new Vector<DMProperty>(); FJPJavaMethod[] declaredMethods = aClass.getMethods(); for (FJPJavaMethod method : declaredMethods) { DMProperty newProperty = null; try { newProperty = makeProperty(method, dataModel, context, source, includesGetOnlyProperties, importReferencedEntities, excludedSignatures); } catch (FJPTypeResolver.CrossReferencedEntitiesException e) { } if (newProperty != null) { returned.add(newProperty); // logger.info("add property "+newProperty+" for method "+method); } } FJPJavaField[] declaredFields = aClass.getFields(); for (int i = 0; i < declaredFields.length; i++) { FJPJavaField field = declaredFields[i]; DMProperty newProperty = null; try { newProperty = makeProperty(field, dataModel, context, source, importReferencedEntities); } catch (FJPTypeResolver.CrossReferencedEntitiesException e) { } if (newProperty != null) { returned.add(newProperty); // logger.info("add property "+newProperty); } } if (logger.isLoggable(Level.FINE)) { logger.fine("searchForProperties() for " + aClass.getName()); logger.fine("Return " + returned); } return returned; } static DMMethod makeMethod(FJPJavaClass aClass, String signature, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean importReferencedEntities) throws FJPTypeResolver.CrossReferencedEntitiesException { FJPJavaMethod method = searchMatchingMethod(aClass, signature, dataModel, context, source); if (method != null) { return makeMethod(method, dataModel, context, source, importReferencedEntities); } return null; } static DMMethod makeMethod(FJPJavaMethod method, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean importReferencedEntities) throws FJPTypeResolver.CrossReferencedEntitiesException { if (method.isConstructor()) { return null; } DMType returnType = method.getReturns(); Vector<FJPJavaParameter> parameters = method.getParameters(); String methodName = method.getName(); DMMethod newMethod = new DMMethod(dataModel.getDMModel(), methodName); newMethod.preventFromModifiedPropagation(); // Visibility DMVisibilityType visibility; if (method.isPublic()) { visibility = DMVisibilityType.PUBLIC; } else if (method.isProtected()) { visibility = DMVisibilityType.PROTECTED; } else if (method.isPrivate()) { visibility = DMVisibilityType.PRIVATE; } else { visibility = DMVisibilityType.NONE; } newMethod.setVisibilityModifier(visibility); // Static newMethod.setIsStatic(method.isStatic()); // Abstract newMethod.setIsAbstract(method.isAbstract()); // Synchronized newMethod.setIsSynchronized(method.isSynchronized()); // Lookup return type // DMEntity returnTypeEntity = null; if (returnType != null) { // May happen with constructors if (FJPTypeResolver.isResolvable(returnType, dataModel, context, source)) { /*returnTypeEntity =*/FJPTypeResolver.resolveEntity(returnType, dataModel, context, source, importReferencedEntities); // newMethod.setReturnType(returnTypeEntity); } newMethod.setReturnType(returnType, true); /*if (returnTypeEntity == null && returnType instanceof DMType) { newMethod.addToUnresolvedTypes((DMType)returnType); newMethod.setUnresolvedReturnType((DMType)returnType); }*/ } if (parameters != null) { for (FJPJavaParameter parameter : parameters) { DMMethodParameter param = new DMMethodParameter(dataModel, newMethod); param.preventFromModifiedPropagation(); param.setName(parameter.getName()); // Lookup type // DMEntity typeEntity = null; if (FJPTypeResolver.isResolvable(parameter.getType(), dataModel, context, source)) { /*typeEntity =*/FJPTypeResolver.resolveEntity(parameter.getType(), dataModel, context, source, importReferencedEntities); // param.setType(typeEntity); } param.setType(parameter.getType()); /*if (typeEntity == null && parameter.getType() instanceof DMType) { param.setUnresolvedType((DMType)parameter.getType()); newMethod.addToUnresolvedTypes((DMType)parameter.getType()); }*/ newMethod.addToParametersNoCheck(param); } } try { newMethod.getSourceCode().setCode((method.getJavadoc() != null ? method.getJavadoc().toString() : "") + method.getSourceCode(), false); } catch (ParserNotInstalledException e) { e.printStackTrace(); } catch (DuplicateMethodSignatureException e) { e.printStackTrace(); } JavadocItem javadoc = method.getJavadoc(); if (javadoc != null) { newMethod.setDescription(javadoc.getComment()); for (ParsedJavadocItem tag : javadoc.getTagsByName("doc")) { if (logger.isLoggable(Level.FINE)) { logger.fine("Handle DOC tag " + tag.getParameterName() + " with " + tag.getParameterValue()); } newMethod.setSpecificDescriptionsForKey(tag.getParameterValue(), tag.getParameterName()); } for (ParsedJavadocItem tag : javadoc.getTagsByName("param")) { if (logger.isLoggable(Level.FINE)) { logger.fine("Handle PARAM tag " + tag.getParameterName() + " with " + tag.getParameterValue()); } DMMethodParameter param = newMethod.getDMParameter(tag.getParameterName()); if (param != null) { if (!tag.getParameterValue().equals(tag.getParameterName())) { param.setDescription(tag.getParameterValue()); } else { param.setDescription(null); } } } } return newMethod; } /** * Build a new DMProperty * * @throws CrossReferencedEntitiesException */ static DMProperty makeProperty(FJPJavaClass aClass, String propertyName, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean includesGetOnlyProperties, boolean importReferencedEntities, Vector<String> excludedSignatures) throws FJPTypeResolver.CrossReferencedEntitiesException { if (logger.isLoggable(Level.FINE)) { logger.fine("Looking for " + propertyName); } FJPJavaMethod method = searchMatchingGetMethod(aClass, propertyName); if (method != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Found method " + method); } return makeProperty(method, dataModel, context, source, includesGetOnlyProperties, importReferencedEntities, excludedSignatures); } if (logger.isLoggable(Level.FINE)) { logger.fine("No method found, looking for field "); } FJPJavaField field = aClass.getFieldByName(propertyName); if (field == null) { field = aClass.getFieldByName("_" + propertyName); } if (field != null) { return makeProperty(field, dataModel, context, source, importReferencedEntities); } return null; } /** * Build a new DMProperty * * @throws CrossReferencedEntitiesException */ static DMProperty makeProperty(FJPJavaMethod method, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean includesGetOnlyProperties, boolean importReferencedEntities, Vector<String> excludedSignatures) throws FJPTypeResolver.CrossReferencedEntitiesException { if (logger.isLoggable(Level.FINE)) { logger.fine("Analyse method " + method.getCallSignature() + " is it a property ?"); } FJPJavaClass parentClass = method.getParentClass(); DMType returnType = method.getReturns(); if (returnType == null) { // This is a constructor, ignore it and return null return null; } Vector<FJPJavaParameter> parameters = method.getParameters(); if (!returnType.isVoid() && method.isPublic() && !method.isStatic() && parameters.size() == 0) { // This signature matches a GET property, lets continue ! // Look for name String propertyName = method.getName(); // Exclude it from methods if (excludedSignatures != null) { excludedSignatures.add(method.getCallSignature()); } // Beautify property name if (propertyName.length() > 3 && propertyName.substring(0, 3).equalsIgnoreCase("get")) { propertyName = propertyName.substring(3); } if (propertyName.length() > 1 && propertyName.substring(0, 1).equals("_")) { propertyName = propertyName.substring(1); } // First char always to lower case propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1, propertyName.length()); // Is there a SET method ? FJPJavaMethod setMethod = searchMatchingSetMethod(parentClass, propertyName, returnType); boolean isSettable = setMethod != null; if (setMethod != null && excludedSignatures != null) { excludedSignatures.add(setMethod.getCallSignature()); } // OK, we have the name, let's look at the cardinality DMCardinality cardinality = getCardinality(returnType); DMType propertyType = null; DMType keyType = null; FJPJavaMethod additionMethod = null; FJPJavaMethod removalMethod = null; if (cardinality == DMCardinality.VECTOR) { TreeSet<AccessorMethod> addToMethods = searchMatchingAddToMethods(parentClass, propertyName); TreeSet<AccessorMethod> removeFromMethods = searchMatchingRemoveFromMethods(parentClass, propertyName); for (Iterator it = addToMethods.iterator(); it.hasNext();) { AccessorMethod next = (AccessorMethod) it.next(); if (excludedSignatures != null) { excludedSignatures.add(next.getMethod().getCallSignature()); } if (propertyType == null) { propertyType = next.getMethod().getParameters().firstElement().getType(); } else if (!propertyType.equals(next.getMethod().getParameters().firstElement().getType())) { isSettable = false; } } for (Iterator it = removeFromMethods.iterator(); it.hasNext();) { AccessorMethod next = (AccessorMethod) it.next(); if (excludedSignatures != null) { excludedSignatures.add(next.getMethod().getCallSignature()); } if (!propertyType.equals(next.getMethod().getParameters().firstElement().getType())) { isSettable = false; } } if (addToMethods.size() == 0 || removeFromMethods.size() == 0) { isSettable = false; } if (addToMethods.size() > 0) { additionMethod = addToMethods.first().method; } if (removeFromMethods.size() > 0) { removalMethod = removeFromMethods.first().method; } returnType = propertyType; if (returnType == null) { returnType = DMType.makeResolvedDMType(dataModel.getDMEntity(Object.class)); } } if (cardinality == DMCardinality.HASHTABLE) { TreeSet<AccessorMethod> setMethods = searchMatchingSetForKeyMethods(parentClass, propertyName); TreeSet<AccessorMethod> removeMethods = searchMatchingRemoveWithKeyMethods(parentClass, propertyName); for (Iterator it = setMethods.iterator(); it.hasNext();) { AccessorMethod next = (AccessorMethod) it.next(); if (excludedSignatures != null) { excludedSignatures.add(next.getMethod().getCallSignature()); } if (propertyType == null) { propertyType = next.getMethod().getParameters().firstElement().getType(); } else if (!propertyType.equals(next.getMethod().getParameters().firstElement().getType())) { isSettable = false; } if (keyType == null) { keyType = next.getMethod().getParameters().elementAt(1).getType(); } logger.info("Method " + next.getMethod().getCallSignature() + " ketType=" + keyType); } for (Iterator it = removeMethods.iterator(); it.hasNext();) { AccessorMethod next = (AccessorMethod) it.next(); if (excludedSignatures != null) { excludedSignatures.add(next.getMethod().getCallSignature()); } } if (setMethods.size() == 0 || removeMethods.size() == 0) { isSettable = false; } if (setMethods.size() > 0) { additionMethod = setMethods.first().method; } if (removeMethods.size() > 0) { removalMethod = removeMethods.first().method; } returnType = propertyType; if (returnType == null) { returnType = DMType.makeResolvedDMType(dataModel.getDMEntity(Object.class)); } } // Creates and register the property if (includesGetOnlyProperties || isSettable) { if (FJPTypeResolver.isResolvable(returnType, dataModel, context, source)) { FJPTypeResolver.resolveEntity(returnType, dataModel, context, source, importReferencedEntities); } // logger.info("Make new property "+propertyName+" type="+returnType+" cardinality="+cardinality+" isSettable="+isSettable); DMProperty newProperty = new DMProperty(dataModel, propertyName, returnType, cardinality, false, isSettable, DMPropertyImplementationType.PUBLIC_ACCESSORS_ONLY); newProperty.preventFromModifiedPropagation(); if (cardinality == DMCardinality.HASHTABLE) { if (keyType != null && FJPTypeResolver.isResolvable(keyType, dataModel, context, source)) { FJPTypeResolver.resolveEntity(keyType, dataModel, context, source, importReferencedEntities); } newProperty.setKeyType(keyType, true); } try { newProperty.getGetterSourceCode().setCode( (method.getJavadoc() != null ? method.getJavadoc().toString() : "") + method.getSourceCode(), false); if (setMethod != null) { newProperty.getSetterSourceCode().setCode( (setMethod.getJavadoc() != null ? setMethod.getJavadoc().toString() : "") + setMethod.getSourceCode(), false); } if (additionMethod != null) { newProperty.getAdditionSourceCode().setCode( (additionMethod.getJavadoc() != null ? additionMethod.getJavadoc().toString() : "") + additionMethod.getSourceCode(), false); } if (removalMethod != null) { newProperty.getRemovalSourceCode().setCode( (removalMethod.getJavadoc() != null ? removalMethod.getJavadoc().toString() : "") + removalMethod.getSourceCode(), false); } } catch (ParserNotInstalledException e) { e.printStackTrace(); } catch (DuplicateMethodSignatureException e) { e.printStackTrace(); } if (isSettable) { if (setMethod != null && setMethod.getParameters() != null && setMethod.getParameters().size() == 1) { newProperty.setSetterParamName(setMethod.getParameters().firstElement().getName()); } } if (cardinality.isMultiple()) { if (additionMethod != null && additionMethod.getParameters() != null && additionMethod.getParameters().size() == 1) { newProperty.setAdditionAccessorParamName(additionMethod.getParameters().firstElement().getName()); } if (removalMethod != null && removalMethod.getParameters() != null && removalMethod.getParameters().size() == 1) { newProperty.setRemovalAccessorParamName(removalMethod.getParameters().firstElement().getName()); } } JavadocItem javadoc = method.getJavadoc(); if (javadoc != null) { newProperty.setDescription(javadoc.getComment()); for (ParsedJavadocItem tag : javadoc.getTagsByName("doc")) { if (logger.isLoggable(Level.FINE)) { logger.fine("Handle DOC tag " + tag.getParameterName() + " with " + tag.getParameterValue()); } newProperty.setSpecificDescriptionsForKey(tag.getParameterValue(), tag.getParameterName()); } } return newProperty; } } return null; } /** * Build a new DMProperty * * @throws CrossReferencedEntitiesException */ static DMProperty makeProperty(FJPJavaField field, DMModel dataModel, FJPDMSet context, FJPJavaSource source, boolean importReferencedEntities) throws FJPTypeResolver.CrossReferencedEntitiesException { DMType fieldType = field.getType(); if (!fieldType.isVoid() && field.isPublic() && !field.isStatic()) { // This signature matches a GET property, lets continue ! // Look for name String propertyName = field.getName(); // OK, we have the name, let's look at the cardinality DMCardinality cardinality = getCardinality(fieldType); // Lookup return type // DMEntity typeEntity = null; if (FJPTypeResolver.isResolvable(fieldType, dataModel, context, source)) { /*typeEntity =*/FJPTypeResolver.resolveEntity(fieldType, dataModel, context, source, importReferencedEntities); } DMProperty newProperty = new DMProperty(dataModel, propertyName, fieldType, cardinality, false, true, DMPropertyImplementationType.PUBLIC_FIELD); newProperty.preventFromModifiedPropagation(); /*if (typeEntity == null) { newProperty.setUnresolvedTypeName(fieldType.getValue()); }*/ return newProperty; } return null; } /** * Try to find a method matching supplied signature * * Returns corresponding method, null if no such method exist */ private static FJPJavaMethod searchMatchingMethod(FJPJavaClass aClass, String signature, DMModel dataModel, FJPDMSet context, FJPJavaSource source) { if (logger.isLoggable(Level.FINE)) { logger.fine("Searching " + signature); } for (FJPJavaMethod method : aClass.getMethods()) { if (logger.isLoggable(Level.FINE)) { logger.fine("Try " + getFullQualifiedCallSignature(method, dataModel, context, source)); } if (getFullQualifiedCallSignature(method, dataModel, context, source).equals(signature)) { return method; } } return null; } private static String getFullQualifiedCallSignature(FJPJavaMethod method, DMModel dataModel, FJPDMSet context, FJPJavaSource source) { String returned = method.getName() + "(" + getParametersTypesAsString(method, dataModel, context, source) + ")"; return returned; } private static String getParametersTypesAsString(FJPJavaMethod method, DMModel dataModel, FJPDMSet context, FJPJavaSource source) { boolean isFirst = true; StringBuffer sb = new StringBuffer(); for (FJPJavaParameter p : method.getParameters()) { DMType t = p.getType(); /*String typeName = t.toString(); try { DMEntity resolvedEntity = FJPTypeResolver.resolveEntity(t,dataModel,context,source,false); if (resolvedEntity != null) typeName = resolvedEntity.getFullQualifiedName(); } catch (FJPTypeResolver.CrossReferencedEntitiesException e) { e.printStackTrace(); } sb.append((isFirst?"":",")+typeName+(p.isVarArgs()?"...":"")); */ try { FJPTypeResolver.resolveEntity(t, dataModel, context, source, false); } catch (CrossReferencedEntitiesException e) { e.printStackTrace(); } String typeName = t.getStringRepresentation() + (p.isVarArgs() ? "..." : ""); sb.append((isFirst ? "" : ",") + typeName); isFirst = false; } return sb.toString(); } /** * Try to find a matching "get" method, such as (in order): * <ul> * <li>propertyName()</li> * <li>_propertyName()</li> * <li>getPropertyName()</li> * <li>_getPropertyName()</li> * </ul> * Returns corresponding method, null if no such method exist */ private static FJPJavaMethod searchMatchingGetMethod(FJPJavaClass aClass, String propertyName) { String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); Vector<String> tries = new Vector<String>(); tries.add(propertyName + "()"); tries.add("_" + propertyName + "()"); tries.add("get" + propertyNameWithFirstCharToUpperCase + "()"); tries.add("_get" + propertyNameWithFirstCharToUpperCase + "()"); for (String tryThis : tries) { FJPJavaMethod returned = aClass.getMethodBySignature(tryThis); if (returned != null) { return returned; } } return null; } /** * Try to find a matching "set" method, such as (in order): * <ul> * <li>setPropertyName(Type)</li> * <li>_setPropertyName(Type)</li> * </ul> * Returns corresponding method, null if no such method exist */ private static FJPJavaMethod searchMatchingSetMethod(FJPJavaClass aClass, String propertyName, DMType aType) { String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); Vector<String> tries = new Vector<String>(); tries.add("set" + propertyNameWithFirstCharToUpperCase); tries.add("_set" + propertyNameWithFirstCharToUpperCase); for (String tryThis : tries) { FJPJavaMethod returned = aClass.getMethodBySignature(tryThis, aType); if (returned != null) { return returned; } } // Little hask to handle properly NSArray as VECTOR-cardinality properties // TODO: implement this better later if (aType.getStringRepresentation().equals("com.webobjects.foundation.NSArray")) { return searchMatchingSetMethod(aClass, propertyName, DMType.makeUnresolvedDMType("com.webobjects.foundation.NSMutableArray")); } return null; } /** * Search and return matching "setForKey" methods<br> * NB: 'setForKey...' methods are methods with general form: <code>setXXXForKey(Class anObject, Class aKey)</code> or * <code>_setXXXForKey(Class anObject, Class aKey)</code>, where XXX is the property name (try with or without a terminal 's' * character), and Class could be anything... Returns an ordered TreeSet of {@link AccessorMethod} objects */ private static TreeSet<AccessorMethod> searchMatchingSetForKeyMethods(FJPJavaClass aClass, String propertyName) { String singularPropertyName; String pluralPropertyName; if (propertyName.endsWith("ies")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 3) + "y"; pluralPropertyName = propertyName; } else if (propertyName.endsWith("s") || propertyName.endsWith("S")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 1); pluralPropertyName = propertyName; } else { singularPropertyName = propertyName; pluralPropertyName = propertyName + "s"; } // end of else String[] methodNameCondidates = new String[4]; methodNameCondidates[0] = "set" + singularPropertyName + "ForKey"; methodNameCondidates[1] = "_set" + singularPropertyName + "ForKey"; methodNameCondidates[2] = "set" + pluralPropertyName + "ForKey"; methodNameCondidates[3] = "_set" + pluralPropertyName + "ForKey"; return searchMethodsWithNameAndParamsNumber(aClass, methodNameCondidates, 2); } /** * Search and return matching "removeWithKey" methods<br> * NB: 'removeWithKey...' methods are methods with general form: <code>removeXXXWithKey(Class aKey)</code> or * <code>_removeXXXWithKey(Class aKey)</code>, where XXX is the property name (try with or without a terminal 's' character), and Class * could be anything... Returns an ordered TreeSet of {@link AccessorMethod} objects */ private static TreeSet<AccessorMethod> searchMatchingRemoveWithKeyMethods(FJPJavaClass aClass, String propertyName) { String singularPropertyName; String pluralPropertyName; if (propertyName.endsWith("ies")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 3) + "y"; pluralPropertyName = propertyName; } else if (propertyName.endsWith("s") || propertyName.endsWith("S")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 1); pluralPropertyName = propertyName; } else { singularPropertyName = propertyName; pluralPropertyName = propertyName + "s"; } // end of else String[] methodNameCondidates = new String[4]; methodNameCondidates[0] = "remove" + singularPropertyName + "WithKey"; methodNameCondidates[1] = "_remove" + singularPropertyName + "WithKey"; methodNameCondidates[2] = "remove" + pluralPropertyName + "WithKey"; methodNameCondidates[3] = "_remove" + pluralPropertyName + "WithKey"; return searchMethodsWithNameAndParamsNumber(aClass, methodNameCondidates, 1); } /** * Search and return matching "addTo" methods<br> * NB: 'addTo...' methods are methods with general form: <code>addToXXX(Class anObject)</code> or <code>_addToXXX(Class anObject)</code> * , where XXX is the property name (try with or without a terminal 's' character), and Class could be anything... Returns an ordered * TreeSet of {@link AccessorMethod} objects */ private static TreeSet<AccessorMethod> searchMatchingAddToMethods(FJPJavaClass aClass, String propertyName) { String singularPropertyName; String pluralPropertyName; if (propertyName.endsWith("s") || propertyName.endsWith("S")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 1); pluralPropertyName = propertyName; } else { singularPropertyName = propertyName; pluralPropertyName = propertyName + "s"; } // end of else String[] methodNameCondidates = new String[4]; methodNameCondidates[0] = "addTo" + singularPropertyName; methodNameCondidates[1] = "_addTo" + singularPropertyName; methodNameCondidates[2] = "addTo" + pluralPropertyName; methodNameCondidates[3] = "_addTo" + pluralPropertyName; return searchMethodsWithNameAndParamsNumber(aClass, methodNameCondidates, 1); } /** * Search and return matching "removeFrom" methods<br> * NB: 'removeFrom...' methods are methods with general form: <code>removeFromXXX(Class anObject)</code> or * <code>_removeFromXXX(Class anObject)</code>, where XXX is the property name (try with or without a terminal 's' character), and Class * could be anything... Returns an ordered TreeSet of {@link AccessorMethod} objects */ private static TreeSet<AccessorMethod> searchMatchingRemoveFromMethods(FJPJavaClass aClass, String propertyName) { String singularPropertyName; String pluralPropertyName; if (propertyName.endsWith("s") || propertyName.endsWith("S")) { singularPropertyName = propertyName.substring(0, propertyName.length() - 1); pluralPropertyName = propertyName; } else { singularPropertyName = propertyName; pluralPropertyName = propertyName + "s"; } // end of else String[] methodNameCondidates = new String[4]; methodNameCondidates[0] = "removeFrom" + singularPropertyName; methodNameCondidates[1] = "_removeFrom" + singularPropertyName; methodNameCondidates[2] = "removeFrom" + pluralPropertyName; methodNameCondidates[3] = "_removeFrom" + pluralPropertyName; return searchMethodsWithNameAndParamsNumber(aClass, methodNameCondidates, 1); } /** * Search and returns all methods (as {@link AccessorMethod} objects) of related class whose names is in the specified string list, with * exactly the specified number of parameters, ascendant ordered regarding parameters specialization. * * @see AccessorMethod */ private static TreeSet<AccessorMethod> searchMethodsWithNameAndParamsNumber(FJPJavaClass aClass, String[] searchedNames, int paramNumber) { TreeSet<AccessorMethod> returnedTreeSet = new TreeSet<AccessorMethod>(); FJPJavaMethod[] allMethods = aClass.getMethods(); for (int i = 0; i < allMethods.length; i++) { FJPJavaMethod tempMethod = allMethods[i]; for (int j = 0; j < searchedNames.length; j++) { if (tempMethod.getName().equalsIgnoreCase(searchedNames[j]) && tempMethod.getParameters().size() == paramNumber) { // This is a good candidate returnedTreeSet.add(new AccessorMethod(tempMethod)); } } } return returnedTreeSet; } private static DMCardinality getCardinality(Type aType) { // Little hask to handle properly NSArray as VECTOR-cardinality properties // TODO: implement this better later Type NSARRAY_TYPE = new Type("NSArray"); if (aType.isA(NSARRAY_TYPE)) { return DMCardinality.VECTOR; } Type NSARRAY_TYPE_FQ = new Type("com.webobjects.foundation.NSArray"); if (aType.isA(NSARRAY_TYPE_FQ)) { return DMCardinality.VECTOR; } Type VECTOR_TYPE = new Type(java.util.Vector.class.getCanonicalName()); if (aType.isA(VECTOR_TYPE)) { return DMCardinality.VECTOR; } Type HASHTABLE_TYPE = new Type(java.util.Hashtable.class.getCanonicalName()); if (aType.isA(HASHTABLE_TYPE)) { return DMCardinality.HASHTABLE; } return DMCardinality.SINGLE; } /** * <p> * <code>AccessorMethod</code> is a class representing a KeyValueProperty accessor method. * </p> * <p> * Because many differents accessors could be defined in a class, all implementing different class-specific levels (more or less * specialized, regarding parameters classes), we store these <code>AccessorMethods</code> in a particular order depending on the * parameters specialization. This order is implemented in this class through {@link Comparable} interface implementation. Note: this * class has a natural ordering that is inconsistent with equals, which means that <code>(x.compareTo(y)==0) == (x.equals(y))</code> * condition is violated. */ private static class AccessorMethod implements Comparable { /** Stores the related <code>Method</code> */ protected FJPJavaMethod method; /** * Creates a new <code>AccessorMethod</code> instance. * * @param aKeyValueProperty * a <code>KeyValueProperty</code> value * @param aMethod * a <code>Method</code> value */ public AccessorMethod(FJPJavaMethod aMethod) { super(); method = aMethod; } /** * Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. * * @param object * an <code>Object</code> value * @return an <code>int</code> value * @exception ClassCastException * if an error occurs */ @Override public int compareTo(Object object) throws ClassCastException { if (object instanceof AccessorMethod) { AccessorMethod comparedAccessorMethod = (AccessorMethod) object; if (getMethod().getParameters().size() != comparedAccessorMethod.getMethod().getParameters().size()) { // Those objects could not be compared and should be treated // as equals // regarding the specialization of their parameters return 2; } else { for (int i = 0; i < getMethod().getParameters().size(); i++) { Type localParameterType = getMethod().getParameters().get(i).getType(); Type comparedParameterType = comparedAccessorMethod.getMethod().getParameters().get(i).getType(); if (!localParameterType.equals(comparedParameterType)) { boolean localParamIsParentOfComparedParam = comparedParameterType.isA(localParameterType); boolean localParamIsChildOfComparedParam = localParameterType.isA(comparedParameterType); if (localParamIsParentOfComparedParam) { return 1; } if (localParamIsChildOfComparedParam) { return -1; } // Those objects could not be compared return 2; } } // end of for // Those objects are equals regarding the specialization of // their parameters return 0; } } else { throw new ClassCastException(); } } /** * Return the related <code>Method</code> * * @return */ public FJPJavaMethod getMethod() { return method; } } }