package org.ntlab.deltaViewer; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import org.ntlab.deltaExtractor.ExtractedStructure; import org.ntlab.trace.MethodExecution; import org.ntlab.trace.MethodInvocation; import org.ntlab.trace.Reference; import org.ntlab.trace.Statement; import org.ntlab.trace.TracePoint; /** * CollaborationObjectCallGraph is IObjectCallGraph implementation class to merge ExtractedStructure. * * @author Nitta Lab. */ public class CollaborationObjectCallGraph implements IObjectCallGraph { private Set<Reference> references = new HashSet<>(); private List<MethodExecution> startPoints = new ArrayList<>(); // Common ancestor point private List<TracePoint> relatedPoints = new ArrayList<>(); public CollaborationObjectCallGraph(ExtractedStructure e) { references.addAll(e.getDelta().getSrcSide()); references.addAll(e.getDelta().getDstSide()); startPoints.add(e.getCoordinator()); relatedPoints.add(e.getRelatedTracePoint().duplicate()); } @Override public List<Reference> getReferences() { return new ArrayList<Reference>(references); // Convert to List from Set. } @Override public List<MethodExecution> getStartPoints() { return startPoints; } @Override public List<TracePoint> getRelatedPoints() { return relatedPoints; } @Override public Map<MethodExecution, List<MethodExecution>> getCallTree() { return null; } /** * Merge ExtractedStructure not to overlap reference. * @param e ExtractedStructure to be merged into the field. */ public void merge(ExtractedStructure e) { // references.addAll(e.getDelta().getSrcSide()); // references.addAll(e.getDelta().getDstSide()); // Prefer isCreatioin() = true references, if references are equal, but isCreation() is different. for (Reference srcSideRef: e.getDelta().getSrcSide()) { if (references.contains(srcSideRef)) { List<Reference> refs = getReferences(); // Convert to List from Set. int idx = refs.indexOf(srcSideRef); Reference ref = refs.get(idx); if (srcSideRef.isCreation() != ref.isCreation()) { if (srcSideRef.isCreation()) { references.remove(ref); references.add(srcSideRef); } } } else { references.add(srcSideRef); } } for (Reference dstSideRef: e.getDelta().getDstSide()) { if (references.contains(dstSideRef)) { List<Reference> refs = getReferences(); // Convert to List from Set. int idx = refs.indexOf(dstSideRef); Reference ref = refs.get(idx); if (dstSideRef.isCreation() != ref.isCreation()) { if (dstSideRef.isCreation()) { references.remove(ref); references.add(dstSideRef); } } } else { references.add(dstSideRef); } } // There may be bug. (Two object has each coordinator like JHotDraw Transform) MethodExecution thisStartPoint = startPoints.get(0); MethodExecution tmp = thisStartPoint; MethodExecution thisRoot = thisStartPoint.getParent(); MethodExecution otherStartPoint = e.getCoordinator(); while(thisRoot != null) { // Get Root of thisStartPoint. tmp = tmp.getParent(); thisRoot = tmp.getParent(); } thisRoot = tmp; /* lowest common ancestor algorithm */ MethodExecution lca = lowestCommonAncestor(thisRoot, thisStartPoint, otherStartPoint); if (lca == null) { if (thisStartPoint.getEntryTime() < otherStartPoint.getEntryTime()) { lca = thisStartPoint; } else { lca = otherStartPoint; } } startPoints.clear(); startPoints.add(lca); // Is it in time stamp order? TracePoint otherRelatedTp = e.getRelatedTracePoint().duplicate(); relatedPoints.add(otherRelatedTp); relatedPoints = sortTracePointByTimeStamp(relatedPoints); } /** * Search lowest common ancestor(lca) of p and q. * @param root * @param p * @param q * @return Lca methodExecution. */ public MethodExecution lowestCommonAncestor(MethodExecution root, MethodExecution p, MethodExecution q) { if(root == null || root == p || root == q) return root; Set<MethodExecution> pRoots = new HashSet<>(); MethodExecution pTmp = p; while (!root.equals(pTmp) && pTmp != null) { pRoots.add(pTmp); pTmp = pTmp.getParent(); } pRoots.add(root); MethodExecution qTmp = q; while (!root.equals(qTmp) && qTmp != null) { if (pRoots.contains(qTmp)) return qTmp; qTmp = qTmp.getParent(); } return qTmp; } /** * Sort tracePoint in time stamp order. * @param tpList TracePoint List to sort. * @return Sorted TracePoint List. */ private List<TracePoint> sortTracePointByTimeStamp(List<TracePoint> tpList) { List<TracePoint> cloneTpList = new ArrayList<>(tpList); List<TracePoint> sortedTpList = cloneTpList.stream().sorted(new Comparator<TracePoint>() { @Override public int compare(TracePoint tp1, TracePoint tp2) { long tp1TimeStamp = tp1.getStatement().getTimeStamp(); long tp2TimeStamp = tp2.getStatement().getTimeStamp(); if (tp1TimeStamp > tp2TimeStamp) return 1; else if (tp1TimeStamp < tp2TimeStamp) return -1; return 0; } }).collect(Collectors.toList()); return sortedTpList; } public void shrinkAll(Map<MethodExecution, Set<MethodExecution>> newToOldMethodExecutionMap) { List<Reference> refs = getReferences(); List<Reference> collectionReferences = collectCollectionReferences(refs); List<List<Reference>> collectionChains = collectCollectionChains(collectionReferences); refs = replaceCollectionChains(refs, collectionChains); references = new HashSet<>(refs); // Convert to Set from List. relatedPoints = replaceRelatedPoints(relatedPoints, newToOldMethodExecutionMap); // For debug. System.out.println("collectionReferences: "); for (Reference ref: collectionReferences) { System.out.println("\t" + ref.getSrcClassName() + "(" + ref.getSrcObjectId() + ")" + " -> " + ref.getDstClassName() + "(" + ref.getDstObjectId() + "): " + ref.isCollection()); } System.out.println("collectionChains: "); for (int i = 0; i < collectionChains.size(); i++) { List<Reference> CollectionChain = collectionChains.get(i); System.out.println("i = " + i); for (Reference ref: CollectionChain) { System.out.println("\t" + ref.getSrcClassName() + "(" + ref.getSrcObjectId() + ")" + " -> " + ref.getDstClassName() + "(" + ref.getDstObjectId() + "): " + ref.isCollection()); } } System.out.println("replaceCollectionChains: "); for (Reference ref: references) { System.out.println("\t" + ref.getSrcClassName() + "(" + ref.getSrcObjectId() + ")" + " -> " + ref.getDstClassName() + "(" + ref.getDstObjectId() + "): " + ref.isCollection() + ", " + ref.isCreation()); } } private List<Reference> collectCollectionReferences(List<Reference> references) { // Collect references that are Collection. List<Reference> collectionRefs = new ArrayList<>(); for (Reference ref: references) { if (ref.isCollection()) { collectionRefs.add(ref); } } return collectionRefs; } private List<List<Reference>> collectCollectionChains(List<Reference> collectionReferences) { // Collect follow references. List<Reference> collectionRefs = new ArrayList<>(collectionReferences); // Create new instance of coping collectionReference. List<List<Reference>> collectionChains = new ArrayList<>(); // Search first reference. int i = 0; while (i < collectionRefs.size()) { Reference ref = collectionRefs.get(i); String srcClassName = ref.getSrcClassName(); String srcObjId = ref.getSrcObjectId(); boolean isFirstRef = true; // If dstClassName matches "$[1-9]" and don't shrink References too much(JHotDrawTransform). if (!ref.getDstClassName().matches(".*\\$[0-9]+.*")) { for (int j = 0; j < collectionReferences.size(); j++) { if (collectionReferences.indexOf(ref) != j) { Reference compareRef = collectionReferences.get(j); if (srcClassName.equals(compareRef.getDstClassName()) && srcObjId.equals(compareRef.getDstObjectId())) { isFirstRef = false; break; } } } } if (isFirstRef) { List<Reference> collectionChain = new ArrayList<>(); collectionChain.add(ref); collectionChains.add(collectionChain); collectionRefs.remove(i); } else { i++; } } // Search references follow first reference. for (i = 0; i < collectionChains.size(); i++) { List<Reference> collectionChain = collectionChains.get(i); int j = 0; while (j < collectionChain.size()) { Reference ref = collectionChain.get(j); String dstClassName = ref.getDstClassName(); String dstObjId = ref.getDstObjectId(); j++; for (int k = 0; k < collectionRefs.size(); k++) { Reference compareRef = collectionRefs.get(k); if (dstClassName.equals(compareRef.getSrcClassName()) && dstObjId.equals(compareRef.getSrcObjectId())) { collectionChain.add(compareRef); collectionRefs.remove(k); break; } } } if (collectionChain.size() == 1) { collectionChains.remove(i); i--; } } return collectionChains; } private List<Reference> replaceCollectionChains(List<Reference> references, List<List<Reference>> collectionChains) { // Replace to shrink Reference in references. List<Reference> replacedReferences = new ArrayList<>(references); for (List<Reference> collectionChain: collectionChains) { // Create shrink new reference. Reference firstRef = collectionChain.get(0); Reference lastRef = collectionChain.get(collectionChain.size() - 1); Reference newRef = new Reference(firstRef.getSrcObjectId(), lastRef.getDstObjectId(), firstRef.getSrcClassName(), lastRef.getDstClassName()); newRef.setCollection(true); // Remove collectionChains from references. for (int i = 0; i < collectionChain.size(); i++) { Reference ref = collectionChain.get(i); int refIdx = replacedReferences.indexOf(ref); // Get index of collection reference in references. if (refIdx != - 1) replacedReferences.remove(refIdx); else System.out.println("Failed to remove collection reference in references..."); } replacedReferences.add(newRef); // Add new reference. } return replacedReferences; } /** * Replace calledMethodExec in relatedPoints to newMethodExec. * @param relatedPoints * @param newToOldMethodExecutionMap * @return Replaced related points. */ private List<TracePoint> replaceRelatedPoints(List<TracePoint> relatedPoints, Map<MethodExecution, Set<MethodExecution>> newToOldMethodExecutionMap) { List<TracePoint> replacedRp = new ArrayList<>(relatedPoints); for (TracePoint rp: replacedRp) { Statement st = rp.getStatement(); if (st instanceof MethodInvocation) { MethodInvocation methodInv = (MethodInvocation)st; MethodExecution calledMethodExec = methodInv.getCalledMethodExecution(); for (Entry<MethodExecution, Set<MethodExecution>> entry: newToOldMethodExecutionMap.entrySet()) { MethodExecution newMethodExec = entry.getKey(); Set<MethodExecution> oldMethodExecSet = entry.getValue(); if (oldMethodExecSet.contains(calledMethodExec)) { methodInv.setCalledMethodExecution(newMethodExec); } } } } return replacedRp; } }