/* 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; } }