/* 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.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import edu.mit.csail.sdg.alloy4.ConstList; import edu.mit.csail.sdg.alloy4.ConstList.TempList; /** Immutable; represents an Alloy model. * * <p><b>Thread Safety:</b> Can be called only by the AWT event thread. */ public final class AlloyModel { /** An unmodifiable sorted set of all types in this model. */ private final Set<AlloyType> types; /** An unmodifiable sorted set of all sets in this model. * <br> AlloyModel's constructor guarantees that, for each set here, set.getType() is in this.types */ private final Set<AlloySet> sets; /** An unmodifiable sorted set of all relations in this model. * <br> AlloyModel's constructor guarantees that, for each relation here, relation.getTypes() are in this.types */ private final Set<AlloyRelation> relations; /** If A extends B, then "(A,B)" will be in this map. * * <p> AlloyModel's constructor ensures the following: * <br> (1) hierachy.keySet() is always a subset of this.types * <br> (2) hierachy.valueSet() is always a subset of this.types * <br> (3) "univ" is never in the keySet * <br> (4) null is never in the keySet nor valueSet * <br> (5) there is no cycle in this relation */ private final Map<AlloyType,AlloyType> hierarchy; /** The map from name to AlloyType. * <br> AlloyModel's constructor guarantees that this.name2types.values() has the same entries as this.types */ private final Map<String,AlloyType> name2types = new HashMap<String,AlloyType>(); /** Returns true iff the nodes x, map.get(x), map.get(map.get(x))... form an infinite chain of nonnull objects. * @param map - a map from AlloyType to AlloyType * @param x - the AlloyType object we want to check */ public static boolean isCycle(Map<AlloyType,AlloyType> map, AlloyType x) { int i=(-5), n=map.size(); while(x!=null) { x=map.get(x); i++; if (i>=n) return true; } return false; } /** Construct a new AlloyModel object. * @param types - the types; we will always add "univ" to it if it's not there already * @param sets - the sets * @param rels - the relations * @param map - we consult this "sig to parent sig" map and extract the mappings relevant to this model. * (If we detect a cycle, we will arbitrarily break the cycle) */ public AlloyModel(Collection<AlloyType> types , Collection<AlloySet> sets , Collection<AlloyRelation> rels , Map<AlloyType,AlloyType> map) { // The following 3 have to be tree sets, since we want to keep them sorted Set<AlloyType> allTypes = new TreeSet<AlloyType>(); Set<AlloySet> allSets = new TreeSet<AlloySet>(); Set<AlloyRelation> allRelations = new TreeSet<AlloyRelation>(); allTypes.addAll(types); allTypes.add(AlloyType.UNIV); for(AlloySet s:sets) if (allTypes.contains(s.getType())) allSets.add(s); for(AlloyRelation r:rels) if (allTypes.containsAll(r.getTypes())) allRelations.add(r); this.types=Collections.unmodifiableSet(allTypes); this.sets=Collections.unmodifiableSet(allSets); this.relations=Collections.unmodifiableSet(allRelations); Map<AlloyType,AlloyType> newmap=new LinkedHashMap<AlloyType,AlloyType>(); for(AlloyType type:allTypes) { AlloyType sup = isCycle(map,type) ? null : map.get(type); if (sup==null || !allTypes.contains(sup)) sup=AlloyType.UNIV; newmap.put(type,sup); } newmap.remove(AlloyType.UNIV); // This ensures univ is not in hierarchy's keySet this.hierarchy=Collections.unmodifiableMap(newmap); for(AlloyType t: this.types) this.name2types.put(t.getName(), t); } /** Construct a new AlloyModel object. * @param types - the types ; we will always add "univ" to it if it's not there already * @param sets - the sets * @param rels - the relations * @param old - we consult this model's "sig to parent sig" map, and extract the mappings relevant to this model. */ public AlloyModel(Collection<AlloyType> types, Collection<AlloySet> sets, Collection<AlloyRelation> rels, AlloyModel old) { this(types, sets, rels, old.hierarchy); } /** If type==univ, return null; otherwise, return a nonnull AlloyType object representing its super type. * <br> (In particular, if "type" does not exist in this model, we'll return "univ" as the answer). */ public AlloyType getSuperType(AlloyType type) { if (type.getName().equals("univ")) return null; AlloyType answer=hierarchy.get(type); return answer==null ? AlloyType.UNIV : answer; } /** If type==univ, return null; otherwise, * return a nonnull AlloyType object representing its topmost non-univ super type. * * <p> Thus, if "type" is in this model, but its supertype is univ, then we'll return type as-is. * <p> Note: if "type" does not exist in this model, we'll return it as-is. */ public AlloyType getTopmostSuperType(AlloyType type) { if (type==null || type.equals(AlloyType.UNIV)) return null; while (true) { AlloyType top = getSuperType(type); if (top==null || top.equals(AlloyType.UNIV)) break; type=top; } return type; } /** Returns a sorted, unmodifiable list of types that are direct or indirect subtypes of the given type. * <br> This method will search recursively, so if the subtypes themselves have subtypes, they too are included. * <br> If type==null, or it does not exist in this model, or it has no subsigs, then we return an empty set. */ public ConstList<AlloyType> getSubTypes(AlloyType type) { TempList<AlloyType> subtypes = new TempList<AlloyType>(); for (AlloyType subType:types) if (isSubtype(subType,type)) subtypes.add(subType); return subtypes.makeConst(); // Since this.types is sorted, the result is already sorted. } /** Returns a sorted, unmodifiable list of types that are direct subtypes of the given type. * <br> This method will only return types that are direct subtypes of the given argument. * <br> If type==null, or it does not exist in this model, or it has no subsigs, then we return an empty set. */ public ConstList<AlloyType> getDirectSubTypes(AlloyType type) { TempList<AlloyType> subtypes = new TempList<AlloyType>(); for (AlloyType subType: types) if (isDirectSubtype(subType,type)) subtypes.add(subType); return subtypes.makeConst(); // Since this.types is sorted, the result is already sorted. } /** Returns true iff "subType" is a direct or indirect subsig of "superType". * <br> If subType==null or superType==null, it always returns false. */ public boolean isSubtype(AlloyType subType, AlloyType superType) { if (subType==null || superType==null || !types.contains(subType) || subType.equals(AlloyType.UNIV)) return false; while(subType!=null) { subType=getSuperType(subType); // Do this before calling equals(), since we want isSubtype(X,X)==false if (superType.equals(subType)) return true; } return false; } /** Returns true iff "subType" is a direct subsig of "superType". * <br> If subType==null or superType==null, it always returns false. */ public boolean isDirectSubtype(AlloyType subType, AlloyType superType) { if (subType==null || superType==null || !types.contains(subType) || subType.equals(AlloyType.UNIV)) return false; if (superType.equals(AlloyType.UNIV) && hierarchy.get(subType)==null) return true; return superType.equals(hierarchy.get(subType)); } /** Returns true iff "subType" is equal to, or is a direct or indirect subsig of "superType". * <br> If subType==null or superType==null, it always returns false. */ public boolean isEqualOrSubtype(AlloyType subType, AlloyType superType) { if (superType==null) return false; while(subType!=null) { if (superType.equals(subType)) return true; subType=getSuperType(subType); } return false; } /** Two AlloyModel objects are equal if they have the same types, sets, relations, and extension relationship. */ @Override public boolean equals(Object other) { if (!(other instanceof AlloyModel)) return false; if (other==this) return true; AlloyModel x=(AlloyModel)other; return types.equals(x.types) && sets.equals(x.sets) && relations.equals(x.relations) && hierarchy.equals(x.hierarchy); } /** Compute a hashcode based on the types, sets, relations, and the extension relationship. */ @Override public int hashCode() { return types.hashCode()+3*sets.hashCode()+5*relations.hashCode()+7*hierarchy.hashCode(); } /** Returns true if this model contains the given type. */ public boolean hasType(AlloyType type) { return types.contains(type); } /** Returns the AlloyType object if this model contains the given type; or return null otherwise. */ public AlloyType hasType(String name) { return name2types.get(name); } /** Returns the AlloyRelation object if this model contains the given relation; or return null otherwise. */ public AlloySet hasSet(String name, AlloyType type) { for(AlloySet s:sets) if (s.getName().equals(name) && s.getType().equals(type)) return s; return null; } /** Returns the AlloyRelation object if this model contains the given relation; or return null otherwise. */ public AlloyRelation hasRelation(String name, List<AlloyType> types) { for(AlloyRelation r:relations) if (r.getName().equals(name) && r.getTypes().equals(types)) return r; return null; } /** Returns an unmodifiable sorted set of all AlloyType(s) in this model. */ public Set<AlloyType> getTypes() { return types; } /** Returns an unmodifiable sorted set of all AlloySet(s) in this model. */ public Set<AlloySet> getSets() { return sets; } /** Returns an unmodifiable sorted set of all AlloyRelation(s) in this model. */ public Set<AlloyRelation> getRelations() { return relations; } }