Java tutorial
/******************************************************************************* * Copyright (c) 2001-2014 Yann-Gal Guhneuc and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * Yann-Gal Guhneuc and others, see in file; API and its implementation ******************************************************************************/ package padl.creator.classfile.relationship; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; import org.apache.commons.lang.ArrayUtils; import com.ibm.toad.cfparse.attributes.AttrInfoList; import com.ibm.toad.cfparse.attributes.CodeAttrInfo; import com.ibm.toad.cfparse.instruction.ImmutableCodeSegment; import com.ibm.toad.cfparse.instruction.JVMConstants; import com.ibm.toad.cfparse.utils.Access; import padl.creator.classfile.util.ExtendedMethodInfo; import padl.creator.classfile.util.ExtendedMethodInvocation; import padl.creator.classfile.util.Utils; import padl.kernel.Constants; import padl.kernel.IClass; import padl.kernel.ICodeLevelModel; import padl.kernel.IConstructor; import padl.kernel.IElement; import padl.kernel.IEntity; import padl.kernel.IField; import padl.kernel.IFirstClassEntity; import padl.kernel.IGhost; import padl.kernel.IMethod; import padl.kernel.IMethodInvocation; import padl.kernel.IOperation; import padl.kernel.IParameter; import padl.kernel.IRelationship; import padl.kernel.impl.Factory; import padl.util.Util; import util.io.ProxyConsole; import util.lang.Modifier; /** * @author Yann-Gal Guhneuc * @since 2004/08/01 */ public class RelationshipAnalyzer { private static final char[] EQUAL_SIGN = "=".toCharArray(); private static IMethod ASSIGNMENT_METHOD; private static Map MapOfIDsEntities; private static void addRelationship(final IFirstClassEntity anEntity, final IRelationship aRelationship) { // Yann 2004/08/07: Duplicates! // When adding relationships, I must check for duplicate // according to their type (use, association...), their // target entity, their visibility, and their multiplicity. // However, this check may dramatically decrease performance! // TODO: Improve performance by stocking relationships in // a hashtable or something... final Iterator iterator = anEntity.getIteratorOnConstituents(); IRelationship duplicateRelationship = null; while (iterator.hasNext() && duplicateRelationship == null) { final IElement element = (IElement) iterator.next(); if (element.getClass().equals(aRelationship.getClass())) { final IRelationship relationship = (IRelationship) element; if (relationship.getTargetEntity().equals(aRelationship.getTargetEntity()) && relationship.getCardinality() == aRelationship.getCardinality() && relationship.getVisibility() == aRelationship.getVisibility()) { duplicateRelationship = relationship; } } } // Yann 2005/07/11: Duplicate relationships // If a relationship is duplicated (existing because of two // different piece of code), I concatenate their names. if (duplicateRelationship == null) { anEntity.addConstituent(aRelationship); } else { final char[] newName = new char[duplicateRelationship.getName().length + 1 + aRelationship.getName().length]; System.arraycopy(duplicateRelationship.getName(), 0, newName, 0, duplicateRelationship.getName().length); newName[duplicateRelationship.getName().length] = '+'; System.arraycopy(aRelationship.getName(), 0, newName, duplicateRelationship.getName().length + 1, aRelationship.getName().length); duplicateRelationship.setName(newName); } } private static int computeMethodInvocationTypeDirect(final IOperation currentMethod, final IOperation calledMethod) { // Yann 2004/07/31: Type. // I compute the type and cardinality // of the method invocation. if (currentMethod.isStatic()) { if (calledMethod.isStatic()) { return IMethodInvocation.CLASS_CLASS; } else { return IMethodInvocation.CLASS_INSTANCE; } } else { // Yann 2010/01/20: Missing method // If there is a discrepancy between the method called // on a class and those provided by the class, it is // possible that the calledMethod be null... and I // assume that it is a instance method, by default. if (calledMethod != null && calledMethod.isStatic()) { return IMethodInvocation.INSTANCE_CLASS; } else { return IMethodInvocation.INSTANCE_INSTANCE; } } } private static int computeMethodInvocationTypeThroughField(final IField receivedField, final IOperation calledMethod) { // Yann 2004/07/31: Type. // I compute the type and cardinality // of the method invocation. if (receivedField.isStatic()) { if (calledMethod.isStatic()) { return IMethodInvocation.CLASS_CLASS_FROM_FIELD; } else { return IMethodInvocation.CLASS_INSTANCE_FROM_FIELD; } } else { // Yann 2010/01/20: Missing method // If there is a discrepancy between the method called // on a class and those provided by the class, it is // possible that the calledMethod be null... and I // assume that it is a instance method, by default. if (calledMethod != null && calledMethod.isStatic()) { return IMethodInvocation.INSTANCE_CLASS_FROM_FIELD; } else { return IMethodInvocation.INSTANCE_INSTANCE_FROM_FIELD; } } } private static char[] computeSignature(final String completeNameMethod) { String methodName = completeNameMethod.substring(completeNameMethod.indexOf(' ') + 1); methodName = methodName.substring(0, methodName.indexOf('.')); final String localArgs = completeNameMethod.substring(completeNameMethod.indexOf('('), completeNameMethod.indexOf(')') + 1); return (methodName + localArgs).toCharArray(); } private static String extractFieldName(final String aFieldInfo) { final String result = aFieldInfo.substring(aFieldInfo.indexOf(' ') + 1); // Yann 2007/01/24: Bug in the givn fieldInfo // When analysing the relationships in Java AWT v1.4.2_04 // at one point, the fieldInfo is actually a method // descriptor (erroneously!): // "getDefaultComponent" "(Ljava/awt/Container;)Ljava/awt/Component;" // Thus, the need of the following test: if (result.indexOf('.') > -1) { return result.substring(0, result.indexOf('.')); } else { return ""; } } private static String extractFieldType(final String completeNameField) { String fieldType = completeNameField.substring(completeNameField.indexOf(" ") + 1); fieldType = fieldType.substring(fieldType.indexOf(".") + 1); return fieldType; } private static char[] extractReturnType(final String completeNameMethod) { String returnType = completeNameMethod.substring(completeNameMethod.indexOf(" ") + 1); returnType = returnType.substring(returnType.indexOf(".") + 1, returnType.indexOf(" ")); return returnType.toCharArray(); } public static void initialise(final Map aMapOfIDsEntities) { RelationshipAnalyzer.MapOfIDsEntities = aMapOfIDsEntities; } public static List recogniseRelationship(final List aListOfSubmittedConstituents, final ICodeLevelModel aCodeLevelModel, final boolean storeMethodInvocation) { final RelationshipAnalyzer analyzer = new RelationshipAnalyzer(aListOfSubmittedConstituents, aCodeLevelModel, storeMethodInvocation); // Yann 2004/08/02: Explanations. // First, I build the method invocations for all methods // in the idiom-level model. analyzer.addMethodInvocations(); // Then, I create relationships according to the // method invocations created. // Yann 2007/10/30: AACRelationshipAnalyse! // The building of the relationships is now // externalised in a real analysis. if (Utils.ENABLE_BUILT_IN_AAC_ANALYSIS) { analyzer.createRelationships(); } return aListOfSubmittedConstituents; } // TODO: This list should be a set. private final ICodeLevelModel codeLevelModel; private final LightMethodInvocationAnalyzer lightMethodInvocationAnalyzer; private final List listOfMethodInvocations; private final List listOfSubmittedConstituents; private final boolean storeMethodInvocation; private RelationshipAnalyzer(final List aListOfSubmittedConstituents, final ICodeLevelModel aCodeLevelModel, final boolean storeMethodInvocation) { this.listOfSubmittedConstituents = aListOfSubmittedConstituents; this.codeLevelModel = aCodeLevelModel; this.storeMethodInvocation = storeMethodInvocation; this.lightMethodInvocationAnalyzer = new LightMethodInvocationAnalyzer(this.codeLevelModel.getFactory(), this.codeLevelModel); this.listOfMethodInvocations = new ArrayList(); } private void addMethodInvocations() { // Yann 2004/06/02: Diry hack! // See the construction of method invocations... if (RelationshipAnalyzer.ASSIGNMENT_METHOD == null) { RelationshipAnalyzer.ASSIGNMENT_METHOD = this.codeLevelModel.getFactory() .createMethod(RelationshipAnalyzer.EQUAL_SIGN, RelationshipAnalyzer.EQUAL_SIGN); // Yann 2007/11/13: Clone! // I should not forget to prepare a possible clone // of this "method" in case I clone a model in // which it is used... BAD BAD UGLY CODE!!! RelationshipAnalyzer.ASSIGNMENT_METHOD.startCloneSession(); RelationshipAnalyzer.ASSIGNMENT_METHOD.performCloneSession(); } // A method submitted is a Use if inside the // method body there are some methods calls or some fields // access (at least one) or some instance creation. // A method is also a Use when it returns an // object, not a primitive type. // Well, all methods (almost) are PLinkingMethods. final Iterator iterator = this.listOfSubmittedConstituents.iterator(); while (iterator.hasNext()) { final Object member = iterator.next(); if (member instanceof ExtendedMethodInfo) { final ExtendedMethodInfo methodInfo = (ExtendedMethodInfo) member; final IFirstClassEntity currentEntity = Utils.getEntityOrCreateGhost(this.codeLevelModel, methodInfo.getDeclaringClassName(), RelationshipAnalyzer.MapOfIDsEntities); // final long startTime = System.currentTimeMillis(); this.listOfMethodInvocations.addAll(this.analyseLightMethodInvocations(currentEntity, methodInfo)); // OutputManager.getCurrentOutputManager().getOutput().println(System.currentTimeMillis() - startTime); // TODO: This is not the best place to call computeMethodInvocations(), // this method should be called within the recognizeMethod() method. // Yann 2004/03/25: Hack! // There seems to be some bugs in the computation // of method invocations. I surround the method call // with a catch block to prevent the failure of tests. // Even though, some tests still fail and also an // infinite loop occurs in // MethodInvocationAnalyer.createCouples(...). // Also, the name of method added to Ghost should not // include parentheses: // public void <init>()() // -> // public void <init>() // Yann 2004/08/02: Comment. // The previous comment is no longer valid. // Yann 2004/07/31: Message types. // I know add the method invocations created during the // analysis of method bodies to create more relationships // if appropriate. // Yann 2004/07/30: Object-orientation. // The flag "storeMethodInvocation" is *not* object-oriented // programming. However, it helps in increasing performances // by avoiding to recompute many variables to compute then // method invocations. this.listOfMethodInvocations.addAll(this.analyseDeepMethodInvocations(currentEntity, methodInfo)); } } } private List analyseDeepMethodInvocations(final IFirstClassEntity currentEntity, final ExtendedMethodInfo methodInfo) { final IOperation currentMethod = (IOperation) currentEntity .getConstituentFromID(Utils.computeSignature(methodInfo)); final DeepMethodInvocationAnalyzer analyzer = DeepMethodInvocationAnalyzer.getUniqueInstance(); final List couplesOfUsedAttributes = analyzer.analyzeMethod(methodInfo); final List methodInvocations = new ArrayList(); boolean foundArrayStaticOrNot = false; Vector foundFieldStaticOrNot = null; IFirstClassEntity foundEntityDeclaringField = null; boolean foundArrayLoad = false; final Iterator iterAttributes = couplesOfUsedAttributes.iterator(); while (iterAttributes.hasNext()) { final String[] nextCouple = (String[]) iterAttributes.next(); final boolean isFieldEmpty = Utils.isMemberNameEmptyOrEqual(nextCouple[0]); final boolean isMethodEmpty = Utils.isMemberNameEmpty(nextCouple[1]); final IFirstClassEntity entityDeclaringField; final IFirstClassEntity entityDeclaringMethod; final Vector callingFields; final IOperation calledMethod; ExtendedMethodInvocation methodInvocation = null; // Yann 2012/02/02: Dealing with static arrays! // The idea is to remember if a static array is // used just before an instance method invocation, // then a dedicated IMetodInvocation is created. if (nextCouple[1] != null && nextCouple[1].endsWith(":" + JVMConstants.AALOAD)) { foundArrayLoad = true; } if (!isFieldEmpty && !isMethodEmpty && !Utils.isAnonymousOrLocalEntity( nextCouple[0].substring(0, nextCouple[0].indexOf(" ")).toCharArray())) { callingFields = this.createFieldsFromFieldInfos(nextCouple[0]); // Yann 2004/06/02: Field assignment! // I want to know when a field is assigned to // distinguish data type from ordanary class. if (nextCouple[1].equals(String.valueOf(RelationshipAnalyzer.EQUAL_SIGN))) { calledMethod = RelationshipAnalyzer.ASSIGNMENT_METHOD; } else { calledMethod = this.createOperation(nextCouple[1]); } entityDeclaringField = Utils.getEntityOrCreateGhost(this.codeLevelModel, nextCouple[0].substring(0, nextCouple[0].indexOf(" ")).toCharArray(), RelationshipAnalyzer.MapOfIDsEntities); if (nextCouple[1].equals(String.valueOf(RelationshipAnalyzer.EQUAL_SIGN))) { entityDeclaringMethod = null; } else { final String fieldNameAndType = nextCouple[0].substring(nextCouple[0].indexOf(" ") + 1); String fieldType = fieldNameAndType.substring(fieldNameAndType.indexOf('.') + 1); final int hashCharPos = fieldType.indexOf('#'); if (hashCharPos > 0) { fieldType = fieldType.substring(0, hashCharPos); } entityDeclaringMethod = Utils.getEntityOrCreateGhost(this.codeLevelModel, fieldType.toCharArray(), RelationshipAnalyzer.MapOfIDsEntities); } methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), currentEntity, currentMethod, // Yann 2012/02/02: Not sure! // I believe that we should use the commented code // instead of the uncommented code. However, the // commented code leads to the creation of more // aggregation relationships than expected, see // for example the test // padl.creator.test.classfile.example.CreationLink_INSTANCE_CREATION_3 RelationshipAnalyzer.computeMethodInvocationTypeThroughField( (IField) callingFields.lastElement(), calledMethod), ((IField) callingFields.lastElement()).getCardinality(), entityDeclaringMethod, entityDeclaringField); methodInvocation.setCallingField(callingFields); methodInvocation.setCalledMethod(calledMethod); methodInvocations.add(methodInvocation); } else if (!isFieldEmpty && isMethodEmpty && !Utils.isAnonymousOrLocalEntity( nextCouple[0].substring(0, nextCouple[0].indexOf(" ")).toCharArray())) { callingFields = this.createFieldsFromFieldInfos(nextCouple[0]); // Yann 2007/01/24: Unneeded test // The test of the callingFields being null // is only needed because of the bug documented // in method extractFieldName()! if (callingFields.get(0) == null) { break; } entityDeclaringField = Utils.getEntityOrCreateGhost(this.codeLevelModel, nextCouple[0].substring(0, nextCouple[0].indexOf(" ")).toCharArray(), RelationshipAnalyzer.MapOfIDsEntities); // Yann 2012/02/02: Null Object! // The use of the "null" value below is really ugly // and suggest misadapted interface: at least a Null // Object should be used in this code. // TODO: Remove the use of "null" methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), currentEntity, currentMethod, IMethodInvocation.OTHER, ((IField) callingFields.lastElement()).getCardinality(), null, entityDeclaringField); methodInvocation.setCallingField(callingFields); methodInvocations.add(methodInvocation); // Yann 2012/02/02: Dealing with static arrays! // The idea is to remember if a static array is // used just before an instance method invocation, // then a dedicated IMetodInvocation is created. if (((IField) callingFields.lastElement()).getCardinality() == Constants.CARDINALITY_MANY) { foundArrayStaticOrNot = true; foundFieldStaticOrNot = callingFields; foundEntityDeclaringField = entityDeclaringField; } } else if (isFieldEmpty && !isMethodEmpty && !Utils.isAnonymousOrLocalEntity( nextCouple[1].substring(0, nextCouple[1].indexOf(" ")).toCharArray())) { calledMethod = this.createOperation(nextCouple[1]); entityDeclaringMethod = Utils.getEntityOrCreateGhost(this.codeLevelModel, nextCouple[1].substring(0, nextCouple[1].indexOf(" ")).toCharArray(), RelationshipAnalyzer.MapOfIDsEntities); // Yann 2012/03/23: New case! // Sgla found out that in Lucene Core v3.0.3, the callee is // actually "long[]", which leads to entityDeclaringMethod being // null and thus break the code below... if (entityDeclaringMethod != null) { // Yann 2012/02/02: Dealing with static arrays! // The idea is to remember if a static array is // used just before an instance method invocation, // then a dedicated IMetodInvocation is created. if (foundArrayStaticOrNot && foundArrayLoad) { methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), currentEntity, currentMethod, RelationshipAnalyzer.computeMethodInvocationTypeThroughField( (IField) foundFieldStaticOrNot.lastElement(), calledMethod), ((IField) foundFieldStaticOrNot.lastElement()).getCardinality(), entityDeclaringMethod, foundEntityDeclaringField); methodInvocation.setCallingField(foundFieldStaticOrNot); methodInvocation.setCalledMethod(calledMethod); methodInvocations.add(methodInvocation); foundArrayStaticOrNot = false; foundFieldStaticOrNot = null; foundEntityDeclaringField = null; foundArrayLoad = false; } else { // Yann 2012/02/21: Cardinality // Thanks to Aminata, we have now clear test about // method invocations and we unify the created MIs // between the JavaFile Creator and the ClassFile // Creator. From now on, cardinality also matters // in the following... final int cardinality; // I must use getID because I need the package name, not just the simple name. if (Util.isArrayOrCollection(entityDeclaringMethod.getID()) || foundArrayLoad) { cardinality = Constants.CARDINALITY_MANY; } else { cardinality = Constants.CARDINALITY_ONE; } methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), currentEntity, currentMethod, RelationshipAnalyzer.computeMethodInvocationTypeDirect(currentMethod, calledMethod), cardinality, entityDeclaringMethod); methodInvocation.setCalledMethod(calledMethod); methodInvocations.add(methodInvocation); } } } if (this.storeMethodInvocation && methodInvocation != null) { currentMethod.addConstituent(methodInvocation.getMethodInvocation()); } } return methodInvocations; } // private static int computeMethodInvocationType( // final IField lastCallingField, // final IOperation calledMethod) { // // // Aminata and Yann 2011/09/06: Type. // // I compute the type and cardinality // // of the method invocation. // if (lastCallingField.isStatic()) { // if (calledMethod.isStatic()) { // return IMethodInvocation.CLASS_CLASS_FROM_FIELD; // } // else { // return IMethodInvocation.INSTANCE_CLASS_FROM_FIELD; // } // } // else { // if (calledMethod != null && calledMethod.isStatic()) { // return IMethodInvocation.CLASS_INSTANCE_FROM_FIELD; // } // else { // return IMethodInvocation.INSTANCE_INSTANCE_FROM_FIELD; // } // } // } private List analyseLightMethodInvocations(final IFirstClassEntity anEntity, final ExtendedMethodInfo aMethodInfo) { final List methodInvocations = new ArrayList(); // Yann 2004/04/04: Context! // The RelationshipAnalyzer builds its // initial context itself. // final Context context = // new Context( // anEntity, // anAbstractLevelModel, // aMethodInfo, // aConstantPool, // messageTypes); final AttrInfoList attributeInfoList = aMethodInfo.getAttributes(); final CodeAttrInfo codeAttributeInfo = (CodeAttrInfo) attributeInfoList.get("Code"); if (codeAttributeInfo != null) { final byte[] rawCode = codeAttributeInfo.getCode(); final ImmutableCodeSegment immutableCodeSegment = new ImmutableCodeSegment(rawCode); // For each instruction found into the class file. // AbstractState state = DefaultState.getCurrentDefaultState(); this.lightMethodInvocationAnalyzer.initialise(anEntity, aMethodInfo); for (int j = 0; j < immutableCodeSegment.getNumInstructions(); j++) { final int offset = immutableCodeSegment.getOffset(j); this.lightMethodInvocationAnalyzer.handleRequest(offset); // state.handleRequest(context, offset); // state = state.getNextState(); } methodInvocations.addAll(this.lightMethodInvocationAnalyzer.listOfMethodInvocations()); } // Yann 2004/08/02: Question! // How could the enclosing method be null? // In the case aMethodInfo.getName().equals("<init>"), of course! // final String methodName = aMethodInfo.getName(); final IOperation method; // Yann 2008/09/05: Is this code necessary? // It generates a bug in case a member class is named // as its enclosing class because getEntityFromName() will // return the member class instead of the constructor! // Thanks to Alban for discovering this bug! // if (Utils.isSpecialMethod(methodName)) { // method = // (IAbstractMethod) anEntity.getEntityFromName( // Util.computeSimpleName(anEntity.getName())); // } // else { method = (IOperation) anEntity.getConstituentFromID(Utils.computeSignature(aMethodInfo)); // } methodInvocations.addAll(this.analyseParameters(anEntity, method)); methodInvocations.addAll(this.analyseReturnType(anEntity, method)); // Yann 2005/08/05: Method invocations! // I must not forget to add to the current method // its appropriate method invocations, else how could // I reproduce the AAC algorithm elsewhere??? if (this.storeMethodInvocation) { final IOperation currentMethod = (IOperation) anEntity .getConstituentFromID(Utils.computeSignature(aMethodInfo)); final Iterator iterator = methodInvocations.iterator(); while (iterator.hasNext()) { final ExtendedMethodInvocation methodInvocation = (ExtendedMethodInvocation) iterator.next(); currentMethod.addConstituent(methodInvocation.getMethodInvocation()); } } return methodInvocations; } private List analyseParameters(final IFirstClassEntity anOriginEntity, final IOperation aMethod) { // Parameters. final List methodInvocations = new ArrayList(); // Yann 2005/10/12: Iterator! // I have now an iterator able to iterate over a // specified type of constituent of a list. final Iterator iterator = aMethod.getIteratorOnConstituents(IParameter.class); while (iterator.hasNext()) { final IParameter parameter = (IParameter) iterator.next(); if (!padl.util.Util.isPrimtiveType(parameter.getTypeName())) { final IEntity targetEntity = parameter.getType(); if (targetEntity instanceof IFirstClassEntity) { // Yann 2006/02/08: Members! // The getEntityFromID() method takes care of members and ghosts... // // Yann 2002/08/01: Ghost! // // From now on, if an entity cannot be found in the // // list of entities, I create a ghost for it... // if (targetEntity == null) { // try { // targetEntity = // aFactory.createGhost(parameter.getType()); // anAbstractLevelModel.addEntity(targetEntity); // } // catch (final ModelDeclarationException mde) { // } // } final ExtendedMethodInvocation methodInvocation; if (Access.isStatic(aMethod.getVisibility())) { methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), anOriginEntity, aMethod, IMethodInvocation.CLASS_CLASS, parameter.getCardinality(), (IFirstClassEntity) targetEntity); } else { methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), anOriginEntity, aMethod, IMethodInvocation.INSTANCE_CLASS, parameter.getCardinality(), (IFirstClassEntity) targetEntity); } // Yann 2004/04/04: Duplicate! // Now that we record method invocations, // is the duplication test valid still? if (!methodInvocations.contains(methodInvocation)) { methodInvocations.add(methodInvocation); } } } } // Yann 2007/11/14: Method invocations! // I should NOT forget to add the newly // created method invocations to the // current method... Well, actually, this // increases the number of UseRelationships // possibly unduly... and it fails many tests! // iterator = methodInvocations.iterator(); // while (iterator.hasNext()) { // final ExtendedMethodInvocation methodInvocation = // (ExtendedMethodInvocation) iterator.next(); // final IMethodInvocation mi = // methodInvocation.getMethodInvocation(); // try { // if (!aMethod.doesContainConstituentWithID(mi.getID())) { // aMethod.addConstituent(mi); // } // // } // catch (final ModelDeclarationException e) { // e.printStackTrace(); // } // } return methodInvocations; } private List analyseReturnType(final IFirstClassEntity anEntity, final IOperation aMethod) { final List methodInvocations = new ArrayList(); if (aMethod instanceof IMethod) { // Return type. char[] invocationSiteName = ((IMethod) aMethod).getReturnType(); String invocationSiteDisplayName = String.valueOf(invocationSiteName); int cardinality = Constants.CARDINALITY_MANY; int index; if ((index = invocationSiteDisplayName.indexOf('[')) == -1) { cardinality = Constants.CARDINALITY_ONE; index = invocationSiteDisplayName.length(); } invocationSiteDisplayName = invocationSiteDisplayName.substring(0, index); invocationSiteName = invocationSiteDisplayName.toCharArray(); if (!Util.isPrimtiveType(invocationSiteName)) { final IFirstClassEntity targetEntity = Utils.getEntityOrCreateGhost(this.codeLevelModel, invocationSiteName, RelationshipAnalyzer.MapOfIDsEntities); // Yann 2006/02/08: Members! // The getConstituentFromID() method takes care of members and ghosts... // // Yann 2002/08/01: Ghost! // // From now on, if an entity cannot be found in the // // list of entities, I create a ghost for it... // if (targetEntity == null) { // try { // targetEntity = // aFactory.createGhost(invocationSiteName); // anAbstractLevelModel.addConstituent(targetEntity); // } // catch (final ModelDeclarationException mde) { // } // } final ExtendedMethodInvocation methodInvocation; if (Access.isStatic(aMethod.getVisibility())) { methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), anEntity, aMethod, IMethodInvocation.CLASS_INSTANCE, cardinality, targetEntity); } else { methodInvocation = new ExtendedMethodInvocation(this.codeLevelModel.getFactory(), anEntity, aMethod, IMethodInvocation.INSTANCE_INSTANCE, cardinality, targetEntity); } // Yann 2004/04/04: Duplicate! // Now that we record method invocations, // is the duplication test valid still? // (See also method Context.addMessageType().) if (!methodInvocations.contains(methodInvocation)) { methodInvocations.add(methodInvocation); } } } // Yann 2007/11/14: Method invocations! // I should NOT forget to add the newly // created method invocations to the // current method... Well, actually, this // increases the number of UseRelationships // possibly unduly... and it fails many tests! // final Iterator iterator = methodInvocations.iterator(); // while (iterator.hasNext()) { // final ExtendedMethodInvocation methodInvocation = // (ExtendedMethodInvocation) iterator.next(); // final IMethodInvocation mi = // methodInvocation.getMethodInvocation(); // try { // if (!aMethod.doesContainConsituentWithID(mi.getID())) { // aMethod.addConstituent(mi); // } // // } // catch (final ModelDeclarationException e) { // e.printStackTrace(); // } // } return methodInvocations; } private IOperation createConstructor(final IFirstClassEntity anOriginEntity, final char[] anID) { final IConstructor constructor = this.codeLevelModel.getFactory().createConstructor(anID, Util.computeSimpleName(anOriginEntity.getName())); // Yann 2004/08/02: Analysis of parameters. // I don't really need to analyse parameters of newly created // methods (in ghosts) because these don't have any parameters. this.listOfMethodInvocations.addAll(this.analyseParameters(anOriginEntity, constructor)); return constructor; } private IOperation createOperation(final String methodData) { // Get the method signature, return type, and declaring class name. final char[] usedMethodName = RelationshipAnalyzer.computeSignature(methodData); final char[] methodClassName = methodData.substring(0, methodData.indexOf(" ")).toCharArray(); final char[] returnType = RelationshipAnalyzer.extractReturnType(methodData); // Get the declaring entity from the abstract level model. // Yann 2008/12/15: Arrays! // There is the case of the clone() method // defined in a java.lang.String[] and // returning as expected a java.lang.Object. // I handle this case by giving back to Ceasar, // what belongs to Ceasar... final IFirstClassEntity entityDeclaringMethod; if (Util.isArray(methodClassName)) { entityDeclaringMethod = Factory.getInstance().createHierarchyRoot(); RelationshipAnalyzer.MapOfIDsEntities.put(methodClassName, entityDeclaringMethod); // Utils.getEntityOrCreateGhost( // this.codeLevelModel, // RelationshipAnalyzer.JAVA_LANG_OBJECT, // RelationshipAnalyzer.MapOfIDsEntities); } else { entityDeclaringMethod = Utils.getEntityOrCreateGhost(this.codeLevelModel, methodClassName, RelationshipAnalyzer.MapOfIDsEntities); } // Yann 2006/02/08: Members! // The searchForEntity() method takes care of members and ghosts... // IEntity entityDeclaringMethod = // (IEntity) (anAbstractLevelModel.getConstituentFromID(methodClassName)); // if (entityDeclaringMethod == null) { // try { // // I create a ghost because the entity is null. // entityDeclaringMethod = aFactory.createGhost(methodClassName); // // // The ghost is added to the abstract level model. // anAbstractLevelModel.addConstituent(entityDeclaringMethod); // } // catch (final ModelDeclarationException e) { // e.printStackTrace( // OutputManager.getCurrentOutputManager().getErrorOutput()); // } // } final IOperation calledMethod = this.searchForMethod(usedMethodName, returnType, entityDeclaringMethod); if (calledMethod == null) { ProxyConsole.getInstance().errorOutput().println("Cannot find called method!"); ProxyConsole.getInstance().errorOutput().print('\t'); ProxyConsole.getInstance().errorOutput().print("Method name :"); ProxyConsole.getInstance().errorOutput().println(usedMethodName); ProxyConsole.getInstance().errorOutput().print('\t'); ProxyConsole.getInstance().errorOutput().print("Return type :"); ProxyConsole.getInstance().errorOutput().println(returnType); ProxyConsole.getInstance().errorOutput().print('\t'); ProxyConsole.getInstance().errorOutput().print("Declaring type: "); ProxyConsole.getInstance().errorOutput().println(entityDeclaringMethod.getDisplayName()); ProxyConsole.getInstance().errorOutput().println( "Missing called method can happen if there is a compile error: a method is called but undefined in the class of the instance on which it is called."); } return calledMethod; } private Vector createFieldsFromFieldInfos(final String fieldInfos) { final Vector callingFields = new Vector(); final StringTokenizer st = new StringTokenizer(fieldInfos, "#"); IFirstClassEntity entityDeclaringField = null; IField aCallingField = null; while (st.hasMoreElements()) { final String oneFieldInfo = st.nextToken(); //Extracts the field name final String usedFieldName = RelationshipAnalyzer.extractFieldName(oneFieldInfo); try { //Extracts the entity name that declares the field final String entityNameDeclaringField = oneFieldInfo.substring(0, oneFieldInfo.indexOf(" ")); // Gets from the abstract level model the entity within its name entityDeclaringField = Utils.getEntityOrCreateGhost(this.codeLevelModel, entityNameDeclaringField.toCharArray(), RelationshipAnalyzer.MapOfIDsEntities); // Yann 2006/02/08: Members! // The getConstituentFromID() method takes care of members and ghosts... // // Tests if the entity exists. Otherwise, I create a ghost. // if (entityDeclaringField == null) { // try { // // Extracts the field type // String fieldType = // RelationshipAnalyzer.extractFieldType( // oneFieldInfo); // final int cardinality = // padl.util.Util.isArrayOrCollection(fieldType) // ? Constants.CARDINALITY_MANY // : Constants.CARDINALITY_ONE; // // // Creates a new Ghost and a field by the Factory // entityDeclaringField = // aFactory.createGhost(entityNameDeclaringField); // aCallingField = // aFactory.createField( // usedFieldName, // fieldType, // cardinality); // // // The field is added to the new ghost // entityDeclaringField.addConstituent(aCallingField); // anAbstractLevelModel.addConstituent(entityDeclaringField); // // } // catch (final ModelDeclarationException e) { // e.printStackTrace( // OutputManager // .getCurrentOutputManager() // .getErrorOutput()); // } // } // else { aCallingField = (IField) entityDeclaringField.getConstituentFromID(usedFieldName); // Yann 2007/01/24: Unneeded test // The test of the usedFieldName being empty // is only needed because of the bug documented // in method extractFieldName()! if (aCallingField == null && !usedFieldName.equals("")) { final String fieldType = RelationshipAnalyzer.extractFieldType(oneFieldInfo); final int cardinality = padl.util.Util.isArrayOrCollection(fieldType.toCharArray()) ? Constants.CARDINALITY_MANY : Constants.CARDINALITY_ONE; aCallingField = this.codeLevelModel.getFactory().createField(usedFieldName.toCharArray(), usedFieldName.toCharArray(), fieldType.toCharArray(), cardinality); aCallingField.setVisibility(Modifier.PUBLIC); entityDeclaringField.addConstituent(aCallingField); } callingFields.add(aCallingField); } catch (final Exception e) { e.printStackTrace(ProxyConsole.getInstance().errorOutput()); } } return callingFields; } private IOperation createMethod(final IFirstClassEntity anOriginEntity, final char[] anID, final char[] aReturnType, final char[] aName) { final IMethod method = this.codeLevelModel.getFactory().createMethod(anID, aName); method.setReturnType(aReturnType); this.listOfMethodInvocations.addAll(this.analyseReturnType(anOriginEntity, method)); // Yann 2004/08/02: Analysis of parameters. // I don't really need to analyse parameters of newly created // methods (in ghosts) because these don't have any parameters. this.listOfMethodInvocations.addAll(this.analyseParameters(anOriginEntity, method)); return method; } private void createRelationships() { IRelationship relationship = null; final Iterator e = this.listOfMethodInvocations.iterator(); while (e.hasNext()) { // This is a Use because it has object references. final ExtendedMethodInvocation methodInvocation = (ExtendedMethodInvocation) e.next(); final char[] name = methodInvocation.getEnclosingMethod().getName(); // System.err.print( // methodInvocation.getType() == IMethodInvocation.CLASS_CLASS // || methodInvocation.getType() // == IMethodInvocation.INSTANCE_CLASS); // System.err.print(" "); // System.err.println(methodInvocation.getTargetEntity() != null); // Yann 2004/08/02: Assignment. // I record assignments as method invocations which // name is "=" and target entity is null. I don't // create relationships for such method invocations. if (methodInvocation.getTargetEntity() != null) { switch (methodInvocation.getType()) { case IMethodInvocation.CLASS_CLASS: case IMethodInvocation.INSTANCE_CLASS: // Yann 2002/12/15: Distinction! // If the current entity is an interface // then we have an agregation relationship, // not a use relationship: If an // interface returns an object, the concrete // implementation must store this object // somewhere! So... aggregation! // Yann 2003/06/12: Spurious? // Well, the preceding comment and its // implementation lead to hard-to-understand // relationships... So, removed! // if (currentEntity.isAbstract()) { // element = // new Aggregation( // actorName, // messageType.getTargetEntity(), // messageType.getCardinality()); // } // else { relationship = this.codeLevelModel.getFactory().createUseRelationship(name, methodInvocation.getTargetEntity(), methodInvocation.getCardinality()); relationship.setVisibility(methodInvocation.getVisibility()); // Yann 2004/07/31: Origin entity. // The current entity is not the origin entity, // the origin entity is stored in the message // type from now on. // currentEntity.addConstituent(element); RelationshipAnalyzer.addRelationship(methodInvocation.getOriginEntity(), relationship); break; case IMethodInvocation.CLASS_INSTANCE: case IMethodInvocation.INSTANCE_INSTANCE: relationship = this.codeLevelModel.getFactory().createAssociationRelationship(name, methodInvocation.getTargetEntity(), methodInvocation.getCardinality()); relationship.setVisibility(methodInvocation.getVisibility()); // Yann 2004/07/31: Origin entity. // The current entity is not the origin entity, // the origin entity is stored in the message // type from now on. // currentEntity.addConstituent(element); RelationshipAnalyzer.addRelationship(methodInvocation.getOriginEntity(), relationship); break; case IMethodInvocation.CLASS_CLASS_FROM_FIELD: case IMethodInvocation.INSTANCE_CLASS_FROM_FIELD: case IMethodInvocation.INSTANCE_INSTANCE_FROM_FIELD: relationship = this.codeLevelModel.getFactory().createAggregationRelationship(name, methodInvocation.getTargetEntity(), methodInvocation.getCardinality()); relationship.setVisibility(methodInvocation.getVisibility()); // Yann 2004/07/31: Origin entity. // The current entity is not the origin entity, // the origin entity is stored in the message // type from now on. // currentEntity.addConstituent(element); RelationshipAnalyzer.addRelationship(methodInvocation.getOriginEntity(), relationship); break; case IMethodInvocation.INSTANCE_CREATION: relationship = this.codeLevelModel.getFactory().createCreationRelationship(name, methodInvocation.getTargetEntity(), methodInvocation.getCardinality()); relationship.setVisibility(methodInvocation.getVisibility()); // Yann 2004/07/31: Origin entity. // The current entity is not the origin entity, // the origin entity is stored in the message // type from now on. // currentEntity.addConstituent(element); RelationshipAnalyzer.addRelationship(methodInvocation.getOriginEntity(), relationship); break; case IMethodInvocation.OTHER: break; default: throw new RuntimeException( "An instance of MethodInvocation must have a recognized type value (was " + methodInvocation.getType() + ')'); } } } } private IOperation searchForMethod(final char[] usedMethodName, final char[] returnType, final IFirstClassEntity entityDeclaringMethod) { IOperation calledMethod = null; // Yann 2006/07/29: Compatiblity! // There is a compatibility issue between classfiles v1.1- // and v1.2+, i.e., compiled with a 1.3- compiler and with // a 1.4+ compiler: the methodref pointed by the index in // a invokevirtual bytecode has changed. In the case of two // classes A and B such as A -<|- B, A defines foo(), B // defines bar() that calls this.foo(), the 1.1- bytcode // looks like this (from BCEL): // // [Code(max_stack = 1, max_locals = 1, code_length = 5) // 0: aload_0 // 1: invokevirtual padl.test.method.A.foo ()V (17) // 4: return // // Attribute(s) = // LineNumber(0, 5), LineNumber(4, 6) // LocalVariable(start_pc = 0, length = 5, index = 0:padl.test.method.B this) // ] // // while the 1.2+ looks like that: // [Code(max_stack = 1, max_locals = 1, code_length = 5) // 0: aload_0 // 1: invokevirtual padl.test.method.B.foo ()V (17) // 4: return // // Attribute(s) = // LineNumber(0, 5), LineNumber(4, 6) // LocalVariable(start_pc = 0, length = 5, index = 0:padl.test.method.B this) // ] // // Note the change in the methodref: with 1.1- it is: // padl.test.method.A.foo // while with 1.2+ it is: // padl.test.method.B.foo // and that actually makes a hell of a difference, because we // were assuming that if the method did not exist we should add // it, regardless if this was to a IGhost or a IClass. // // Thus, with 1.2+ bytecode, class B would define an additional // method foo() because of "padl.test.method.B.foo": indeed, // method foo() does not exist in B and thus it would be added! // I modified the test below to add the "instanceof", which // fixes nicely the problem. if (!entityDeclaringMethod.doesContainConstituentWithID(usedMethodName) && entityDeclaringMethod instanceof IGhost) { // Yann 2004/04/03: Constituent ID! // When creating a new method, we give it a unique actorID. // We must also give it a name without extra parentheses. // TODO: Refactor to remove the call to substring(). // Yann 2004/04/09: Modifiers! // When creating a new method, we must also set its modifier: // Test case padl.test.example.Composite1 fails because the // print() method built should be set abstract. // Yann 2004/05/20: Constructors. // If the method is a constructor, its name is // its declaring class name. final char[] name = ArrayUtils.subarray(usedMethodName, 0, ArrayUtils.indexOf(usedMethodName, '(')); if (Utils.isSpecialMethod(name)) { // Creates a new constructor that will be added to the ghost calledMethod = this.createConstructor(entityDeclaringMethod, usedMethodName); } else { // Creates a new method that will be added to the ghost calledMethod = this.createMethod(entityDeclaringMethod, usedMethodName, returnType, name); } entityDeclaringMethod.addConstituent(calledMethod); } else { calledMethod = (IOperation) entityDeclaringMethod.getConstituentFromID(usedMethodName); } // Yann 2006/08/03: Compatibility! // Now that I don't add a method in silly cases, I must // look for this method in the hierarchy of the current // entity, because it can be defined higher up in the // hierarchy... // We are in Java, only one super-class is allowed. // Yann 2007/03/16: Interfaces... // But we could be dealing with an interface, // with no super-interfaces... // TODO: Anyhow, it is strange to deal with interfaces here... Iterator iterator = entityDeclaringMethod.getIteratorOnInheritedEntities(); if (calledMethod == null && iterator.hasNext()) { /* * Alban : 2008/09/30 * Iterate on all parents to find method */ while (iterator.hasNext() && calledMethod == null) { calledMethod = this.searchForMethod(usedMethodName, returnType, (IFirstClassEntity) iterator.next()); } } if (entityDeclaringMethod instanceof IClass) { iterator = ((IClass) entityDeclaringMethod).getIteratorOnImplementedInterfaces(); if (calledMethod == null && iterator.hasNext()) { /* * Aminata & Venera : 2011/08/19 * Iterate on all interfaces to find method */ while (iterator.hasNext() && calledMethod == null) { calledMethod = this.searchForMethod(usedMethodName, returnType, (IFirstClassEntity) iterator.next()); } } } return calledMethod; } }