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);
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 root;
}
/**
* 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;
}
}