package org.constellation.json.metadata.v2; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.sis.util.logging.Logging; import org.constellation.json.JsonMetadataConstants; import static org.constellation.json.JsonMetadataConstants.getLastOrdinal; import static org.constellation.json.JsonMetadataConstants.isNumeratedPath; import org.constellation.json.metadata.binding.Block; import org.constellation.json.metadata.binding.BlockObj; import org.constellation.json.metadata.binding.ComponentObj; import org.constellation.json.metadata.binding.Field; import org.constellation.json.metadata.binding.FieldObj; import org.constellation.json.metadata.binding.IBlock; import org.constellation.json.metadata.binding.RootObj; import org.constellation.json.metadata.binding.SuperBlock; /** * * @author guilhem */ public class TemplateTree { private static final Logger LOGGER = Logging.getLogger("org.constellation.json.metadata.v2"); private final List<ValueNode> nodes = new ArrayList<>(); public ValueNode getNodeByPath(String path) { for (ValueNode node : nodes) { if (node.path.equals(path)) { return node; } } return null; } public ValueNode getNodeByNumeratedPath(String numeratedPath, String blockName) { for (ValueNode node : nodes) { if (node.getNumeratedPath().equals(numeratedPath) && Objects.equals(node.blockName, blockName)) { return node; } } return null; } public ValueNode getNodeByNumeratedPath(String numeratedPath) { for (ValueNode node : nodes) { if (node.getNumeratedPath().equals(numeratedPath)) { return node; } } return null; } public List<ValueNode> getNodesByPath(String path) { final List<ValueNode> results = new ArrayList<>(); for (ValueNode node : nodes) { if (node.path.equals(path)) { results.add(node); } } return results; } public List<ValueNode> getNodesByPathAndType(String path, String type) { final List<ValueNode> results = new ArrayList<>(); for (ValueNode node : nodes) { if (node.path.equals(path) && Objects.equals(node.type, type)) { results.add(node); } } return results; } public List<ValueNode> getNodesByBlockName(String blockName) { final List<ValueNode> results = new ArrayList<>(); for (ValueNode node : nodes) { if (blockName.equals(node.blockName)) { results.add(node); } } return results; } public List<ValueNode> getNodesForBlock(Block block) { final List<ValueNode> results = new ArrayList<>(); if (block.getPath() != null) { for (ValueNode node : nodes) { if (block.getName().equals(node.blockName)) { results.add(node); } } return results; } else { results.add(null); } return results; } public ValueNode getRoot() { for (ValueNode node : nodes) { if (node.parent == null) { return node; } } return null; } /** * Duplicate a node only it does not exist * @param node * @return */ public ValueNode duplicateNode(ValueNode node, int i) { String numeratedPath = updateLastOrdinal(node.getNumeratedPath(), i); ValueNode exist = getNodeByNumeratedPath(numeratedPath, node.blockName); // issue here if (exist == null) { int j = i; ValueNode n = getNodeByNumeratedPath(numeratedPath); while (n != null) { j++; numeratedPath = updateLastOrdinal(numeratedPath, j); ValueNode tmp = getNodeByNumeratedPath(numeratedPath); n.updateOrdinal(j); n = tmp; } exist = new ValueNode(node, node.parent, i); nodes.add(exist); for (ValueNode child : node.children) { duplicateNode(child, exist); } } return exist; } private void duplicateNode(ValueNode node, ValueNode parent) { ValueNode newNode = new ValueNode(node, parent, node.ordinal); nodes.add(newNode); for (ValueNode child : node.children) { duplicateNode(child, newNode); } } public List<ValueNode> getNodesByFieldAndParent(String fieldName, ValueNode parent) { final List<ValueNode> results = new ArrayList<>(); for (ValueNode node : nodes) { if (node.blockName != null && node.blockName.equals(fieldName) && (parent == null || node.hashParent(parent))) { results.add(node); } } return results; } private String updateLastOrdinal(final String numeratedPath, int ordinal) { int i = numeratedPath.lastIndexOf('['); if (i != -1) { return numeratedPath.substring(0, i + 1) + ordinal +"]"; } throw new IllegalArgumentException(numeratedPath + " does not contain numerated value"); } private void addNode(ValueNode node, ValueNode ancestor, final RootObj template, String numPath) { nodes.add(node); // for a new Node to add, we create all the missing parent nodes ValueNode child = node; String path = node.path; while (path.indexOf('.') != -1) { path = path.substring(0, path.lastIndexOf('.')); numPath = numPath.substring(0, numPath.lastIndexOf('.')); int i = getLastOrdinal(numPath); List<ValueNode> parents; if (isNumeratedPath(numPath)) { parents = new ArrayList<>(); ValueNode v = getNodeByNumeratedPath(numPath); if (v != null) { parents.add(v); } } else { parents = getNodesByPath(path); } if (parents.isEmpty()) { ValueNode parent = new ValueNode(path, template.getTypeForPath(path), i, null, null, false); nodes.add(parent); parent.addChild(child); child = parent; } else { boolean found = false; for (ValueNode parent : parents) { if (ancestor == null || parent.hashParent(ancestor) || parent.simpleEquals(ancestor)) { parent.addChild(child); found = true; break; } } if (!found) { ValueNode parent = new ValueNode(path, template.getTypeForPath(path), i, null, null,false); nodes.add(parent); parent.addChild(child); child = parent; } else { break; } } } } private void moveFollowingNumeratedPath(String path, int ordinal) { path = JsonMetadataConstants.cleanNumeratedPath(path); List<ValueNode> toUpdate = getNodesByPath(path); for (ValueNode n : toUpdate) { if (n.ordinal >= ordinal) { n.updateOrdinal(n.ordinal++); } } } public static TemplateTree getTreeFromRootObj(RootObj template) { final TemplateTree tree = new TemplateTree(); for (SuperBlock sb : template.getSuperBlocks()) { final Map<String, Integer> blockPathOrdinal = new HashMap<>(); for (BlockObj block : sb.getChildren()) { updateTreeFromBlock(block, tree, template, blockPathOrdinal); } } return tree; } public static void updateTreeFromBlock(BlockObj blockO, TemplateTree tree, final RootObj template, Map<String, Integer> blockPathOrdinal) { final Block block = blockO.getBlock(); // Multiple Block ValueNode ancestor = null; if (block.getPath() != null) { int ordinal = updateOrdinal(blockPathOrdinal, block.getPath()); ancestor = new ValueNode(block, ordinal); if (block.getPath().endsWith("+")) { block.updatePath(ordinal); template.moveFollowingNumeratedPath(block, ordinal); } tree.addNode(ancestor, null, template, block.getPath()); } // Fields final Map<String, Integer> fieldPathOrdinal = new HashMap<>(); for (ComponentObj child : block.getChildren()) { if (child instanceof FieldObj) { updateTreeFromField((FieldObj)child, tree, ancestor, template, fieldPathOrdinal); } else { updateTreeFromBlock((BlockObj)child, tree, template, blockPathOrdinal); } } } public static void updateTreeFromField(FieldObj fieldObj, TemplateTree tree, final ValueNode ancestor, final RootObj template, Map<String, Integer> fieldPathOrdinal) { final Field field = fieldObj.getField(); int fieldOrdinal = updateOrdinal(fieldPathOrdinal, field.getPath()); final ValueNode node = new ValueNode(field, fieldOrdinal); tree.addNode(node, ancestor, template, field.getPath()); } private static int updateOrdinal(Map<String, Integer> pathOrdinal, String path) { path = JsonMetadataConstants.removeLastNumeratedPathPart(path); int ordinal = 0; if (pathOrdinal.containsKey(path)) { ordinal = pathOrdinal.get(path) + 1; } pathOrdinal.put(path, ordinal); return ordinal; } public static RootObj getRootObjFromTree(final RootObj rootobj, final TemplateTree tree, final boolean prune) { final RootObj result = new RootObj(rootobj); for (SuperBlock sb : result.getSuperBlocks()) { final List<BlockObj> children = new ArrayList<>(sb.getChildren()); int blockCount = 0; for (BlockObj block : children) { blockCount = updateRootObjFromTree(block, sb, tree, blockCount); blockCount++; if (prune && block.getBlock().childrenEmpty()) { sb.removeBlock(block); blockCount--; } } if (sb.childrenEmpty()) { result.getRoot().remove(sb); } } return result; } private static int updateRootObjFromTree(final BlockObj blockObj, final IBlock owner, final TemplateTree tree, int blockCount) { Block block = blockObj.getBlock(); final Block origBlock = new Block(block); final List<ValueNode> blockNodes = tree.getNodesForBlock(block); if (blockNodes.isEmpty()) { owner.removeBlock(blockObj); blockCount--; } for (int i = 0; i < blockNodes.size(); i++) { final ValueNode node = blockNodes.get(i); if (i > 0) { block = owner.addBlock(blockCount + 1, new Block(origBlock)); blockCount++; } if (node != null) { block.setPath(node.getNumeratedPath()); } final List<ComponentObj> blockChildren = new ArrayList<>(block.getChildren()); int fieldCount = 0; for (ComponentObj child : blockChildren) { if (child instanceof FieldObj) { fieldCount = updateRootObjFromTree((FieldObj) child, block, tree, node, fieldCount); } else { fieldCount = updateRootObjFromTree((BlockObj) child, block, tree, fieldCount); } fieldCount++; } } return blockCount; } private static int updateRootObjFromTree(final FieldObj fieldObj, final Block owner, final TemplateTree tree, final ValueNode node, int fieldCount) { Field field = fieldObj.getField(); final List<ValueNode> fieldNodes = tree.getNodesByFieldAndParent(field.getName(), node); if (fieldNodes.isEmpty()) { owner.removeField(fieldObj); } for (int j = 0; j < fieldNodes.size(); j++) { final ValueNode childNode = fieldNodes.get(j); if (j > 0) { if (field.getMultiplicity() > 1) { field = owner.addField(fieldCount + 1, new Field(field)); fieldCount++; } else { LOGGER.log(Level.INFO, "field value excluded for multiplicity purpose:{0}", field.getPath()); continue; } } field.setPath(childNode.getNumeratedPath()); field.setValue(childNode.value); } return fieldCount; } public static void pruneTree(TemplateTree tree, final ValueNode node) { List<ValueNode> toRemove = new ArrayList<>(); for (ValueNode child : node.children) { pruneTree(tree, child); if (child.isField()) { if (child.value == null || child.value.isEmpty()) { toRemove.add(child); } } } if (!toRemove.isEmpty()) { node.children.removeAll(toRemove); tree.nodes.removeAll(toRemove); } } }