/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package edu.mit.csail.sdg.alloy4viz; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import edu.mit.csail.sdg.alloy4.Util; import edu.mit.csail.sdg.alloy4.XMLNode; import edu.mit.csail.sdg.alloy4graph.DotColor; import edu.mit.csail.sdg.alloy4graph.DotPalette; import edu.mit.csail.sdg.alloy4graph.DotShape; import edu.mit.csail.sdg.alloy4graph.DotStyle; /** * This utility class contains methods to read and write VizState * customizations. * <p> * <b>Thread Safety:</b> Can be called only by the AWT event thread. */ public final class StaticThemeReaderWriter { /** * Constructor is private, since this utility class never needs to be * instantiated. */ private StaticThemeReaderWriter() {} /** * Read the XML file and merge its settings into an existing VizState object. */ public static void readAlloy(String filename, VizState theme) throws IOException { File file = new File(filename); try { XMLNode elem = new XMLNode(file); for (XMLNode sub : elem.getChildren("view")) parseView(sub, theme); } catch (Throwable e) { throw new IOException("The file \"" + file.getPath() + "\" is not a valid XML file, or an error occurred in reading."); } } /** * Write the VizState's customizations into a new file (which will be * overwritten if it exists). */ public static void writeAlloy(String filename, VizState theme) throws IOException { PrintWriter bw = new PrintWriter(filename, "UTF-8"); bw.write("<?xml version=\"1.0\"?>\n<alloy>\n\n"); if (theme != null) { try { writeView(bw, theme); } catch (IOException ex) { Util.close(bw); throw new IOException("Error writing to the file \"" + filename + "\""); } } bw.write("\n</alloy>\n"); if (!Util.close(bw)) throw new IOException("Error writing to the file \"" + filename + "\""); } /* * ========================================================= ================ * =================== */ /** Does nothing if the element is malformed. */ private static void parseView(final XMLNode x, VizState now) { /* * <view orientation=".." nodetheme=".." edgetheme=".." hidePrivate="yes/no" * hideMeta="yes/no" useOriginalAtomNames="yes/no" fontsize="12"> <projection> * .. </projection> <defaultnode../> <defaultedge../> 0 or more NODE or EDGE * </view> */ if (!x.is("view")) return; for (XMLNode xml : x) { if (xml.is("projection")) { now.deprojectAll(); for (AlloyType t : parseProjectionList(now, xml)) now.project(t); } } if (has(x, "useOriginalAtomNames")) now.useOriginalName(getbool(x, "useOriginalAtomNames")); if (has(x, "hidePrivate")) now.hidePrivate(getbool(x, "hidePrivate")); if (has(x, "hideMeta")) now.hideMeta(getbool(x, "hideMeta")); if (has(x, "fontsize")) now.setFontSize(getint(x, "fontsize")); if (has(x, "nodetheme")) now.setNodePalette(parseDotPalette(x, "nodetheme")); if (has(x, "edgetheme")) now.setEdgePalette(parseDotPalette(x, "edgetheme")); for (XMLNode xml : x) { if (xml.is("defaultnode")) parseNodeViz(xml, now, null); else if (xml.is("defaultedge")) parseEdgeViz(xml, now, null); else if (xml.is("node")) { for (XMLNode sub : xml.getChildren("type")) { AlloyType t = parseAlloyType(now, sub); if (t != null) parseNodeViz(xml, now, t); } for (XMLNode sub : xml.getChildren("set")) { AlloySet s = parseAlloySet(now, sub); if (s != null) parseNodeViz(xml, now, s); } } else if (xml.is("edge")) { for (XMLNode sub : xml.getChildren("relation")) { AlloyRelation r = parseAlloyRelation(now, sub); if (r != null) parseEdgeViz(xml, now, r); } } } } /* * ========================================================= ================ * =================== */ /** Writes nothing if the argument is null. */ private static void writeView(PrintWriter out, VizState view) throws IOException { if (view == null) return; VizState defaultView = new VizState(view.getOriginalInstance()); out.write("<view"); writeDotPalette(out, "nodetheme", view.getNodePalette(), defaultView.getNodePalette()); writeDotPalette(out, "edgetheme", view.getEdgePalette(), defaultView.getEdgePalette()); if (view.useOriginalName() != defaultView.useOriginalName()) { out.write(" useOriginalAtomNames=\""); out.write(view.useOriginalName() ? "yes" : "no"); out.write("\""); } if (view.hidePrivate() != defaultView.hidePrivate()) { out.write(" hidePrivate=\""); out.write(view.hidePrivate() ? "yes" : "no"); out.write("\""); } if (view.hideMeta() != defaultView.hideMeta()) { out.write(" hideMeta=\""); out.write(view.hideMeta() ? "yes" : "no"); out.write("\""); } if (view.getFontSize() != defaultView.getFontSize()) { out.write(" fontsize=\"" + view.getFontSize() + "\""); } out.write(">\n"); if (view.getProjectedTypes().size() > 0) writeProjectionList(out, view.getProjectedTypes()); out.write("\n<defaultnode" + writeNodeViz(view, defaultView, null)); out.write("/>\n\n<defaultedge" + writeEdgeViz(view, defaultView, null)); out.write("/>\n"); // === nodes === Set<AlloyNodeElement> types = new TreeSet<AlloyNodeElement>(); types.addAll(view.getOriginalModel().getTypes()); types.addAll(view.getCurrentModel().getTypes()); types.addAll(view.getOriginalModel().getSets()); types.addAll(view.getCurrentModel().getSets()); Map<String,Set<AlloyNodeElement>> viz2node = new TreeMap<String,Set<AlloyNodeElement>>(); for (AlloyNodeElement t : types) { String str = writeNodeViz(view, defaultView, t); Set<AlloyNodeElement> nodes = viz2node.get(str); if (nodes == null) viz2node.put(str, nodes = new TreeSet<AlloyNodeElement>()); nodes.add(t); } for (Map.Entry<String,Set<AlloyNodeElement>> e : viz2node.entrySet()) { out.write("\n<node" + e.getKey() + ">\n"); for (AlloyNodeElement ts : e.getValue()) { if (ts instanceof AlloyType) writeAlloyType(out, (AlloyType) ts); else if (ts instanceof AlloySet) writeAlloySet(out, (AlloySet) ts); } out.write("</node>\n"); } // === edges === Set<AlloyRelation> rels = new TreeSet<AlloyRelation>(); rels.addAll(view.getOriginalModel().getRelations()); rels.addAll(view.getCurrentModel().getRelations()); Map<String,Set<AlloyRelation>> viz2edge = new TreeMap<String,Set<AlloyRelation>>(); for (AlloyRelation r : rels) { String str = writeEdgeViz(view, defaultView, r); if (str.length() == 0) continue; Set<AlloyRelation> edges = viz2edge.get(str); if (edges == null) viz2edge.put(str, edges = new TreeSet<AlloyRelation>()); edges.add(r); } for (Map.Entry<String,Set<AlloyRelation>> e : viz2edge.entrySet()) { out.write("\n<edge" + e.getKey() + ">\n"); for (AlloyRelation r : e.getValue()) writeAlloyRelation(out, r); out.write("</edge>\n"); } // === done === out.write("\n</view>\n"); } /* * ========================================================= ================ * =================== */ /** Return null if the element is malformed. */ private static AlloyType parseAlloyType(VizState now, XMLNode x) { /* * class AlloyType implements AlloyNodeElement { String name; } <type * name="the type name"/> */ if (!x.is("type")) return null; String name = x.getAttribute("name"); if (name.length() == 0) return null; else return now.getCurrentModel().hasType(name); } /** Writes nothing if the argument is null. */ private static void writeAlloyType(PrintWriter out, AlloyType x) throws IOException { if (x != null) Util.encodeXMLs(out, " <type name=\"", x.getName(), "\"/>\n"); } /* * ========================================================= ================ * =================== */ /** Return null if the element is malformed. */ private static AlloySet parseAlloySet(VizState now, XMLNode x) { /* * class AlloySet implements AlloyNodeElement { String name; AlloyType type; } * <set name="name" type="name"/> */ if (!x.is("set")) return null; String name = x.getAttribute("name"), type = x.getAttribute("type"); if (name.length() == 0 || type.length() == 0) return null; AlloyType t = now.getCurrentModel().hasType(type); if (t == null) return null; else return now.getCurrentModel().hasSet(name, t); } /** Writes nothing if the argument is null. */ private static void writeAlloySet(PrintWriter out, AlloySet x) throws IOException { if (x != null) Util.encodeXMLs(out, " <set name=\"", x.getName(), "\" type=\"", x.getType().getName(), "\"/>\n"); } /* * ========================================================= ================ * =================== */ /** Return null if the element is malformed. */ private static AlloyRelation parseAlloyRelation(VizState now, XMLNode x) { /* * <relation name="name"> 2 or more <type name=".."/> </relation> */ List<AlloyType> ans = new ArrayList<AlloyType>(); if (!x.is("relation")) return null; String name = x.getAttribute("name"); if (name.length() == 0) return null; for (XMLNode sub : x.getChildren("type")) { String typename = sub.getAttribute("name"); if (typename.length() == 0) return null; AlloyType t = now.getCurrentModel().hasType(typename); if (t == null) return null; ans.add(t); } if (ans.size() < 2) return null; else return now.getCurrentModel().hasRelation(name, ans); } /** Writes nothing if the argument is null. */ private static void writeAlloyRelation(PrintWriter out, AlloyRelation x) throws IOException { if (x == null) return; Util.encodeXMLs(out, " <relation name=\"", x.getName(), "\">"); for (AlloyType t : x.getTypes()) Util.encodeXMLs(out, " <type name=\"", t.getName(), "\"/>"); out.write(" </relation>\n"); } /* * ========================================================= ================ * =================== */ /** * Always returns a nonnull (though possibly empty) set of AlloyType. */ private static Set<AlloyType> parseProjectionList(VizState now, XMLNode x) { /* * <projection> 0 or more <type name=".."/> </projection> */ Set<AlloyType> ans = new TreeSet<AlloyType>(); if (x.is("projection")) for (XMLNode sub : x.getChildren("type")) { String name = sub.getAttribute("name"); if (name.length() == 0) continue; AlloyType t = now.getOriginalModel().hasType(name); if (t != null) ans.add(t); } return ans; } /** * Writes an empty Projection tag if the argument is null or empty */ private static void writeProjectionList(PrintWriter out, Set<AlloyType> types) throws IOException { if (types == null || types.size() == 0) { out.write("\n<projection/>\n"); return; } out.write("\n<projection>"); for (AlloyType t : types) Util.encodeXMLs(out, " <type name=\"", t.getName(), "\"/>"); out.write(" </projection>\n"); } /* * ========================================================= ================ * =================== */ /** * Do nothing if the element is malformed; note: x can be null. */ private static void parseNodeViz(XMLNode xml, VizState view, AlloyNodeElement x) { /* * <node visible="inherit/yes/no" label=".." color=".." shape=".." style=".." * showlabel="inherit/yes/no" showinattr="inherit/yes/no" * hideunconnected="inherit/yes/no" nubmeratoms="inherit/yes/no"> zero or more * SET or TYPE </node> Each attribute, if omitted, means "no change". Note: * BOOLEAN is tristate. */ if (has(xml, "visible")) view.nodeVisible.put(x, getbool(xml, "visible")); if (has(xml, "hideunconnected")) view.hideUnconnected.put(x, getbool(xml, "hideunconnected")); if (x == null || x instanceof AlloySet) { AlloySet s = (AlloySet) x; if (has(xml, "showlabel")) view.showAsLabel.put(s, getbool(xml, "showlabel")); if (has(xml, "showinattr")) view.showAsAttr.put(s, getbool(xml, "showinattr")); } if (x == null || x instanceof AlloyType) { AlloyType t = (AlloyType) x; if (has(xml, "numberatoms")) view.number.put(t, getbool(xml, "numberatoms")); } if (has(xml, "style")) view.nodeStyle.put(x, parseDotStyle(xml)); if (has(xml, "color")) view.nodeColor.put(x, parseDotColor(xml)); if (has(xml, "shape")) view.shape.put(x, parseDotShape(xml)); if (has(xml, "label")) view.label.put(x, xml.getAttribute("label")); } /** * Returns the String representation of an AlloyNodeElement's settings. */ private static String writeNodeViz(VizState view, VizState defaultView, AlloyNodeElement x) throws IOException { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); writeBool(out, "visible", view.nodeVisible.get(x), defaultView.nodeVisible.get(x)); writeBool(out, "hideunconnected", view.hideUnconnected.get(x), defaultView.hideUnconnected.get(x)); if (x == null || x instanceof AlloySet) { AlloySet s = (AlloySet) x; writeBool(out, "showlabel", view.showAsLabel.get(s), defaultView.showAsLabel.get(s)); writeBool(out, "showinattr", view.showAsAttr.get(s), defaultView.showAsAttr.get(s)); } if (x == null || x instanceof AlloyType) { AlloyType t = (AlloyType) x; writeBool(out, "numberatoms", view.number.get(t), defaultView.number.get(t)); } writeDotStyle(out, view.nodeStyle.get(x), defaultView.nodeStyle.get(x)); writeDotShape(out, view.shape.get(x), defaultView.shape.get(x)); writeDotColor(out, view.nodeColor.get(x), defaultView.nodeColor.get(x)); if (x != null && !view.label.get(x).equals(defaultView.label.get(x))) Util.encodeXMLs(out, " label=\"", view.label.get(x), "\""); if (out.checkError()) throw new IOException("PrintWriter IO Exception!"); return sw.toString(); } /* * ========================================================= ================ * =================== */ /** * Do nothing if the element is malformed; note: x can be null. */ private static void parseEdgeViz(XMLNode xml, VizState view, AlloyRelation x) { /* * <edge visible="inherit/yes/no" label=".." color=".." style=".." weight=".." * constraint=".." attribute="inherit/yes/no" merge="inherit/yes/no" * layout="inherit/yes/no"> zero or more RELATION </edge> Each attribute, if * omitted, means "no change". Note: BOOLEAN is tristate. */ if (has(xml, "visible")) view.edgeVisible.put(x, getbool(xml, "visible")); if (has(xml, "attribute")) view.attribute.put(x, getbool(xml, "attribute")); if (has(xml, "merge")) view.mergeArrows.put(x, getbool(xml, "merge")); if (has(xml, "layout")) view.layoutBack.put(x, getbool(xml, "layout")); if (has(xml, "constraint")) view.constraint.put(x, getbool(xml, "constraint")); if (has(xml, "style")) view.edgeStyle.put(x, parseDotStyle(xml)); if (has(xml, "color")) view.edgeColor.put(x, parseDotColor(xml)); if (has(xml, "weight")) view.weight.put(x, getint(xml, "weight")); if (has(xml, "label")) view.label.put(x, xml.getAttribute("label")); } /** * Returns the String representation of an AlloyRelation's settings. */ private static String writeEdgeViz(VizState view, VizState defaultView, AlloyRelation x) throws IOException { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); writeDotColor(out, view.edgeColor.get(x), defaultView.edgeColor.get(x)); writeDotStyle(out, view.edgeStyle.get(x), defaultView.edgeStyle.get(x)); writeBool(out, "visible", view.edgeVisible.get(x), defaultView.edgeVisible.get(x)); writeBool(out, "merge", view.mergeArrows.get(x), defaultView.mergeArrows.get(x)); writeBool(out, "layout", view.layoutBack.get(x), defaultView.layoutBack.get(x)); writeBool(out, "attribute", view.attribute.get(x), defaultView.attribute.get(x)); writeBool(out, "constraint", view.constraint.get(x), defaultView.constraint.get(x)); if (view.weight.get(x) != defaultView.weight.get(x)) out.write(" weight=\"" + view.weight.get(x) + "\""); if (x != null && !view.label.get(x).equals(defaultView.label.get(x))) Util.encodeXMLs(out, " label=\"", view.label.get(x), "\""); if (out.checkError()) throw new IOException("PrintWriter IO Exception!"); return sw.toString(); } /* * ========================================================= ================ * =================== */ /** * Returns null if the attribute doesn't exist, or is malformed. */ private static DotPalette parseDotPalette(XMLNode x, String key) { return DotPalette.parse(x.getAttribute(key)); } /** Writes nothing if value==defaultValue. */ private static void writeDotPalette(PrintWriter out, String key, DotPalette value, DotPalette defaultValue) throws IOException { if (value != defaultValue) Util.encodeXMLs(out, " " + key + "=\"", value == null ? "inherit" : value.toString(), "\""); } /* * ========================================================= ================ * =================== */ /** * Returns null if the attribute doesn't exist, or is malformed. */ private static DotColor parseDotColor(XMLNode x) { return DotColor.parse(x.getAttribute("color")); } /** Writes nothing if value==defaultValue. */ private static void writeDotColor(PrintWriter out, DotColor value, DotColor defaultValue) throws IOException { if (value != defaultValue) Util.encodeXMLs(out, " color=\"", value == null ? "inherit" : value.toString(), "\""); } /* * ========================================================= ================ * =================== */ /** * Returns null if the attribute doesn't exist, or is malformed. */ private static DotShape parseDotShape(XMLNode x) { return DotShape.parse(x.getAttribute("shape")); } /** Writes nothing if value==defaultValue. */ private static void writeDotShape(PrintWriter out, DotShape value, DotShape defaultValue) throws IOException { if (value != defaultValue) Util.encodeXMLs(out, " shape=\"", value == null ? "inherit" : value.toString(), "\""); } /* * ========================================================= ================ * =================== */ /** * Returns null if the attribute doesn't exist, or is malformed. */ private static DotStyle parseDotStyle(XMLNode x) { return DotStyle.parse(x.getAttribute("style")); } /** Writes nothing if value==defaultValue. */ private static void writeDotStyle(PrintWriter out, DotStyle value, DotStyle defaultValue) throws IOException { if (value != defaultValue) Util.encodeXMLs(out, " style=\"", value == null ? "inherit" : value.toString(), "\""); } /* * ========================================================= ================ * =================== */ /** * Returns null if the attribute doesn't exist, or is malformed. */ private static Boolean getbool(XMLNode x, String attr) { String value = x.getAttribute(attr); if (value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true")) return Boolean.TRUE; if (value.equalsIgnoreCase("no") || value.equalsIgnoreCase("false")) return Boolean.FALSE; return null; } /** * Writes nothing if the value is equal to the default value. */ private static void writeBool(PrintWriter out, String key, Boolean value, Boolean defaultValue) throws IOException { if (value == null && defaultValue == null) return; if (value != null && defaultValue != null && value.booleanValue() == defaultValue.booleanValue()) return; out.write(' '); out.write(key); if (value == null) out.write("=\"inherit\""); else out.write(value ? "=\"yes\"" : "=\"no\""); } /* * ========================================================= ================ * =================== */ /** * Returns true if the XML element has the given attribute. */ private static boolean has(XMLNode x, String attr) { return x.getAttribute(attr, null) != null; } /** * Returns 0 if the attribute doesn't exist, or is malformed. */ private static int getint(XMLNode x, String attr) { String value = x.getAttribute(attr); int i; try { i = Integer.parseInt(value); } catch (NumberFormatException ex) { i = 0; } return i; } }