/******************************************************************************* * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * * contact.vitam@culture.gouv.fr * * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as * circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the * successive licensors have only limited liability. * * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or * developing or reproducing the software by the user in light of its specific status of free software, that may mean * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data * to be ensured and, more generally, to use and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you * accept its terms. *******************************************************************************/ package fr.gouv.vitam.common.graph; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; import com.fasterxml.jackson.databind.JsonNode; import fr.gouv.vitam.common.logging.VitamLogger; import fr.gouv.vitam.common.logging.VitamLoggerFactory; /** * Graph contains Directed Acyclic Graph */ public class Graph { private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(Graph.class); private final Vertex[] vertices; private int size; private final int maxSize; private final Deque<Vertex> stack; private int count = 0; // map id_xml index private final BidiMap<Integer, String> indexMapping; // set of roots private final Set<String> roots; // longest path private Map<Integer, Set<String>> longestsPath; /** * Graph constructor * * @param jsonGraph { "ID027" : { }, "ID028" : { "_up" : [ "ID027" ] }, "ID029" : { "_up" : [ "ID028" ] }} */ public Graph(JsonNode jsonGraph) { roots = new HashSet<>(); indexMapping = new DualHashBidiMap<>(); maxSize = jsonGraph.size(); // number of vertice LOGGER.debug("maxSize:" + maxSize); vertices = new Vertex[maxSize]; stack = new ArrayDeque<>(maxSize); for (int i = 0; i < maxSize; i++) { addVertex(i + 1); } // parse json to create graph final Iterator<Entry<String, JsonNode>> levelIterator = jsonGraph.fields(); while (levelIterator.hasNext()) { final Entry<String, JsonNode> cycle = levelIterator.next(); final String idChild = cycle.getKey(); final JsonNode up = cycle.getValue(); // create mappping addMapIdToIndex(idChild); if (up != null && up.size() > 0) { final JsonNode arrNode = up.get("_up"); for (final JsonNode idParent : arrNode) { addEdge(getIndex(idParent.textValue()), getIndex(idChild)); LOGGER.debug("source:" + idParent); LOGGER.debug("destin:" + idChild); } } else { roots.add(idChild); } } } private int addMapIdToIndex(String idXml) { if (indexMapping != null) { // FIXME P1 since called many times, better to assign one for all this inverseBidiMap to a private variable final BidiMap<String, Integer> xmlIdToIndex = indexMapping.inverseBidiMap(); if (xmlIdToIndex.get(idXml) == null) { count++; indexMapping.put(count, idXml); } } return count; } /** * add vertex * * @param data */ private void addVertex(int data) { vertices[size++] = new Vertex(data); } /** * add edge * * @param source * @param destination */ private void addEdge(int source, int destination) { vertices[source - 1].adj = new Neighbour(destination - 1, vertices[source - 1].adj); } /** * Vertex class */ public class Vertex { int data; Neighbour adj; int cost = 0; State state = State.NEW; /** * Vertex constructor * * @param data */ public Vertex(int data) { this.data = data; } } /** * state enum */ public enum State { NEW, VISITED } /** * Neighbour class */ public class Neighbour { int index; Neighbour next; int weight = 1; Neighbour(int index, Neighbour next) { this.index = index; this.next = next; } } /** * * @param source * @return {@link Map} : longest path for list of child */ private Map<Integer, Integer> findLongestsPath(int source) { applyTopologicalSort(); final Map<Integer, Integer> longestsPathMap = new WeakHashMap<>(); vertices[source - 1].cost = 0; while (!stack.isEmpty()) { final Vertex u = stack.pop(); if (u.cost != Integer.MIN_VALUE) { Neighbour temp = u.adj; while (temp != null) { final Vertex v = vertices[temp.index]; if (v.cost < temp.weight + u.cost) { v.cost = temp.weight + u.cost; } temp = temp.next; } } } // put longest path in the map for (int i = 0; i < maxSize; i++) { longestsPathMap.put(i + 1, vertices[i].cost); } LOGGER.debug("Longest path from " + longestsPath); return longestsPathMap; } /** * create all longest path (or level stack: will be respected when indexing the units) * * @param roots * @return Map<Integer, Integer> :longest path for different roots */ private Map<Integer, Integer> findAllLongestsPath(Set<String> roots) { final Map<Integer, Integer> allLongestsPath = new WeakHashMap<>(); for (final String rootXmlId : roots) { // find longest path for different roots final Map<Integer, Integer> longestsPathMap = findLongestsPath(getIndex(rootXmlId)); if (allLongestsPath.isEmpty()) { // if empty put all distances allLongestsPath.putAll(longestsPathMap); } else { // we verify the longest path for (final Map.Entry<Integer, Integer> e : longestsPathMap.entrySet()) { final Integer key = e.getKey(); final Integer value = e.getValue(); LOGGER.debug("key" + key + "------------ value:" + indexMapping.get(key)); if (allLongestsPath.containsKey(key) && allLongestsPath.get(key) < value) { // replace old value allLongestsPath.put(key, value); } } } } return allLongestsPath; } /** * create level stack: the longest path for different roots * * @return {@link Map} */ public Map<Integer, Set<String>> getGraphWithLongestPaths() { longestsPath = new HashMap<>(); Map<Integer, Integer> paths = findAllLongestsPath(roots); if (paths != null) { for (final Map.Entry<Integer, Integer> e : paths.entrySet()) { final Integer unitId = e.getKey(); final Integer level = e.getValue(); if (longestsPath.containsKey(level) && longestsPath.get(level) != null) { // add value longestsPath.get(level).add(indexMapping.get(unitId)); } else { final Set<String> units = new HashSet<>(); units.add(indexMapping.get(unitId)); longestsPath.put(level, units); } } } paths = null; return longestsPath; } private void applyTopologicalSort() { for (int i = 0; i < maxSize; i++) { if (vertices[i].state != State.VISITED) { depthFirstSearch(vertices[i]); } } } private void depthFirstSearch(Vertex u) { Neighbour temp = u.adj; u.state = State.VISITED; while (temp != null) { final Vertex v = vertices[temp.index]; if (v.state == State.NEW) { depthFirstSearch(v); } temp = temp.next; } stack.push(u); } private int getIndex(String id) { int key = 0; if (indexMapping != null) { // check value if exist if (indexMapping.containsValue(id)) { final BidiMap<String, Integer> xmlIdToIndex = indexMapping.inverseBidiMap(); key = xmlIdToIndex.get(id); } else { key = addMapIdToIndex(id); } return key; } return key; } }