/* 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.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** This utility class performs projection of AlloyModel and AlloyInstance. * * <p><b>Thread Safety:</b> Can be called only by the AWT event thread. */ public final class StaticProjector { /** Constructor is private, since this utility class never needs to be instantiated. */ private StaticProjector() { } /** Given an unprojected model, project it over the given collection of AlloyType(s). * @param unprojectedModel - the original unprojected model * @param typesToBeProjected - the collection of types to project over */ public static AlloyModel project(AlloyModel unprojectedModel, Collection<AlloyType> typesToBeProjected) { return project(unprojectedModel, typesToBeProjected, null); } /** Given an unprojected model, project it over the given collection of AlloyType(s). * @param unprojectedModel - the original unprojected model * @param typesToBeProjected - the collection of types to project over * @param data - if nonnull, this method will record into this map how the relations are changed by the projection * <p> * For every relation R that gets altered to become a new AlloySet or a new AlloyRelation, * data.get(R) will give us a list of integers indicating the columns deleted from R due to the projection. * (For example, if an original relation A->B->C->D becomes B->D, * then the list of integers will be (0,2) indicating the first and third columns were removed). * <p> * If a relation R remains unchanged during the projection, then data.get(R) will return an empty list. * <p> * If a relation R is totally deleted, due to the projection, then R won't be in data.keySet(). */ private static AlloyModel project( AlloyModel unprojectedModel, Collection<AlloyType> typesToBeProjected, Map<AlloyRelation,List<Integer>> data) { Set<AlloyType> types = new LinkedHashSet<AlloyType>(unprojectedModel.getTypes()); List<AlloySet> sets = new ArrayList<AlloySet>(unprojectedModel.getSets()); List<AlloyRelation> relations = new ArrayList<AlloyRelation>(); // Get rid of all projected types, as well as their subtypes. for (AlloyType type: typesToBeProjected) { types.remove(type); types.removeAll(unprojectedModel.getSubTypes(type)); } types.add(AlloyType.UNIV); // "univ" has to be a type // Now go over the relations... for (AlloyRelation rel: unprojectedModel.getRelations()) { List<AlloyType> relTypes = new ArrayList<AlloyType>(rel.getTypes()); List<Integer> indices = new ArrayList<Integer>(); int currentIndex = 0; // For each type in a relation, if it is a removed type, remove it and keep track of its index. for (Iterator<AlloyType> relTypesIter = relTypes.iterator(); relTypesIter.hasNext();) { if (!types.contains(relTypesIter.next())) { relTypesIter.remove(); indices.add(currentIndex); } currentIndex++; } // If the relation still contains at least two types, it becomes a new relation if (relTypes.size() > 1) { relations.add(new AlloyRelation(rel.getName(), rel.isPrivate, rel.isMeta, relTypes)); if (data!=null) data.put(rel, indices); } // If it contains only one type, it becomes a new set. else if (relTypes.size() == 1) { sets.add(new AlloySet(rel.getName(), rel.isPrivate, rel.isMeta, relTypes.get(0))); if (data!=null) data.put(rel, indices); } } // Finally, go through the sets and remove any whose type was removed. for (Iterator<AlloySet> setsIter = sets.iterator(); setsIter.hasNext();) { AlloySet set = setsIter.next(); if (!types.contains(set.getType())) setsIter.remove(); } return new AlloyModel(types, sets, relations, unprojectedModel); } /** Project an instance over the given list of types (and their associated chosen atom). * @param oldInstance - the original unprojected instance * @param projection - the list of types to be projected and their associated chosen atoms * * <p> For each type t in projection.getProjectedTypes: * * <p> (1) If t doesn't exist in the instance, then we will simply ignore t. * * <p> (2) Otherwise, if t has one or more atoms in the original instance, * <br> then projection.getProjectedAtom(t) must be one of the atoms (indicating the chosen atom for that type) * <br> else projection.getProjectedAtom(t) must be null. * <br> If rule (2) is violated, then some tuples may not show up in the return value. */ public static AlloyInstance project(AlloyInstance oldInstance, AlloyProjection projection) { Map<AlloyRelation,List<Integer>> data=new LinkedHashMap<AlloyRelation,List<Integer>>(); Map<AlloyAtom,Set<AlloySet>> atom2sets = new LinkedHashMap<AlloyAtom,Set<AlloySet>>(); Map<AlloyRelation,Set<AlloyTuple>> rel2tuples = new LinkedHashMap<AlloyRelation,Set<AlloyTuple>>(); AlloyModel newModel = project(oldInstance.model, projection.getProjectedTypes(), data); // First put all the atoms from the old instance into the new one for(AlloyAtom atom:oldInstance.getAllAtoms()) { atom2sets.put(atom, new LinkedHashSet<AlloySet>(oldInstance.atom2sets(atom))); } // Now, decide what tuples to generate for(AlloyRelation r:oldInstance.model.getRelations()) { List<Integer> list=data.get(r); if (list==null) continue; // This means that relation was deleted entirely tupleLabel: for(AlloyTuple oldTuple:oldInstance.relation2tuples(r)) { for (Integer i:list) { // If an atom in the original tuple should be projected, but it doesn't match the // chosen atom for that type, then this tuple must not be included in the new instance AlloyAtom a=oldTuple.getAtoms().get(i); AlloyType bt=r.getTypes().get(i); bt=oldInstance.model.getTopmostSuperType(bt); if (!a.equals(projection.getProjectedAtom(bt))) continue tupleLabel; } List<AlloyAtom> newTuple=oldTuple.project(list); List<AlloyType> newObj=r.project(list); if (newObj.size()>1 && newTuple.size()>1) { AlloyRelation r2=new AlloyRelation(r.getName(), r.isPrivate, r.isMeta, newObj); Set<AlloyTuple> answer=rel2tuples.get(r2); if (answer==null) rel2tuples.put(r2, answer=new LinkedHashSet<AlloyTuple>()); answer.add(new AlloyTuple(newTuple)); } else if (newObj.size()==1 && newTuple.size()==1) { AlloyAtom a=newTuple.get(0); Set<AlloySet> answer=atom2sets.get(a); if (answer==null) atom2sets.put(a, answer=new LinkedHashSet<AlloySet>()); answer.add(new AlloySet(r.getName(), r.isPrivate, r.isMeta, newObj.get(0))); } } } // Here, we don't have to explicitly filter out "illegal" atoms/tuples/... // (that is, atoms that belong to types that no longer exist, etc). // That's because AlloyInstance's constructor must do the check too, so there's no point in doing that twice. return new AlloyInstance(oldInstance.originalA4, oldInstance.filename, oldInstance.commandname, newModel, atom2sets, rel2tuples, oldInstance.isMetamodel); } }