Java tutorial
/******************************************************************************* * Copyright (C) 2013, 2014, 2015 by Inria and Paris-Sud University * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package fr.inria.oak.paxquery.common.xml.navigation; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.BitSet; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class models the navigation tree patterns. * */ public final class NavigationTreePattern implements Serializable { /** * Universal version identifier for TreePattern class */ private static final long serialVersionUID = 6770099296197279852L; private static final Log logger = LogFactory.getLog(NavigationTreePattern.class); private static String graphicsPath = "."; private static boolean printGraphics = true; private static int printCardinal = 0; /** * Unique identifier for the Pattern */ private long patternID; /** * The name of the Pattern */ private String name; private NavigationTreePatternNode root; private boolean ordered; private BitSet paths; public NavigationTreePattern() { } public NavigationTreePattern(NavigationTreePatternNode r, boolean ordered) { this.root = r; this.ordered = ordered; } public long getPatternID() { return patternID; } public void setPatternID(long patternID) { this.patternID = patternID; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void setGraphicsPath(String folderName) { graphicsPath = folderName; } public static void setPrintGraphics(boolean doPrint) { printGraphics = doPrint; } public static void resetPrintCardinal() { setPrintCardinal(0); } public static void setPrintCardinal(int cardinal) { printCardinal = cardinal; } /** * @return the ordered */ public boolean isOrdered() { return ordered; } /** * @param ordered the ordered to set */ public void setOrdered(boolean ordered) { this.ordered = ordered; } /** * @param root the root to set */ public void setRoot(NavigationTreePatternNode root) { this.root = root; this.root.setNestingDepth(0); } /** * Returns the XamNode which is the "top" node of this xam * * @return */ public NavigationTreePatternNode getRoot() { return root; } /** * Counts the number of nodes in this subtree. Does not count the root. * * @return number of nodes for which there will be stacks. */ public int getNodesNo() { return (root.getNumberOfNodes() - 1); } /** * Counts the number of nodes in this subtree that return an * ID, Cont, Val. Considers the root in the counting * * @return number of nodes for which there will be stacks. */ public int getRetNodesNo() { return (root.getNumberOfReturningNodes()); } /** * Gets the complete list of nodes that belong to this tree pattern. * @return the list of nodes */ public LinkedList<NavigationTreePatternNode> getNodes() { if (root != null) return root.getNodes(); return null; } /** * The following method is used for obtaining the simplify representation * of a tree pattern. It is equivalent to calling the method * {@link #toString(PrintingLevel.SIMPLIFY)}. */ public String toString() { return (this.root.treeToString(PrintingLevel.SIMPLIFY)); } /** * The following method is used for obtaining the representation * of a tree pattern. * We can specify several level of details while transforming the * tree into a String. * The levels of detail are defined in the {@link PrintingLevel} * enumeration. * * @param level of detail * @return the tree representation */ public String toString(PrintingLevel level) { return (this.root.treeToString(level)); } /** * The following method is used for displaying the simplify representation * of a tree pattern. */ public void display() { if (logger.isInfoEnabled()) logger.info("#>#> " + this.root.treeToString(PrintingLevel.SIMPLIFY)); } /** * Get a String ready for using in dot and get an image representation of * the pattern. * @param givenFilename * @return */ public String getDotString(String givenFilename, String backgroundColor, String foregroundColor) { StringBuffer sb = new StringBuffer(); sb.append("graph " + givenFilename + "{\n"); sb.append("graph[label=\""); sb.append(name); sb.append("\" fontsize=22 labeljust=center]\n"); sb.append("node [fontname=\"LucidaGrande\" fontsize=18 color=\"white\"]\n"); this.root.drawTree(sb, backgroundColor, foregroundColor); sb.append("}\n"); return new String(sb); } /** * Method that creates an image representation of the pattern and stores it to a file * @param givenFilename the name of the file where the image will be saved * @param query true if the pattern that we want to draw is a query or false otherwise. * Query nodes will be filled in yellow and view nodes will be filled in blue */ public void draw(String imagesPath, String givenFilename, boolean query, String backgroundColor, String foregroundColor) { String fileName; Calendar cal = new GregorianCalendar(); if (givenFilename == null) { if (query) fileName = "xam-" + "-" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + "-Query"; else fileName = "xam-" + "-" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND); } else { if (query) fileName = givenFilename + "-Query"; else fileName = givenFilename; } String sb = getDotString(fileName, backgroundColor, foregroundColor); try { String fileNameDot = new String(fileName + ".dot"); String fileNamePNG; if (fileName.contains("/")) //fileNamePNG = new String(fileName + ".pdf"); fileNamePNG = new String(fileName + ".png"); else //fileNamePNG = new String(imagesPath + File.separator + fileName + ".pdf"); fileNamePNG = new String(imagesPath + File.separator + fileName + ".png"); FileWriter file = new FileWriter(fileNameDot); // writing the .dot file to disk file.write(sb); file.close(); // calling GraphViz Runtime r = Runtime.getRuntime(); //String com = new String("/usr/local/bin/dot -Tpdf " + fileNameDot + " -o " + fileNamePNG); String com = new String("dot -Tpng " + fileNameDot + " -o " + fileNamePNG); Process p = r.exec(com); p.waitFor(); // removing the .dot file //Process p2=r.exec("rm "+fileNameDot+"\n"); //p2.waitFor(); } catch (IOException e) { logger.error("IOException: ", e); } catch (InterruptedException e) { logger.error("InterruptedException: ", e); } } public void draw(boolean query, String backgroundColor, String foregroundColor) { if (printGraphics == false) return; String fileName; /*Calendar cal = new GregorianCalendar(); if(givenFilename == null) { if(query) fileName = "xam-" + "-" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + "-Query"; else fileName = "xam-" + "-" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND); } else { if(query) fileName = givenFilename + "-Query"; else fileName = givenFilename; }*/ if (graphicsPath.endsWith("/") == true) fileName = graphicsPath + "tp" + printCardinal; else fileName = graphicsPath + "/tp" + printCardinal; String sb = getDotString("tp" + printCardinal, backgroundColor, foregroundColor); printCardinal++; try { String fileNameDot = new String(fileName + ".dot"); String fileNamePNG = new String(fileName + ".png"); /*String fileNamePNG; if(fileName.contains("/")) //fileNamePNG = new String(fileName + ".pdf"); fileNamePNG = new String(fileName + ".png"); else //fileNamePNG = new String(imagesPath + File.separator + fileName + ".pdf"); fileNamePNG = new String(imagesPath + File.separator + fileName + ".png");*/ FileWriter file = new FileWriter(fileNameDot); // writing the .dot file to disk file.write(sb); file.close(); // calling GraphViz Runtime r = Runtime.getRuntime(); //String com = new String("/usr/local/bin/dot -Tpdf " + fileNameDot + " -o " + fileNamePNG); String com = new String("dot -Tpng " + fileNameDot + " -o " + fileNamePNG); Process p = r.exec(com); p.waitFor(); // removing the .dot file //Process p2=r.exec("rm "+fileNameDot+"\n"); //p2.waitFor(); } catch (IOException e) { logger.error("IOException: ", e); } catch (InterruptedException e) { logger.error("InterruptedException: ", e); } } public final NavigationTreePattern deepCopy() { NavigationTreePattern p = new NavigationTreePattern(this.root.deepCopy(), this.ordered); p.name = this.name; return p; } /** * Gives fresh numbers to all nodes in a tree pattern. * */ public void renumberNodes() { Stack<NavigationTreePatternNode> st = new Stack<NavigationTreePatternNode>(); st.push(this.root); while (!st.empty()) { NavigationTreePatternNode pn = st.pop(); if (pn.getNodeCode() == -1) { // nothing //Parameters.logger.debug("-1 node! Left unchanged"); } else { pn.setNodeCode(NavigationTreePatternNode.globalNodeCounter.getAndIncrement()); } Iterator<NavigationTreePatternEdge> ie = pn.getEdges().iterator(); while (ie.hasNext()) { st.push(ie.next().n2); } } } /** * Same as {@link #renumberNodes()}, but it starts the numbering from the localCounter. * * @author Konstantinos KARANASOS */ public void renumberNodes(int localCounter) { Stack<NavigationTreePatternNode> st = new Stack<NavigationTreePatternNode>(); st.push(this.root); while (!st.empty()) { NavigationTreePatternNode pn = st.pop(); if (pn.getNodeCode() == -1) { // nothing //Parameters.logger.debug("-1 node! Left unchanged"); } else { pn.setNodeCode(localCounter); localCounter++; } Iterator<NavigationTreePatternEdge> ie = pn.getEdges().iterator(); while (ie.hasNext()) { st.push(ie.next().n2); } } } /** * Calls {@link #renumberNodes(int)}, using as localCounter the nodeCode of the first node of the given Pattern. * However, if the nodeCode of the first nodes (immediately below the root) of the two patterns are the same, the * renumbering is not performed (so as to avoid unnecessary renumbering). * @author Konstantinos KARANASOS */ public void renumberNodes(NavigationTreePattern patToUse) { int patToUseFirstNodeCode = patToUse.getRoot().getEdges().get(0).n2.getNodeCode(); if (this.getRoot().getEdges().get(0).n2.getNodeCode() != patToUseFirstNodeCode) renumberNodes(patToUse.getRoot().getEdges().get(0).n2.getNodeCode()); } /** * Method that transforms the tree pattern its equivalent in a XAM file. * * @return the tree pattern representation for a XAM file */ public String toXAMString() { StringBuffer nodeBuffer = new StringBuffer(); nodeBuffer.append(this.ordered ? "o" : ""); NavigationTreePatternEdge rootEdge = ((NavigationTreePatternEdge) this.root.getEdges().get(0)); nodeBuffer.append(rootEdge.isParent() ? "/\n" : ""); StringBuffer edgeBuffer = new StringBuffer(); NavigationTreePatternNode realRoot = rootEdge.n2; realRoot.toXAMString(nodeBuffer, edgeBuffer, 0, 1); return new String(nodeBuffer) + "\n;\n" + new String(edgeBuffer); } public BitSet getBitMap() { return paths; } public void setPathBitMap(BitSet arg0) { this.paths = arg0; } /** * Returns a xam that contains all my nodes except the hierarchy under n. * In other words: all n ancestors and their descendants except for those that are also * the descendants of n. * @param n * @return */ public NavigationTreePattern getCopyAbove(NavigationTreePatternNode n) { //NodeInformation.Parameters.logger.debug("Getting copy of " + this.toString() + " above XAMNodeHashCode: " + // n.XAMNodeHashCode + " nodeCode: " + n.nodeCode); NavigationTreePattern p = this.deepCopy(); NavigationTreePatternNode copyOfN = p.root.locate(n.getNodeCode()); assert (copyOfN != null) : "Erroneous call to getCopyAbove"; copyOfN.cleanEdges(); return p; } public NavigationTreePattern getPathAbove(NavigationTreePatternNode n1) { NavigationTreePattern p = this.deepCopy(); p.root.pruneAllButPathTo(n1); return p; } public void minimize() { // TODO logical minimization a la Amer-Yahia } public ArrayList<String> getColumnsNames() { LinkedList<NavigationTreePatternNode> nodes = this.getNodes(); LinkedList<String> list = new LinkedList<String>(); list.add("Document ID"); for (NavigationTreePatternNode node : nodes) list.addAll(node.getColumnsName()); return new ArrayList<String>(list); } public List<Boolean> getColumnsStoringValue() { LinkedList<NavigationTreePatternNode> nodes = this.getNodes(); List<Boolean> list = new ArrayList<Boolean>(); list.add(false); for (NavigationTreePatternNode node : nodes) list.addAll(node.getColumnsStoringValue()); return list; } public List<Boolean> getColumnsBelongingToAttribute() { LinkedList<NavigationTreePatternNode> nodes = this.getNodes(); List<Boolean> list = new ArrayList<Boolean>(); list.add(false); for (NavigationTreePatternNode node : nodes) list.addAll(node.getColumnsBelongingToAttribute()); return list; } /** * This method will return an {@link ArrayList} containing 3 strings each representing * a transcription of the TreePattern into a aquax expression. * The first string will contain the only the needed nodes from the pattern. * The second string will contain all the nodes in the pattern. * The third string will have the needed nodes as mandatory and the others as optional. * * @return an {@link ArrayList} containing 3 {@link String} objects with the significance described above. */ public ArrayList<String> convertToAquax() { // the list that will contain the aquax form for the pattern ArrayList<String> aquaxTranslations = new ArrayList<String>(); LinkedList<NavigationTreePatternNode> optionalNodes = this.getNodes(); LinkedList<NavigationTreePatternNode> neededNodes = new LinkedList<NavigationTreePatternNode>(); for (NavigationTreePatternNode node : optionalNodes) { if (node.nodeStoresSomething()) { neededNodes.add(node); } } optionalNodes.removeAll(neededNodes); // creating the first string that contains all the needed Nodes StringBuffer sb = new StringBuffer(), sb2 = new StringBuffer(); for (NavigationTreePatternNode n : neededNodes) { if (n.getParentEdge().n1.getNodeCode() == -1) sb.append(n.getNodeCode() + ":" + 0 + ":m " + n.getTag() + " "); else sb.append(n.getNodeCode() + ":" + n.getParentEdge().n1.getNodeCode() + ":m " + n.getTag() + " "); } aquaxTranslations.add(sb.toString()); sb2.append(sb); // second and third strings containing both the needed and the other nodes, in one as mandatory, in the other as optional for (NavigationTreePatternNode n : optionalNodes) { if (n.getParentEdge().n1.getNodeCode() == -1) { sb.append(n.getNodeCode() + ":" + 0 + ":m " + n.getTag() + " "); sb2.append(n.getNodeCode() + ":" + 0 + ":o " + n.getTag() + " "); } else { sb.append(n.getNodeCode() + ":" + n.getParentEdge().n1.getNodeCode() + ":m " + n.getTag() + " "); sb2.append(n.getNodeCode() + ":" + n.getParentEdge().n1.getNodeCode() + ":o " + n.getTag() + " "); } } aquaxTranslations.add(sb.toString()); aquaxTranslations.add(sb2.toString()); return aquaxTranslations; } /** * Gets the normal form of a tree pattern. * @return the normal tree pattern */ public NavigationTreePattern getNormalizedTreePattern() { NavigationTreePattern normalizedTreePattern = deepCopy(); int counter = 1; TreeSet<NavigationTreePatternNode> sortedSetChildrenNodes = new TreeSet<NavigationTreePatternNode>(); int newCounter = counter + normalizedTreePattern.root.getEdges().size(); for (NavigationTreePatternEdge edge : normalizedTreePattern.root.getEdges()) { newCounter = recGetNormalizedTreePattern(edge.n2, newCounter); sortedSetChildrenNodes.add(edge.n2); } normalizedTreePattern.root.cleanEdges(); for (NavigationTreePatternNode node : sortedSetChildrenNodes.descendingSet()) { node.setNodeCode(counter); counter++; node.getParentEdge().n1.addEdge(node.getParentEdge()); } return normalizedTreePattern; } /** * Recursive method called in order to create the normalized tree pattern. * * @param node a tree pattern node, we will order its children * @param counter the value of the counter that we are using for renumbering the tree pattern nodes * @return the counter that we are using for renumbering the tree pattern nodes */ private int recGetNormalizedTreePattern(NavigationTreePatternNode node, int counter) { if (node.getEdges().isEmpty()) return counter; TreeSet<NavigationTreePatternNode> sortedSetChildrenNodes = new TreeSet<NavigationTreePatternNode>(); int newCounter = counter + node.getEdges().size(); for (NavigationTreePatternEdge edge : node.getEdges()) { newCounter = recGetNormalizedTreePattern(edge.n2, newCounter); sortedSetChildrenNodes.add(edge.n2); } node.cleanEdges(); for (NavigationTreePatternNode child : sortedSetChildrenNodes.descendingSet()) { child.setNodeCode(counter); counter++; child.getParentEdge().n1.addEdge(child.getParentEdge()); } return newCounter; } public NavigationTreePattern deepCopyWithReplace(NavigationTreePatternNode unfoldingNode, NavigationTreePatternNode pNew) { return new NavigationTreePattern(root.deepCopyWithReplace(unfoldingNode, pNew), this.ordered); } public void parseUnrequiredData(String tagOfTuplesToKeep) { this.root.parseUnrequiredData(tagOfTuplesToKeep); } /** * Goes through the tree (depth-first) and calls NavigationTreePatternNode.setStoresID(true) on nodes storing content or value. * Returns a list of variables: each variable points to a "store ID" column for each node in the TP. These variables are added * to their corresponding node by calling the method NavigationTreePatternNode.addMatchingVariables(Variable...) on the node * Recursive. */ public ArrayList<Variable> markAsStoresIDWhereNeeded() { ArrayList<Variable> newVariables = new ArrayList<Variable>(); //new variables added, they point to node IDs markAsStoresIDWhereNeeded(root, newVariables); return newVariables; } private void markAsStoresIDWhereNeeded(NavigationTreePatternNode node, ArrayList<Variable> newVariables) { if (node.getNestingDepth() == 0 && (node.storesContent() || node.storesValue())) { node.setStoresID(true); node.setIDType(false, false, true, false); if (node.getMatchingVariablesSize() > 0) { String newVarName = "IDNode-" + node.getMatchingVariables().get(0).name; Variable newVar = new Variable(newVarName, Variable.VariableDataType.NodeID); node.addMatchingVariables(newVar); newVariables.add(newVar); } } ArrayList<NavigationTreePatternEdge> edges = node.getEdges(); if (edges != null) { for (NavigationTreePatternEdge edge : edges) { markAsStoresIDWhereNeeded(edge.n2, newVariables); } } } /** * Returns a list with all the variables contained in this Tree Pattern. * The tree is gone through in pre-order, and for each node the sorting is done as {ID, Value, Cont} * @return */ public ArrayList<Variable> getAllVariables() { ArrayList<Variable> variables = new ArrayList<Variable>(); getVariables(root, variables, true, true, true); return variables; } /** * Returs a list with variables satisfying the criteria indicated by the params * @param includeIDVars vars with datatype Variable.VariableDataType.NodeID * @param includeValueVars vars with datatype Variable.VariableDataType.Value * @param includeContentVars vars with datatype Variable.VariabeDataType.Content * @return the list the variables that satisfy the criteria indicated by the params */ public ArrayList<Variable> getVariables(boolean includeIDVars, boolean includeValueVars, boolean includeContentVars) { ArrayList<Variable> variables = new ArrayList<Variable>(); getVariables(root, variables, includeIDVars, includeValueVars, includeContentVars); return variables; } private void getVariables(NavigationTreePatternNode node, ArrayList<Variable> variables, boolean includeIDVars, boolean includeValueVars, boolean includeContentVars) { //visit this node if (includeIDVars) variables.addAll(node.getMatchingVariablesStoringID()); if (includeValueVars) variables.addAll(node.getMatchingVariablesStoringValue()); if (includeContentVars) variables.addAll(node.getMatchingVariablesStoringContent()); //visit children ArrayList<NavigationTreePatternEdge> edges = node.getEdges(); if (edges != null) { for (NavigationTreePatternEdge edge : edges) getVariables(edge.n2, variables, includeIDVars, includeValueVars, includeContentVars); } } }