/* 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.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorFatal;
import edu.mit.csail.sdg.alloy4.ErrorSyntax;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4.XMLNode;
import edu.mit.csail.sdg.alloy4compiler.ast.Expr;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig;
import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution;
import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader;
import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple;
import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet;
/** This utility class parses an XML file into an AlloyInstance object.
*
* <p><b>Thread Safety:</b> Can be called only by the AWT event thread.
*/
public final class StaticInstanceReader {
/** The resulting AlloyInstance object. */
private final AlloyInstance ans;
/** This is the list of toplevel sigs. */
private final List<PrimSig> toplevels = new ArrayList<PrimSig>();
/** This maps each Sig to its corresponding Visualizer AlloyType. */
private final LinkedHashMap<Sig,AlloyType> sig2type = new LinkedHashMap<Sig,AlloyType>();
/** This maps each Sig ot its corresponding unique VIsualizer AlloyAtom (if isMeta is true). */
private final LinkedHashMap<Sig,AlloyAtom> sig2atom = new LinkedHashMap<Sig,AlloyAtom>();
/** This stores the "extends" relationship among sigs (if isMeta is true). */
private final LinkedHashSet<AlloyTuple> exts = new LinkedHashSet<AlloyTuple>();
/** This stores the "in" relationship among sigs (if isMeta is true). */
private final LinkedHashSet<AlloyTuple> ins = new LinkedHashSet<AlloyTuple>();
/** This stores the set of Visualizer AlloySet objects we created. */
private final Set<AlloySet> sets = new LinkedHashSet<AlloySet>();
/** This maps each Visualizer AlloyRelation to its set of (possibly 0) tuples. */
private final Map<AlloyRelation,Set<AlloyTuple>> rels = new LinkedHashMap<AlloyRelation,Set<AlloyTuple>>();
/** For each sig A and B, if A extends B, and B is not univ, then (A,B) will be in this map. */
private final Map<AlloyType,AlloyType> ts = new LinkedHashMap<AlloyType,AlloyType>();
/** This maps each Visualizer AlloyAtom to its set of (possibly 0) AlloySet that contains it. */
private final Map<AlloyAtom,Set<AlloySet>> atom2sets = new LinkedHashMap<AlloyAtom,Set<AlloySet>>();
/** This maps each AlloyAtom label to the AlloyAtom we created for it. */
private final Map<String,AlloyAtom> string2atom = new LinkedHashMap<String,AlloyAtom>();
/** Create a new AlloyType whose label is unambiguous with any existing one. */
private AlloyType makeType(String label, boolean isOne, boolean isAbstract, boolean isBuiltin, boolean isPrivate, boolean isMeta, boolean isEnum) {
if (label.startsWith("this/")) label = label.substring(5);
while(true) {
AlloyType ans = new AlloyType(label, isOne, isAbstract, isBuiltin, isPrivate, isMeta, isEnum);
if (!sig2type.values().contains(ans)) return ans;
label=label+"'";
}
}
/** Create a new AlloySet whose label is unambiguous with any existing one. */
private AlloySet makeSet(String label, boolean isPrivate, boolean isMeta, AlloyType type) {
while(label.equals(Sig.UNIV.label) || label.equals(Sig.SIGINT.label) || label.equals(Sig.SEQIDX.label) || label.equals(Sig.STRING.label)) label=label+"'";
while(true) {
AlloySet ans = new AlloySet(label, isPrivate, isMeta, type);
if (!sets.contains(ans)) return ans;
label=label+"'";
}
}
/** Create a new AlloyRelation whose label is unambiguous with any existing one. */
private AlloyRelation makeRel(String label, boolean isPrivate, boolean isMeta, List<AlloyType> types) {
while(label.equals(Sig.UNIV.label) || label.equals(Sig.SIGINT.label) || label.equals(Sig.SEQIDX.label) || label.equals(Sig.STRING.label)) label=label+"'";
while(true) {
AlloyRelation ans = new AlloyRelation(label, isPrivate, isMeta, types);
if (!rels.containsKey(ans)) return ans;
label=label+"'";
}
}
/** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */
private AlloyType sig(PrimSig s) throws Err {
if (s==Sig.NONE) throw new ErrorFatal("Unexpected sig \"none\" encountered.");
AlloyType ans = sig2type.get(s);
if (ans == null) {
ans = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null);
sig2type.put(s, ans);
if (s.parent!=Sig.UNIV) ts.put(ans, sig(s.parent));
}
return ans;
}
/** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */
private AlloyType sigMETA(PrimSig s) throws Err {
if (s==Sig.NONE) throw new ErrorFatal("Unexpected sig \"none\" encountered.");
AlloyType type = sig2type.get(s);
if (type != null) return type;
if (s==Sig.UNIV) type=AlloyType.UNIV;
else if (s==Sig.SIGINT) type=AlloyType.INT;
else if (s==Sig.SEQIDX) type=AlloyType.SEQINT;
else if (s==Sig.STRING) type=AlloyType.STRING;
else type = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null);
sig2type.put(s, type);
AlloyAtom atom = new AlloyAtom(type, (type==AlloyType.SEQINT ? Integer.MIN_VALUE : Integer.MAX_VALUE), s.label);
atom2sets.put(atom, new LinkedHashSet<AlloySet>());
sig2atom.put(s, atom);
if (s.parent!=Sig.UNIV && s.parent!=null)
ts.put(type, sigMETA(s.parent));
if (s.parent!=null)
exts.add(new AlloyTuple(atom, sig2atom.get(s.parent)));
Iterable<PrimSig> children = (s==Sig.UNIV ? toplevels : s.children());
for(PrimSig sub:children) sigMETA(sub);
return type;
}
/** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */
private void sigMETA(SubsetSig s) throws Err {
AlloyAtom atom;
AlloyType type = sig2type.get(s);
if (type != null) return;
type = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null);
atom = new AlloyAtom(type, Integer.MAX_VALUE, s.label);
atom2sets.put(atom, new LinkedHashSet<AlloySet>());
sig2atom.put(s, atom);
sig2type.put(s, type);
ts.put(type, AlloyType.SET);
for(Sig p: ((SubsetSig)s).parents) {
if (p instanceof SubsetSig) sigMETA((SubsetSig)p); else sigMETA((PrimSig)p);
ins.add(new AlloyTuple(atom, sig2atom.get(p)));
}
}
/** Constructs the atoms corresponding to the given sig. */
private void atoms(A4Solution sol, PrimSig s) throws Err {
Expr sum=Sig.NONE;
for(PrimSig c:s.children()) { sum=sum.plus(c); atoms(sol, c); }
A4TupleSet ts = (A4TupleSet) (sol.eval(s.minus(sum))); // This ensures that atoms will be associated with the most specific sig
for(A4Tuple z: ts) {
String atom = z.atom(0);
int i, dollar = atom.lastIndexOf('$');
try { i = Integer.parseInt(dollar>=0 ? atom.substring(dollar+1) : atom); } catch(NumberFormatException ex) { i = Integer.MAX_VALUE; }
AlloyAtom at = new AlloyAtom(sig(s), ts.size()==1 ? Integer.MAX_VALUE : i, atom);
atom2sets.put(at, new LinkedHashSet<AlloySet>());
string2atom.put(atom, at);
}
}
/** Construct an AlloySet or AlloyRelation corresponding to the given expression. */
private void setOrRel(A4Solution sol, String label, Expr expr, boolean isPrivate, boolean isMeta) throws Err {
for(List<PrimSig> ps:expr.type().fold()) {
if (ps.size()==1) {
PrimSig t = ps.get(0);
AlloySet set = makeSet(label, isPrivate, isMeta, sig(t));
sets.add(set);
for(A4Tuple tp: (A4TupleSet)(sol.eval(expr.intersect(t)))) {
atom2sets.get(string2atom.get(tp.atom(0))).add(set);
}
} else {
Expr mask = null;
List<AlloyType> types = new ArrayList<AlloyType>(ps.size());
for(int i=0; i<ps.size(); i++) {
types.add(sig(ps.get(i)));
if (mask==null) mask=ps.get(i); else mask=mask.product(ps.get(i));
}
AlloyRelation rel = makeRel(label, isPrivate, isMeta, types);
Set<AlloyTuple> ts = new LinkedHashSet<AlloyTuple>();
for(A4Tuple tp: (A4TupleSet)(sol.eval(expr.intersect(mask)))) {
AlloyAtom[] atoms = new AlloyAtom[tp.arity()];
for(int i=0; i<tp.arity(); i++) {
atoms[i] = string2atom.get(tp.atom(i));
if (atoms[i]==null) throw new ErrorFatal("Unexpected XML inconsistency: cannot resolve atom "+tp.atom(i));
}
ts.add(new AlloyTuple(atoms));
}
rels.put(rel, ts);
}
}
}
/** Parse the file into an AlloyInstance if possible. */
private StaticInstanceReader(XMLNode root) throws Err {
XMLNode inst = null;
for(XMLNode sub: root) if (sub.is("instance")) { inst=sub; break; }
if (inst==null) throw new ErrorSyntax("The XML file must contain an <instance> element.");
boolean isMeta = "yes".equals(inst.getAttribute("metamodel"));
A4Solution sol = A4SolutionReader.read(new ArrayList<Sig>(), root);
for (Sig s:sol.getAllReachableSigs()) if (s instanceof PrimSig && ((PrimSig)s).parent==Sig.UNIV) toplevels.add((PrimSig)s);
if (!isMeta) {
sig2type.put(Sig.UNIV, AlloyType.UNIV);
sig2type.put(Sig.SIGINT, AlloyType.INT);
sig2type.put(Sig.SEQIDX, AlloyType.SEQINT);
sig2type.put(Sig.STRING, AlloyType.STRING);
ts.put(AlloyType.SEQINT, AlloyType.INT);
for(int i=sol.min(), max=sol.max(), maxseq=sol.getMaxSeq(); i<=max; i++) {
AlloyAtom at = new AlloyAtom(i>=0 && i<maxseq ? AlloyType.SEQINT : AlloyType.INT, i, ""+i);
atom2sets.put(at, new LinkedHashSet<AlloySet>());
string2atom.put(""+i, at);
}
for(Sig s:sol.getAllReachableSigs()) if (!s.builtin && s instanceof PrimSig) sig((PrimSig)s);
for(Sig s:toplevels) if (!s.builtin || s==Sig.STRING) atoms(sol, (PrimSig)s);
for(Sig s:sol.getAllReachableSigs()) if (s instanceof SubsetSig) setOrRel(sol, s.label, s, s.isPrivate!=null, s.isMeta!=null);
for(Sig s:sol.getAllReachableSigs()) for(Field f:s.getFields()) setOrRel(sol, f.label, f, f.isPrivate!=null, f.isMeta!=null);
for(ExprVar s:sol.getAllSkolems()) setOrRel(sol, s.label, s, false, false);
}
if (isMeta) {
sigMETA(Sig.UNIV);
for(Sig s:sol.getAllReachableSigs()) if (s instanceof SubsetSig) sigMETA((SubsetSig)s);
for(Sig s:sol.getAllReachableSigs()) for(Field f:s.getFields()) {
for(List<PrimSig> ps:f.type().fold()) {
List<AlloyType> types = new ArrayList<AlloyType>(ps.size());
AlloyAtom[] tuple = new AlloyAtom[ps.size()];
for(int i=0; i<ps.size(); i++) {
types.add(sig(ps.get(i)));
tuple[i] = sig2atom.get(ps.get(i));
}
AlloyRelation rel = makeRel(f.label, f.isPrivate!=null, false, types);
rels.put(rel, Util.asSet(new AlloyTuple(tuple)));
}
}
if (ins.size()>0) { sig2type.put(null, AlloyType.SET); rels.put(AlloyRelation.IN, ins); }
AlloyAtom univAtom = sig2atom.get(Sig.UNIV);
AlloyAtom intAtom = sig2atom.get(Sig.SIGINT);
AlloyAtom seqAtom = sig2atom.get(Sig.SEQIDX);
AlloyAtom strAtom = sig2atom.get(Sig.STRING);
for(Set<AlloyTuple> t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(univAtom)) { univAtom=null; break; }
for(Set<AlloyTuple> t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(intAtom)) { intAtom=null; break; }
for(Set<AlloyTuple> t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(seqAtom)) { seqAtom=null; break; }
for(Set<AlloyTuple> t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(strAtom)) { strAtom=null; break; }
if (univAtom!=null) {
for(Iterator<AlloyTuple> it=exts.iterator(); it.hasNext();) {
AlloyTuple at=it.next();
if (at.getStart()==univAtom || at.getEnd()==univAtom) it.remove();
}
atom2sets.remove(univAtom);
}
if (strAtom!=null) {
for(Iterator<AlloyTuple> it=exts.iterator(); it.hasNext();) {
AlloyTuple at=it.next();
if (at.getStart()==strAtom || at.getEnd()==strAtom) it.remove();
}
atom2sets.remove(strAtom);
}
if (intAtom!=null && seqAtom!=null) {
for(Iterator<AlloyTuple> it=exts.iterator(); it.hasNext();) {
AlloyTuple at=it.next();
if (at.getStart()==intAtom || at.getEnd()==intAtom || at.getStart()==seqAtom || at.getEnd()==seqAtom) it.remove();
}
atom2sets.remove(intAtom);
atom2sets.remove(seqAtom);
}
if (exts.size()>0) { rels.put(AlloyRelation.EXTENDS, exts); }
}
AlloyModel am = new AlloyModel(sig2type.values(), sets, rels.keySet(), ts);
ans=new AlloyInstance(sol, sol.getOriginalFilename(), sol.getOriginalCommand(), am, atom2sets, rels, isMeta);
}
/** Parse the file into an AlloyInstance if possible. */
public static AlloyInstance parseInstance(File file) throws Err {
try {
return (new StaticInstanceReader(new XMLNode(file))).ans;
} catch(IOException ex) {
throw new ErrorFatal("Error reading the XML file: " + ex, ex);
}
}
/** Parse the file into an AlloyInstance if possible, then close the Reader afterwards. */
public static AlloyInstance parseInstance(Reader reader) throws Err {
try {
return (new StaticInstanceReader(new XMLNode(reader))).ans;
} catch(IOException ex) {
throw new ErrorFatal("Error reading the XML file: " + ex, ex);
}
}
}