Skip to content

Commit

Permalink
fix: circular reference (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
coryhh authored Aug 9, 2023
1 parent 9cf3c9e commit ad2be16
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 91 deletions.
2 changes: 1 addition & 1 deletion arex-compare-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>arex-compare-parent</artifactId>
<groupId>com.arextest</groupId>
<version>0.1.24</version>
<version>0.1.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,49 @@
import com.arextest.diff.model.key.ListSortEntity;
import com.arextest.diff.model.key.ReferenceEntity;
import com.arextest.diff.model.log.NodeEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


public class KeyCompute {

public KeyComputeResponse doHandler(RulesConfig rulesConfig, Object baseObj, Object testObj) throws ExecutionException, InterruptedException {
private static final Logger LOGGER = LoggerFactory.getLogger(KeyCompute.class);
private static final int KEY_COMPUTE_WAIT_TIME = 2;

public KeyComputeResponse doHandler(RulesConfig rulesConfig, Object baseObj, Object testObj) throws ExecutionException, InterruptedException, TimeoutException {

List<ReferenceEntity> allReferenceEntities = rulesConfig.getReferenceEntities();
List<ListSortEntity> listSortConfig = rulesConfig.getListSortEntities();

Callable<HashMap<List<NodeEntity>, HashMap<Integer, String>>> callable1 = () -> {
CompletableFuture<HashMap<List<NodeEntity>, HashMap<Integer, String>>> future1 = CompletableFuture.supplyAsync(() -> {
ListKeyProcess keyProcessLeft = new ListKeyProcess(allReferenceEntities, listSortConfig);
keyProcessLeft.computeAllListKey(baseObj);
return keyProcessLeft.getListIndexKeys();
};

Callable<HashMap<List<NodeEntity>, HashMap<Integer, String>>> callable2 = () -> {
ListKeyProcess keyProcessRight = new ListKeyProcess(allReferenceEntities, listSortConfig);
keyProcessRight.computeAllListKey(testObj);
return keyProcessRight.getListIndexKeys();
};
}, TaskThreadFactory.jsonObjectThreadPool);

HashMap<List<NodeEntity>, HashMap<Integer, String>> listIndexKeysLeft = TaskThreadFactory.jsonObjectThreadPool.submit(callable1).get();
HashMap<List<NodeEntity>, HashMap<Integer, String>> listIndexKeysRight = TaskThreadFactory.jsonObjectThreadPool.submit(callable2).get();
CompletableFuture<HashMap<List<NodeEntity>, HashMap<Integer, String>>> future2 = CompletableFuture.supplyAsync(() -> {
ListKeyProcess keyProcessLeft = new ListKeyProcess(allReferenceEntities, listSortConfig);
keyProcessLeft.computeAllListKey(testObj);
return keyProcessLeft.getListIndexKeys();
}, TaskThreadFactory.jsonObjectThreadPool);

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
try {
combinedFuture.get(KEY_COMPUTE_WAIT_TIME, TimeUnit.MINUTES);
} catch (TimeoutException e) {
LOGGER.error("KeyCompute doHandler TimeoutException", e);
throw e;
}

HashMap<List<NodeEntity>, HashMap<Integer, String>> listIndexKeysLeft = future1.get();
HashMap<List<NodeEntity>, HashMap<Integer, String>> listIndexKeysRight = future2.get();

KeyComputeResponse response = new KeyComputeResponse();
response.setAllReferenceEntities(allReferenceEntities);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.arextest.diff.handler.keycompute;

import com.arextest.diff.model.enumeration.Constant;
import com.arextest.diff.model.exception.ListKeyCycleException;
import com.arextest.diff.model.key.ListSortEntity;
import com.arextest.diff.model.key.ReferenceEntity;
import com.arextest.diff.model.log.LogEntity;
Expand All @@ -11,18 +12,14 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.*;

public class ListKeyProcess {

private static final Logger LOGGER = LoggerFactory.getLogger(ListKeyProcess.class);
private List<NodeEntity> currentNodePath = new ArrayList<>();

private List<LogEntity> logs = new ArrayList<>();
Expand All @@ -31,25 +28,19 @@ public class ListKeyProcess {

private List<ListSortEntity> allListKeys;

private LinkedList<ListSortEntity> prioritylistSortEntities;

// <referenceListPath,refNum,keyValue>
private HashMap<String, HashMap<String, String>> referenceKeys = new HashMap<>();

// <list path, <index,keyValue>>
private HashMap<List<NodeEntity>, HashMap<Integer, String>> listIndexKeys = new HashMap<>();

private HashMap<String, List<String>> listKeysMap;

private List<String> currentParentPath;

boolean useFirstElementKey = false;

public ListKeyProcess(List<ReferenceEntity> responseReferences, List<ListSortEntity> allListKeys) {
this.responseReferences = responseReferences;
this.allListKeys = allListKeys;
this.listKeysMap = getListKeysMap();
this.prioritylistSortEntities = computeReferencedListPriority();
}

public List<LogEntity> getLogs() {
Expand All @@ -60,55 +51,43 @@ public HashMap<List<NodeEntity>, HashMap<Integer, String>> getListIndexKeys() {
return listIndexKeys;
}

// probably dead loop
private LinkedList<ListSortEntity> computeReferencedListPriority() {
List<String> fkPaths = new ArrayList<>();
Set<String> pkPaths = new LinkedHashSet<>();
/**
* Get the listKey that needs to be calculated first
*
* @param responseReferences
* @param allListKeys
* @return
* @throws ListKeyCycleException
*/
private LinkedList<ListSortEntity> computeReferencedListPriority(List<ReferenceEntity> responseReferences,
List<ListSortEntity> allListKeys) throws ListKeyCycleException {
if (responseReferences == null || responseReferences.isEmpty()) {
return new LinkedList<>();
}

Map<String, List<String>> listKeysMap = this.getListKeysMap(allListKeys);

// the collection of fkPaths
Set<String> fkPaths = new HashSet<>();
// the collection of pkNodeListPaths
Set<String> pkListPaths = new HashSet<>();

HashMap<String, List<String>> relations = new HashMap<>();
Queue<String> queue = new LinkedList<>();
// fkPath -> the collection of pkNodeListPaths
Map<String, Set<String>> relations = new HashMap<>();
for (ReferenceEntity re : responseReferences) {
String fkPath = ListUti.convertToString2(re.getFkNodePath());
String pkPath = ListUti.convertToString2(re.getPkNodeListPath());
String pkListPath = ListUti.convertToString2(re.getPkNodeListPath());
fkPaths.add(fkPath);
pkPaths.add(pkPath);
if (relations.containsKey(fkPath)) {
relations.get(fkPath).add(pkPath);
} else {
List<String> list = new ArrayList<>();
list.add(pkPath);
relations.put(fkPath, list);
}
pkListPaths.add(pkListPath);
relations.computeIfAbsent(fkPath, k -> new HashSet<>()).add(pkListPath);
}

while (pkPaths.size() > queue.size()) {
for (String s : pkPaths) {
if (queue.contains(s)) continue;
List<String> list = findMatchPath(fkPaths, s);
if (list.size() == 0) {
queue.add(s);
} else {
boolean flag = true;
for (int i = 0; i < list.size(); i++) {
String refNode = list.get(i);
List<String> refPkNodes = relations.get(refNode);

for (String refPkNode : refPkNodes) {
if (!queue.contains(refPkNode)) {
flag = false;
break;
}
}
if (!flag) {
break;
}
}
if (flag) {
queue.add(s);
}
}
}
}
// all traversed node collections
Set<String> traversedSet = new HashSet<>();
LinkedList<String> queue = new LinkedList<>();

this.doPriorityReferences(queue, pkListPaths, new LinkedList<>(), traversedSet, relations, fkPaths, listKeysMap);

LinkedList<ListSortEntity> listSortEntityQueue = new LinkedList<>();
String path;
while ((path = queue.poll()) != null) {
Expand All @@ -124,36 +103,76 @@ private LinkedList<ListSortEntity> computeReferencedListPriority() {
return listSortEntityQueue;
}

private List<String> findMatchPath(List<String> fkPaths, String s) {
List<String> keyPaths = listKeysMap.get(s);
//pkList keys match in fkNode path
List<String> matchPath = new ArrayList<>();
if (keyPaths == null) {
return matchPath;
/**
* find all fkPaths in pkNodeListPath
*
* @param fkPaths the collection of fkPaths
* @param pkNodeListPath pkNodeListPath
* @return
*/
private Set<String> findFkPathInListKey(Set<String> fkPaths, String pkNodeListPath, Map<String, List<String>> listKeysMap) {
List<String> keyPaths = listKeysMap.get(pkNodeListPath);
if (keyPaths == null || keyPaths.isEmpty()) {
return Collections.emptySet();
}
for (int i = 0; i < keyPaths.size(); i++) {
for (int j = 0; j < fkPaths.size(); j++) {
String str = fkPaths.get(j);
if (str.equals(keyPaths.get(i))) matchPath.add(str);

Set<String> matchPath = new HashSet<>(keyPaths.size());
for (String keyPath : keyPaths) {
if (fkPaths.contains(keyPath)) {
matchPath.add(keyPath);
}
}

return matchPath;
}

private HashMap<String, List<String>> getListKeysMap() {
HashMap<String, List<String>> map = new HashMap<>();
private Map<String, List<String>> getListKeysMap(List<ListSortEntity> allListKeys) {
if (allListKeys == null || allListKeys.isEmpty()) {
return Collections.emptyMap();
}

Map<String, List<String>> listKeysMap = new HashMap<>();
for (ListSortEntity listSortEntity : allListKeys) {
String listPath = ListUti.convertToString2(listSortEntity.getListNodepath());
List<String> keyPaths = new ArrayList<>();
for (int i = 0; i < listSortEntity.getKeys().size(); i++) {
String listKeyPath = ListUti.convertToString2(mergePath(listSortEntity.getListNodepath(), listSortEntity.getKeys().get(i)));
String listKeyPath = ListUti.convertToString2(
mergePath(listSortEntity.getListNodepath(), listSortEntity.getKeys().get(i)));
keyPaths.add(listKeyPath);
}
map.put(listPath, keyPaths);
listKeysMap.put(listPath, keyPaths);
}
return listKeysMap;
}

private void doPriorityReferences(LinkedList<String> queue, Set<String> refLinkNodes, LinkedList<String> singleLinkAllNodeSet,
Set<String> traversedSet, Map<String, Set<String>> relations, Set<String> fkPaths,
Map<String, List<String>> listKeysMap) throws ListKeyCycleException {
if (refLinkNodes == null || refLinkNodes.isEmpty()) {
return;
}

for (String refLinkNode : refLinkNodes) {
if (singleLinkAllNodeSet.contains(refLinkNode)) {
throw new ListKeyCycleException(String.format("an infinite loop occurs, path: %s", singleLinkAllNodeSet));
}

return map;
if (traversedSet.contains(refLinkNode)) {
continue;
}

queue.addLast(refLinkNode);
traversedSet.add(refLinkNode);

Set<String> refFkNodePaths = findFkPathInListKey(fkPaths, refLinkNode, listKeysMap);

for (String refFkNode : refFkNodePaths) {
singleLinkAllNodeSet.addLast(refLinkNode);
Set<String> refPkListPaths = relations.get(refFkNode);
this.doPriorityReferences(queue, refPkListPaths, singleLinkAllNodeSet, traversedSet, relations,
fkPaths, listKeysMap);
singleLinkAllNodeSet.removeLast();
}
}
}

// add node name
Expand Down Expand Up @@ -297,12 +316,16 @@ private String getKeyValueByPath(List<String> relativePath, Object obj) {
return result;
}

public void computeAllListKey(Object obj) {
public void computeAllListKey(Object obj) throws ListKeyCycleException {

LinkedList<ListSortEntity> priorityListSortEntities = this.computeReferencedListPriority(
responseReferences, allListKeys);

// priority list keys
ListSortEntity listSortEntity;

try {
while ((listSortEntity = prioritylistSortEntities.poll()) != null) {
while ((listSortEntity = priorityListSortEntities.poll()) != null) {
List<String> listNodePath = listSortEntity.getListNodepath();
Object object = getObject(obj, listNodePath);
if (object == null || obj instanceof NullNode) {
Expand All @@ -318,7 +341,7 @@ public void computeAllListKey(Object obj) {
Object listElement = listObj.get(i);
String refValue = getObject(listElement, listSortEntity.getReferenceNodeRelativePath()).toString();

if (listSortEntity.getKeys() == null || listSortEntity.getKeys().size() == 0) {
if (listSortEntity.getKeys() == null || listSortEntity.getKeys().isEmpty()) {
throw new RuntimeException("ref list node don't have listkey!");
}
for (List<String> path : listSortEntity.getKeys()) {
Expand All @@ -335,7 +358,7 @@ public void computeAllListKey(Object obj) {
}
}
} catch (Throwable throwable) {
throwable.printStackTrace();
LOGGER.error("computePriorityListSort error", throwable);
}

// other normal list keys,traverse the whole tree
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.arextest.diff.model.exception;

public class ListKeyCycleException extends RuntimeException {
public ListKeyCycleException() {
super();
}

public ListKeyCycleException(String s) {
super(s);
}
}
Loading

0 comments on commit ad2be16

Please sign in to comment.