/******************************************************************************* * Copyright 2006 - 2012 Vienna University of Technology, * Department of Software Technology and Interactive Systems, IFS * * 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 eu.scape_project.planning.xml.freemind; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.scape_project.planning.manager.CriteriaManager; import eu.scape_project.planning.model.Policy; import eu.scape_project.planning.model.PolicyNode; import eu.scape_project.planning.model.kbrowser.CriteriaLeaf; import eu.scape_project.planning.model.kbrowser.CriteriaNode; import eu.scape_project.planning.model.kbrowser.CriteriaTreeNode; import eu.scape_project.planning.model.measurement.Measure; import eu.scape_project.planning.model.scales.BooleanScale; import eu.scape_project.planning.model.scales.FreeStringScale; import eu.scape_project.planning.model.scales.IntRangeScale; import eu.scape_project.planning.model.scales.OrdinalScale; import eu.scape_project.planning.model.scales.PositiveFloatScale; import eu.scape_project.planning.model.scales.RestrictedScale; import eu.scape_project.planning.model.scales.Scale; import eu.scape_project.planning.model.scales.YanScale; import eu.scape_project.planning.model.tree.Leaf; import eu.scape_project.planning.model.tree.TreeNode; /** * Helper class for importing FreeMind MindMaps into the objective tree structure of Plato. * This class holds both text and a list of children. * All the getter,setter and adder are for the Digester import, * createNode does the real work. * @author Christoph Becker * @see eu.scape_project.planning.xml.TreeLoader#loadFreeMindPolicyMap(java.io.InputStream) */ public class Node { private static Logger log = LoggerFactory.getLogger(Node.class); /** * property corresponding to a freemind xml mindmap */ private String TEXT; /** * property corresponding to a freemind xml mindmap */ private String CREATED; /** * property corresponding to a freemind xml mindmap */ private String MODIFIED; private String DESCRIPTION; public String getDESCRIPTION() { return DESCRIPTION; } public void setDESCRIPTION(String description) { DESCRIPTION = description; } private List<Node> children = new ArrayList<Node>(); private Node parent; public Node getParent() { return parent; } public void setParent(Node parent) { this.parent = parent; } public boolean isLeaf() { return children.isEmpty(); } /** * creates an appropriate scale out of myself, * one of the subtypes in the package eu.scape_project.planning.model.scales * @return {@link Scale} */ public Scale createScale() { try { if (TEXT == null) { return null; } if (("Y/N".equals(TEXT)) || ("Yes/No".equals(TEXT))){ return new BooleanScale(); } YanScale yan = new YanScale(); if ((yan.getRestriction().equals(TEXT)) || ("Y/A/N".equals(TEXT))) { return yan; } if ("?".equals(TEXT)) { return null; } if ("FREE TEXT".equals(TEXT.toUpperCase())) { return new FreeStringScale(); } if (TEXT.indexOf(Scale.SEPARATOR) != -1 ) { RestrictedScale v = null; // try { // v = new IntRangeScale(); // v.setRestriction(TEXT); // } catch (IllegalArgumentException e) { // // do nothing, that's ok, thrown by IntRange - // // it means we don't have numbers, but an ordinary scale. // // proceed: // v = new OrdinalScale(); // v.setRestriction(TEXT); // } v = new IntRangeScale(); if (! ((IntRangeScale)v).validateAndSetRestriction(TEXT)) { v= new OrdinalScale(); v.setRestriction(TEXT); } return v; } } catch (Exception e) { log.warn("invalid scale format, ignoring: "+TEXT,e); } //default behaviour: float scale and the TEXT as unit PositiveFloatScale v = new PositiveFloatScale(); v.setUnit(TEXT); return v; } /** * Creates a complete {@link eu.scape_project.planning.model.tree.TreeNode} out of myself * and my children. Works recursively! * So calling this on the root actually constructs the whole Plato-conforming * ObjectiveTree out of a FreeMind MindMap. * @return {@link Node} or {@link Leaf} * @param hasUnits If this is set to true, the leafs are ignored. * This can be useful if the tree to be imported already contains all the measurement * units, but defined as leaf nodes (We found that to be very useful during workshops.) * The measurement units are not imported yet if it is set to true, we might want to * add this at a later stage. */ public TreeNode createNode(boolean hasUnits, boolean hasLeaves) { if (hasLeaves && isLeaf()) { // i am a leaf, so create a Leaf. // this is called only if hasUnits == false, see below. // So we don't need to check that again. Leaf leaf = new Leaf(); setNameAndWeight(leaf); setDescription(leaf); setMIU(leaf); return leaf; } else { // We start with assuming that I'm a Node TreeNode node = new eu.scape_project.planning.model.tree.Node(); setNameAndWeight(node); setDescription(node); for (Node n : children) { if (hasLeaves && n.isLeaf()) { // Case 1: we don't have units in the tree, so n is in fact a LEAF // and I am a NODE as assumed above if (!hasUnits) { Leaf leaf = new Leaf(); n.setNameAndWeight(leaf); n.setDescription(leaf); n.setMIU(leaf); ((eu.scape_project.planning.model.tree.Node)node).addChild(leaf); } else { // Case 2: we have units, so n is the SCALE of myself // - but this means that I AM a LEAF // and that we can finish recursion node = new Leaf(); setNameAndWeight(node); setDescription(node); Leaf leaf = (Leaf) node; leaf.changeScale(n.createScale()); setMIU(leaf); return leaf; // == node } } else { ((eu.scape_project.planning.model.tree.Node)node).addChild(n.createNode(hasUnits,hasLeaves)); } } return node; } } public PolicyNode createPolicyNode() { if (isLeaf()) { Policy p = new Policy(); p.setValue(getTEXT()); return p; } else { PolicyNode node = new PolicyNode(); node.setName(getTEXT()); for (Node n : children) { if (n.isLeaf()) { Policy p = new Policy(); p.setValue(n.getTEXT()); //Policy p = (Policy)n.createPolicyNode(); p.setName(getTEXT()); return p; } else { node.addChild(n.createPolicyNode()); } } return node; } } /** * Method responsible for converting this node (and its successors) into corresponding CriteriaTreeNodes. * * @param criteriaManager Class used to map CriteriaLeaves to correct criterion. * @return Node (and its successors) converted into corresponding a CriteriaTreeNodes. */ public CriteriaTreeNode createCriteriaTreeNode(CriteriaManager criteriaManager) { // Leaf if (isLeaf()) { CriteriaLeaf leaf = new CriteriaLeaf(); // Hint: If the node is mapped its text has a specific format (my property name|outcome:format/image/width) // We have to check this int stringLength = TEXT.length(); int mappingSeparatorIndex = TEXT.lastIndexOf('|'); // node has no mapping specified if (mappingSeparatorIndex == -1 || mappingSeparatorIndex == (stringLength - 1)) { leaf.setMapped(false); leaf.setName(TEXT); } // node has mapping specified else { String name = TEXT.substring(0, mappingSeparatorIndex); String criterionUri = TEXT.substring(mappingSeparatorIndex + 1); leaf.setName(name); // set criterion only if we can identify it Measure crit = criteriaManager.getMeasure(criterionUri); if (crit != null) { leaf.setMapped(true); leaf.setMeasure(new Measure(crit)); } else { leaf.setMapped(false); } } return leaf; // Node } else { CriteriaNode node = new CriteriaNode(); node.setName(TEXT); for (Node n : children) { node.addChild(n.createCriteriaTreeNode(criteriaManager)); } return node; } } /** * sets the name and weight of a TreeNode from MY members * @param node */ private void setNameAndWeight(TreeNode node) { int k = TEXT.indexOf("%"); double weight = 0.0; if (k != -1) { try { // we have weighting in the tree: // remove and set weighting, continue. if ( k == TEXT.length()-1) { int l = TEXT.lastIndexOf(' '); weight = Double.parseDouble(TEXT.substring(l+1,k).trim()) / 100; setTEXT(TEXT.substring(0,l)); } else { weight = Double.parseDouble(TEXT.substring(0,k)) / 100; setTEXT(TEXT.substring(k+1)); } } catch (Exception e) { log.error("could not import weighting: "+e.getMessage(),e); } } node.setWeight(weight); node.setName(TEXT); } /** * sets the description of a provided Treenode from MY {@link #DESCRIPTION} * and takes care to remove potentiall included measuredProperty=xxx from this description * @param node */ public void setDescription(TreeNode node) { if (DESCRIPTION == null) { return; } String key = "measureId="; int idx = DESCRIPTION.indexOf(key); if (idx > -1) { int newLineIndex = DESCRIPTION.indexOf('\n'); if (newLineIndex > -1) { node.setDescription(DESCRIPTION.substring(newLineIndex+1)); } } else { node.setDescription(DESCRIPTION); } } /** * sets the measured property on a provided Leaf (!!) from my {@link #DESCRIPTION} * @param leaf */ public void setMIU(Leaf leaf) { String descr = DESCRIPTION; if (descr != null) { String key = "measureId="; int idx = descr.indexOf(key); if (idx > -1) { int endIdx = descr.indexOf("\n", idx+key.length()); if (endIdx == -1) { endIdx = descr.length(); } else { endIdx = endIdx-1; } String mInfo = descr.substring(idx+key.length(), endIdx); try { // TODO: Do we really need and use this functionality? //leaf.getCriterion().fromUri(mInfo); } catch (IllegalArgumentException e) { log.debug("Invalid measurement info for leaf: " + TEXT + "=" + mInfo); } int newLineIndex = DESCRIPTION.indexOf('\n'); if (newLineIndex > -1) { descr = DESCRIPTION.substring(newLineIndex+1); } else { descr = ""; } } } } public void addChild(Node n) { children.add(n); n.setParent(this); } public List<Node> getChildren() { return children; } public void setChildren(List<Node> children) { this.children = children; } public String getCREATED() { return CREATED; } public void setCREATED(String created) { CREATED = created; } public String getMODIFIED() { return MODIFIED; } public void setMODIFIED(String modified) { MODIFIED = modified; } public String getTEXT() { return TEXT; } public void setTEXT(String text) { TEXT = text; } }