/* 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.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
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.ConstList;
import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution;
/** Immutable; represents an Alloy instance that can be displayed in the visualizer.
*
* <p><b>Thread Safety:</b> Can be called only by the AWT event thread.
*/
public final class AlloyInstance {
/** The original A4Solution object. */
final A4Solution originalA4; // FIXTHIS: eventually we shouldn't need this field...
/** If true, it is a metamodel, else it is not a metamodel. */
public final boolean isMetamodel;
/** The original filename of the model that generated this instance; can be "" if unknown. */
public final String filename;
/** The original command that generated this instance; can be "" if unknown. */
public final String commandname;
/** The AlloyModel that this AlloyInstance is an instance of. */
public final AlloyModel model;
/** Maps each AlloyAtom to the AlloySet(s) it is in; its keySet is considered the universe of all atoms.
* <br> The constructor ensures every AlloySet here is in model.getSets()
* <br> Furthermore, every AlloyAtom's type is in model.getTypes()
* <br> Finally, if an atom A is in a set S, we guarantee that A.type is equal or subtype of S.type
*/
private final Map<AlloyAtom,ConstList<AlloySet>> atom2sets;
/** Maps each AlloyType to the AlloyAtom(s) in that type; it is derived from atom2sets.keySet() directly.
* <br> Thus, every AlloyType here is in model.getTypes(), and every AlloyAtom here is in atom2sets.keySet()
* <br> Furthermore, the constructor ensures that if an atom is in a subtype, it is also in the supertype.
*/
private final Map<AlloyType,List<AlloyAtom>> type2atoms;
/** Maps each AlloySet to the AlloyAtom(s) in that set; it is derived from atom2sets directly.
* <br> Thus, every AlloySet here is in model.getSets(), and every AlloyAtom here is in atom2sets.keySet()
* <br> Finally, if an atom A is in a set S, we guarantee that A.type is equal or subtype of S.type
*/
private final Map<AlloySet,List<AlloyAtom>> set2atoms;
/** Maps each AlloyRelation to a set of AlloyTuple(s).
* <br> The constructor ensures every AlloyRelation here is in model.getRelations()
* <br> Furthermore, every AlloyAtom in every AlloyTuple here is in atom2sets.keySet()
* <br> Finally, if a tuple T is in a relation R, we guarantee that T is a legal tuple for R
* (Meaning T.arity==R.arity, and for each i, T.getAtom(i).type is equal or subtype of R.getType(i).)
*/
private final Map<AlloyRelation,Set<AlloyTuple>> rel2tuples;
/** This always stores an empty unmodifiable list of atoms. */
private static final List<AlloyAtom> noAtom = ConstList.make();
/** This always stores an empty unmodifiable list of sets. */
private static final List<AlloySet> noSet = ConstList.make();
/** This always stores an empty unmodifiable set of tuples. */
private static final Set<AlloyTuple> noTuple = Collections.unmodifiableSet(new TreeSet<AlloyTuple>());
/** Create a new instance.
*
* @param filename - the original filename of the model that generated this instance; can be "" if unknown
* @param commandname - the original command that generated this instance; can be "" if unknown
* @param model - the AlloyModel that this AlloyInstance is an instance of
* @param atom2sets - maps each atom to the set(s) it is in; its KeySet is considered the universe of all atoms
* @param rel2tuples - maps each relation to the tuple(s) it is in
* <p>
* (The constructor will ignore any atoms, sets, types, and relations not in the model. So there's no need
* to explicitly filter them out prior to passing "atom2sets" and "rel2tuples" to the constructor.)
*/
public AlloyInstance(A4Solution originalA4, String filename, String commandname, AlloyModel model,
Map<AlloyAtom,Set<AlloySet>> atom2sets, Map<AlloyRelation,Set<AlloyTuple>> rel2tuples, boolean isMetamodel) {
this.originalA4 = originalA4;
this.filename = filename;
this.commandname = commandname;
this.model = model;
this.isMetamodel=isMetamodel;
// First, construct atom2sets (Use a treemap because we want its keyset to be sorted)
{
Map<AlloyAtom,ConstList<AlloySet>> a2s = new TreeMap<AlloyAtom,ConstList<AlloySet>>();
for(Map.Entry<AlloyAtom,Set<AlloySet>> e:atom2sets.entrySet()) {
AlloyAtom atom=e.getKey();
if (!model.hasType(atom.getType())) continue; // We discard any AlloyAtom whose type is not in this model
// We discard any AlloySet not in this model; and we discard AlloySet(s) that don't match the atom's type
List<AlloySet> sets=new ArrayList<AlloySet>();
for(AlloySet set:e.getValue())
if (model.getSets().contains(set) && model.isEqualOrSubtype(atom.getType(), set.getType()))
sets.add(set);
Collections.sort(sets);
a2s.put(atom, ConstList.make(sets));
}
this.atom2sets = Collections.unmodifiableMap(a2s);
}
// Next, construct set2atoms
{
Map<AlloySet,List<AlloyAtom>> s2a = new LinkedHashMap<AlloySet,List<AlloyAtom>>();
for(Map.Entry<AlloyAtom,ConstList<AlloySet>> e:this.atom2sets.entrySet()) for(AlloySet set:e.getValue()) {
List<AlloyAtom> atoms=s2a.get(set);
if (atoms==null) {atoms=new ArrayList<AlloyAtom>(); s2a.put(set,atoms);}
atoms.add(e.getKey());
}
for(AlloySet set:model.getSets()) {
List<AlloyAtom> atoms=s2a.get(set);
if (atoms==null) continue;
Collections.sort(atoms);
s2a.put(set, Collections.unmodifiableList(atoms));
}
this.set2atoms = Collections.unmodifiableMap(s2a);
}
// Next, construct type2atoms
{
Map<AlloyType,List<AlloyAtom>> t2a = new LinkedHashMap<AlloyType,List<AlloyAtom>>();
for(AlloyAtom a: this.atom2sets.keySet()) {
for(AlloyType t=a.getType(); t!=null; t=model.getSuperType(t)) {
List<AlloyAtom> atoms=t2a.get(t);
if (atoms==null) { atoms=new ArrayList<AlloyAtom>(); t2a.put(t,atoms); }
atoms.add(a);
}
}
for(AlloyType t:model.getTypes()) {
List<AlloyAtom> atoms=t2a.get(t);
if (atoms==null) continue;
Collections.sort(atoms);
t2a.put(t, Collections.unmodifiableList(atoms));
}
this.type2atoms = Collections.unmodifiableMap(t2a);
}
// Finally, construct rel2tuples
Map<AlloyRelation,Set<AlloyTuple>> r2t = new LinkedHashMap<AlloyRelation,Set<AlloyTuple>>();
for(Map.Entry<AlloyRelation,Set<AlloyTuple>> e:rel2tuples.entrySet()) {
AlloyRelation rel=e.getKey();
if (!model.getRelations().contains(rel)) continue; // We discard any AlloyRelation not in this model
Set<AlloyTuple> tuples=new TreeSet<AlloyTuple>();
for(AlloyTuple tuple:e.getValue()) {
if (tuple.getArity()!=rel.getArity()) continue; // The arity must match
for(int i=0; ; i++) {
if (i==tuple.getArity()) { tuples.add(tuple); break; }
AlloyAtom a=tuple.getAtoms().get(i);
if (!this.atom2sets.containsKey(a)) break; // Every atom must exist
if (!model.isEqualOrSubtype(a.getType(), rel.getTypes().get(i))) break; // Atom must match the type
}
}
if (tuples.size()!=0) r2t.put(rel, Collections.unmodifiableSet(tuples));
}
this.rel2tuples = Collections.unmodifiableMap(r2t);
}
/** Returns an unmodifiable sorted set of all AlloyAtoms in this AlloyInstance. */
public Set<AlloyAtom> getAllAtoms() { return Collections.unmodifiableSet(atom2sets.keySet()); }
/** Returns an unmodifiable sorted list of AlloySet(s) that this atom is in; answer can be an empty list. */
public List<AlloySet> atom2sets(AlloyAtom atom) {
List<AlloySet> answer=atom2sets.get(atom);
return answer!=null ? answer : noSet;
}
/** Returns an unmodifiable sorted list of AlloyAtom(s) in this type; answer can be an empty list. */
public List<AlloyAtom> type2atoms(AlloyType type) {
List<AlloyAtom> answer=type2atoms.get(type);
return answer!=null ? answer : noAtom;
}
/** Returns an unmodifiable sorted list of AlloyAtom(s) in this set; answer can be an empty list. */
public List<AlloyAtom> set2atoms(AlloySet set) {
List<AlloyAtom> answer=set2atoms.get(set);
return answer!=null ? answer : noAtom;
}
/** Returns an unmodifiable sorted set of AlloyTuple(s) in this relation; answer can be an empty set. */
public Set<AlloyTuple> relation2tuples(AlloyRelation rel) {
Set<AlloyTuple> answer=rel2tuples.get(rel);
return answer!=null ? answer : noTuple;
}
/** Two instances are equal if they have the same filename, same commands,
* same model, and same atoms and tuples relationships.
*/
@Override public boolean equals(Object other) {
if (!(other instanceof AlloyInstance)) return false;
if (other==this) return true;
AlloyInstance x=(AlloyInstance)other;
if (!filename.equals(x.filename)) return false;
if (!commandname.equals(x.commandname)) return false;
if (!model.equals(x.model)) return false;
if (!atom2sets.equals(x.atom2sets)) return false;
if (!type2atoms.equals(x.type2atoms)) return false;
if (!set2atoms.equals(x.set2atoms)) return false;
if (!rel2tuples.equals(x.rel2tuples)) return false; return true;
}
/** Computes a hash code based on the same information used in equals(). */
@Override public int hashCode() {
int n = 5*filename.hashCode() + 7*commandname.hashCode();
n = n + 7*atom2sets.hashCode() + 31*type2atoms.hashCode() + 71*set2atoms.hashCode() + 3*rel2tuples.hashCode();
return 17*n + model.hashCode();
}
/** Returns a textual dump of the instance. */
@Override public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("Instance's model:\n");
sb.append(model.toString());
sb.append("Instance's atom2sets:\n");
for(Map.Entry<AlloyAtom,ConstList<AlloySet>> entry: atom2sets.entrySet()) {
sb.append(" "); sb.append(entry.getKey()); sb.append(" "); sb.append(entry.getValue()); sb.append('\n');
}
sb.append("Instance's rel2tuples:\n");
for(Map.Entry<AlloyRelation,Set<AlloyTuple>> entry: rel2tuples.entrySet()) {
sb.append(" "); sb.append(entry.getKey()); sb.append(" "); sb.append(entry.getValue()); sb.append('\n');
}
return sb.toString();
}
}