fiore@0: /* fiore@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@0: fiore@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@0: fiore@0: This program is free software: you can redistribute it and/or modify fiore@0: it under the terms of the GNU General Public License as published by fiore@0: the Free Software Foundation, either version 3 of the License, or fiore@0: (at your option) any later version. fiore@0: fiore@0: This program is distributed in the hope that it will be useful, fiore@0: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@0: GNU General Public License for more details. fiore@0: fiore@0: You should have received a copy of the GNU General Public License fiore@0: along with this program. If not, see . fiore@0: */ fiore@0: package uk.ac.qmul.eecs.ccmi.diagrammodel; fiore@0: fiore@0: import java.util.ArrayList; fiore@0: import java.util.Collections; fiore@0: import java.util.LinkedHashMap; fiore@0: import java.util.LinkedHashSet; fiore@0: import java.util.List; fiore@0: import java.util.Map; fiore@0: import java.util.Set; fiore@0: fiore@0: /** fiore@0: * This class represents the internal properties of a node. Internal properties can be seen as fiore@0: * attributes that each single nodes owns and that, in a visual diagram, would normally be displayed inside fiore@0: * or in the neighbourhood of the node itself. This is a very high abstraction of the concept of properties fiore@0: * of a node as the user can define their own type of properties through the property type definition object fiore@0: * passed as argument in the constructor. fiore@0: * An example of properties is attributes and methods of a class diagram in the UML language or just the fiore@0: * attributes of entities in an ER diagram. fiore@0: * fiore@0: */ fiore@0: public final class NodeProperties implements Cloneable { fiore@0: fiore@0: /** fiore@0: * Creates a diagram element property data structure out of a property type specification. In a UML fiore@0: * diagram the NodeProperties for a class node would be constructed passing as arguments a linked hash fiore@0: * map containing the strings "attributes" and "properties" and the strings "public", "protected", "private", fiore@0: * "static" as value for both the keys. "attributes" and "methods" would be the types of the NodeProperties, fiore@0: * that is, all the values inserted in the object such as values would fall under either type. For example fiore@0: * if a UML diagram class has the methods getX() and getY(), these would be inserted in the NodeProperties fiore@0: * object with a type "methods". fiore@0: * fiore@0: * @param typeDefinition a linked Hash Map holding the properties types as keys fiore@0: * and the modifiers type definition of each property as values fiore@0: */ fiore@0: fiore@0: public NodeProperties(LinkedHashMap> typeDefinition){ fiore@0: if(typeDefinition == null) fiore@0: this.typeDefinition = EMPTY_PROPERTY_TYPE_DEFINITION; fiore@0: else fiore@0: this.typeDefinition = typeDefinition; fiore@0: /* create the type collection out of the typeDefinition keyset */ fiore@0: types = Collections.unmodifiableList(new ArrayList(this.typeDefinition.keySet())); fiore@0: properties = new LinkedHashMap(); fiore@0: for(String s : types){ fiore@0: Entry q = new Entry(); fiore@0: q.values = new ArrayList(); // property values to be filled by user + modifiers fiore@0: q.indexes = new ArrayList>(); fiore@0: q.view = null; fiore@0: Set modifierTypeDefinition = this.typeDefinition.get(s); fiore@0: if(modifierTypeDefinition == null) fiore@0: /* null modifiers for this property */ fiore@0: q.modifiers = new Modifiers(EMPTY_MODIFIER_TYPE_DEFINITION,q.indexes); fiore@0: else if(modifierTypeDefinition.size() == 0) fiore@0: /* null modifiers for this property */ fiore@0: q.modifiers = new Modifiers(EMPTY_MODIFIER_TYPE_DEFINITION,q.indexes); fiore@0: else fiore@0: q.modifiers = new Modifiers(modifierTypeDefinition, q.indexes); fiore@0: properties.put(s, q); fiore@0: } fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the type definition argument of the constructor this object has been created through. fiore@0: * fiore@0: * @return the type definition fiore@0: */ fiore@0: public Map> getTypeDefinition(){ fiore@0: return typeDefinition; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the types of properties. fiore@3: * fiore@3: * @return a list of strings holding types of properties fiore@0: */ fiore@0: public List getTypes(){ fiore@0: return types; fiore@0: } fiore@0: fiore@0: /** fiore@3: * @param propertyType the property type we want to get the values of. fiore@0: * @return an array of string with the different properties set by the user or fiore@3: * {@code null} if the specified type does not exist. fiore@0: */ fiore@0: public List getValues(String propertyType){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: return new ArrayList(e.values); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the view object associated with a property type in this NodeProperties instance. A view object is fiore@0: * defined by the client of this class and it holds the information needed for a visual representation fiore@3: * of the property. fiore@3: * fiore@0: * @param type the property type the returned view is associated with fiore@0: * @return the View object or null if non has been set previously fiore@0: */ fiore@0: public Object getView(String type){ fiore@0: Entry e = properties.get(type); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+type); fiore@0: return e.view; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Sets the View object for the a type of properties. The NodeProperties only works fiore@0: * as a holder as for the view objects. The client code of this class has to define fiore@3: * its own view. fiore@3: * fiore@0: * @param type the type of property the view is associated with. fiore@0: * @param o an object defined by user fiore@0: * @see #getView(String) fiore@0: */ fiore@0: public void setView(String type, Object o){ fiore@0: Entry e = properties.get(type); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+type); fiore@0: e.view = o; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns a reference to the modifier object associated with a property type. Changes to the returned fiore@0: * reference will affect the internal state of the NodeProperty object. fiore@0: * fiore@0: * @param propertyType a property type fiore@0: * @return the modifiers of the specified property type or null if either fiore@0: * the property type is null or it was not listed in the property type definition fiore@0: * passed to the constructor fiore@0: */ fiore@0: public Modifiers getModifiers(String propertyType){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: return e.modifiers; fiore@0: } fiore@0: fiore@0: /** fiore@3: * Adds a value of the type passed as argument. fiore@3: * fiore@0: * @param propertyType a property type defined in the property type definition passed as argument to the constructor fiore@0: * @param propertyValue the value to add to the property type fiore@0: */ fiore@0: public void addValue(String propertyType, String propertyValue){ fiore@0: Entry e = properties.get(propertyType); fiore@0: /* no such property type exists */ fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: e.values.add(propertyValue); fiore@0: e.indexes.add(new LinkedHashSet()); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Adds a value of the type passed as argument and sets the modifier indexes for it as for fiore@3: * {@link Modifiers#setIndexes(int, Set)}. fiore@3: * fiore@0: * @param propertyType a property type fiore@0: * @param propertyValue a property value fiore@0: * @param modifierIndexes the modifier set of indexes fiore@0: * fiore@0: * @throws IllegalArgumentException if propertyType fiore@0: * is not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@0: public void addValue(String propertyType, String propertyValue, Set modifierIndexes){ fiore@0: for(Integer i : modifierIndexes) fiore@0: if((i < 0)||(i >= getModifiers(propertyType).getTypes().size())) fiore@0: throw new ArrayIndexOutOfBoundsException("Index "+ i + fiore@0: " corresponds to no Modifier type (modifierType size = "+ fiore@0: getModifiers(propertyType).getTypes().size()+")" ); fiore@0: fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: e.values.add(propertyValue); fiore@0: e.indexes.add(modifierIndexes); fiore@0: } fiore@0: fiore@0: /** fiore@3: * Removes the value at the specified index for the specified property type. fiore@3: * fiore@0: * @param propertyType a property type fiore@0: * @param valueIndex the index of the value to remove fiore@0: * @return the removed value fiore@0: * @throws IllegalArgumentException if propertyType fiore@0: * is not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@0: public String removeValue(String propertyType, int valueIndex){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: e.indexes.remove(valueIndex); fiore@0: return e.values.remove(valueIndex); fiore@0: } fiore@0: fiore@0: /** fiore@3: * Sets the value of a property type at the specified index to a new value. fiore@3: * fiore@0: * @param propertyType a property type fiore@0: * @param valueIndex the index of the value which must be replaced fiore@0: * @param newValue the new value for the specified index fiore@3: * @throws IllegalArgumentException if propertyType fiore@0: * is not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@0: public void setValue(String propertyType, int valueIndex, String newValue){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: e.values.set(valueIndex, newValue); fiore@0: } fiore@0: fiore@0: /** fiore@3: * Removes all the values and modifiers for a specific type. fiore@3: * fiore@0: * @param propertyType the type whose property and modifiers must be removed fiore@0: * @throws IllegalArgumentException if propertyType fiore@0: * is not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@0: public void clear(String propertyType){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: e.values.clear(); fiore@0: e.indexes.clear(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Removes all the values and modifiers of this object. fiore@0: */ fiore@0: public void clear(){ fiore@0: for(String type : types){ fiore@0: clear(type); fiore@0: } fiore@0: } fiore@0: fiore@0: /** fiore@3: * Returns true if this NodeProperties contains no values. fiore@3: * fiore@0: * @return true if this NodeProperties contains no values fiore@0: */ fiore@0: public boolean isEmpty(){ fiore@0: boolean empty = true; fiore@0: for(String type : types) fiore@0: if(!properties.get(type).values.isEmpty()){ fiore@0: empty = false; fiore@0: break; fiore@0: } fiore@0: return empty; fiore@0: } fiore@0: fiore@0: /** fiore@3: * Returns true if there are no values for the specified type in this NodeProperties instance. fiore@3: * fiore@5: * @param propertyType the property type to be checked for value presence fiore@0: * @return true if there are no values for the specified type in this NodeProperties instance fiore@0: */ fiore@0: public boolean typeIsEmpty(String propertyType){ fiore@0: Entry e = properties.get(propertyType); fiore@0: if(e == null) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType); fiore@0: return e.values.isEmpty(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * true if this NodeProperties object has no types. This can happen if the constructor fiore@3: * has been called with an empty or null property type definition. fiore@3: * fiore@0: * @return true if this NodeProperties object has no types fiore@0: */ fiore@0: public boolean isNull(){ fiore@0: return types.isEmpty(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns a string representation of types, value and modifiers of this property. Such a fiore@3: * representation can be passed as argument to {@link #fill(String)} to populate a NodeProperties fiore@0: * object. Such NodeProperties object though must have the same type and modifier definition of fiore@0: * the object this method is called on. fiore@0: * fiore@0: * @return a string representation of the values and modifiers of this object fiore@0: */ fiore@0: @Override fiore@0: public String toString(){ fiore@0: StringBuilder builder = new StringBuilder(); fiore@0: for(String type : types){ fiore@0: builder.append(TYPE_ENTRY_SEPARATOR).append(type); fiore@0: int propertyValueIndex = 0; fiore@0: for(String value : properties.get(type).values){ fiore@0: builder.append(VALUE_ENTRY_SEPARATOR); fiore@0: builder.append(value); fiore@0: for(int modifierIndex : getModifiers(type).getIndexes(propertyValueIndex)){ fiore@0: builder.append(MODIFIER_ENTRY_SEPARATOR); fiore@0: builder.append(modifierIndex); fiore@0: } fiore@0: propertyValueIndex++; fiore@0: } fiore@0: } fiore@0: return builder.toString(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Fills up the this property according to the string passed as arguments fiore@3: * The string must be generated by calling toString on a NodeProperty instance fiore@0: * holding the same property types as type, otherwise an IllegalArgumentException fiore@0: * is likely to be thrown. fiore@0: * fiore@0: * @param s the string representation of the property values, such as the one returned fiore@0: * by toString() fiore@0: * @throws IllegalArgumentException if s contains property types which fiore@0: * are not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@3: public void fill(String s){ fiore@0: /* clear up previous values */ fiore@0: clear(); fiore@0: fiore@0: /* a property entry is a string with the type of the property followed by value * fiore@0: * entries of that type. all value entries begin with the VALUE_SEPARATOR. * fiore@0: * a value entry is a property value followed by its modifiers entries, a modifier entry * fiore@0: * is a modifier beginning with the MODIFIER_SEPARATOR */ fiore@0: String propertyEntries[] = s.split(TYPE_ENTRY_SEPARATOR); fiore@0: for(String propertyEntry : propertyEntries){ fiore@0: String[] typeEntries = propertyEntry.split(VALUE_ENTRY_SEPARATOR); fiore@0: String type = typeEntries[0]; fiore@0: for(int i=1;i modifiers = new LinkedHashSet(); fiore@0: for(int j=1;j)properties.clone(); fiore@0: for(String key : p.properties.keySet()){ fiore@0: Entry old = p.properties.get(key); fiore@0: Entry q = new Entry(); fiore@0: q.values = new ArrayList(); fiore@0: q.indexes = new ArrayList>(); fiore@0: q.view = old.view; fiore@0: if(old.modifiers != null){ fiore@0: q.modifiers = new Modifiers(old.modifiers.modifierTypes, q.indexes); fiore@0: for(String modifierType : q.modifiers.getTypes()) fiore@0: q.modifiers.setView(modifierType, old.modifiers.getView(modifierType)); fiore@0: } fiore@0: p.properties.put(key, q); fiore@0: } fiore@0: return p; fiore@0: } fiore@0: fiore@0: private class Entry{ fiore@0: List values; fiore@0: List> indexes; fiore@0: Modifiers modifiers; fiore@0: Object view; fiore@0: } fiore@0: fiore@0: /** fiore@0: * A modifier is a label peculiar of a certain subset of properties. For example in fiore@0: * a UML class diagram one or more methods can be labelled as public, private or protected. fiore@0: * Had a NodeProperties instance been used to represent the methods of a class node, there would be then fiore@0: * one modifier for each label: one for public, one for private, and one for protected. To each modifier fiore@0: * a view-object can be associated, which describes how these labels would be rendered visually. fiore@0: * Following on from the UML example, a view-object would be used with the protected modifier fiore@0: * to hold the information that the properties which are assigned such a modifier must be prefixed fiore@0: * with the '#' sign. fiore@0: */ fiore@0: public class Modifiers{ fiore@0: private Modifiers(Set modifierTypes, List> indexes){ fiore@0: views = new LinkedHashMap(); fiore@0: indexesRef = indexes; fiore@0: this.modifierTypes = Collections.unmodifiableList(new ArrayList(modifierTypes)); fiore@0: for(String modifierType : modifierTypes){ fiore@0: views.put(modifierType,null); fiore@0: } fiore@0: } fiore@0: fiore@0: /* only used by NodeProperties.clone() method */ fiore@0: private Modifiers(List modifierTypes, List> indexes){ fiore@0: views = new LinkedHashMap(); fiore@0: indexesRef = indexes; fiore@0: this.modifierTypes = modifierTypes; fiore@0: for(String modifierType : modifierTypes){ fiore@0: views.put(modifierType,null); fiore@0: } fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the list of modifier types, as per the type definition passed as argument to the NodeProperties fiore@0: * constructor. fiore@3: * fiore@0: * @return a list of modifier types fiore@0: * @see NodeProperties#NodeProperties(LinkedHashMap) fiore@0: */ fiore@0: public List getTypes(){ fiore@0: return modifierTypes; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the view object associated with a modifier type in this Modifier instance. A view object is fiore@0: * defined by the client of this class and it holds the information needed for a visual representation fiore@0: * of the modifier. fiore@3: * fiore@3: * @param modifierType the property type the returned view is associated with fiore@0: * @return the View object or null if non has been set previously fiore@0: */ fiore@0: public Object getView(String modifierType){ fiore@0: return views.get(modifierType); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Sets the View object for the a type of modifier. The NodeProperties only works fiore@0: * as a holder as for the view objects. The client code of this class has to define fiore@0: * its own view, which will then be used by the client itself to visualise the modifier fiore@3: * fiore@3: * @param modifierType the type of modifier the view is associated with. fiore@5: * @param view an object defined by user defining how this property looks like fiore@0: * @see #getView(String) fiore@0: * @throws IllegalArgumentException if modifierType fiore@0: * is not among the ones in the type definition passed as argument to the constructor fiore@0: */ fiore@0: public void setView(String modifierType, Object view){ fiore@0: if(!views.containsKey(modifierType)) fiore@0: throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+modifierType); fiore@0: views.put(modifierType,view); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Returns the modifier indexes for the specified property value. Each property type can be associated fiore@0: * with one or more modifier type and each property value can be assigned one or more modifier. The fiore@0: * integer set returned by this method tells the client code to which modifiers the property value at the index fiore@0: * passed as argument is assigned. The returned indexes refer to the modifier list returned by {@link #getTypes()} fiore@0: * fiore@5: * @param propertyValueIndex the index of the property value for which the set of modifier indexes fiore@5: * is being queried fiore@5: * @return a set of indexes of the modifier types the property value at the index passed as argument fiore@0: * is assigned to fiore@0: */ fiore@0: public Set getIndexes(int propertyValueIndex){ fiore@0: Set set = indexesRef.get(propertyValueIndex); fiore@0: return (new LinkedHashSet(set)); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Set the modifier indexes for the property value at the index passed as argument fiore@3: * fiore@0: * @param propertyValueIndex the index of the property value fiore@0: * @param modifierIndexes a set of indexes which refer to the list returned by {@link #getTypes()} fiore@0: * @throws ArrayIndexOutOfBoundsException if one or more of the indexes in modifierIndexes are lower than 0 or greater fiore@0: * or equal to the size of the modifier type list returned by {@link #getTypes()}. The same exception is also thrown fiore@0: * if propertyValueIndex is lower than 0 or greater or equal to the property values list returned by {@link NodeProperties#getValues(String)} fiore@0: * passing as argument the same property type passed to {@link NodeProperties#getModifiers(String)} to obtain fiore@0: * this Modifiers instance. fiore@0: * @see #getIndexes(int) fiore@0: */ fiore@0: public void setIndexes(int propertyValueIndex, Set modifierIndexes){ fiore@0: for(Integer i : modifierIndexes) fiore@0: if((i < 0)||(i >= getTypes().size())) fiore@0: throw new ArrayIndexOutOfBoundsException("Index "+ i + " corresponds to no Modifier Type (modifierType size = "+getTypes().size()+")" ); fiore@0: fiore@0: Set m = indexesRef.get(propertyValueIndex); fiore@0: m.clear(); fiore@0: m.addAll(modifierIndexes); fiore@0: } fiore@0: fiore@0: /** fiore@3: * Removes all the modifier indexes for a property value at the specified index. fiore@3: * fiore@0: * @param propertyValueIndex the index of the property value fiore@0: */ fiore@0: public void clear(int propertyValueIndex){ fiore@0: Set m = indexesRef.get(propertyValueIndex); fiore@0: m.clear(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * true if this Modifiers object has no types. This can happen if the constructor fiore@0: * has been called with an empty or null modifier in the type definition passed as argument fiore@3: * to the constructor of the NodeProperties object holding this Modifiers object. fiore@3: * fiore@0: * @return true if this Modifiers object has no types fiore@0: */ fiore@0: public boolean isNull(){ fiore@0: return modifierTypes.isEmpty(); fiore@0: } fiore@0: fiore@0: private LinkedHashMap views; fiore@0: private final List modifierTypes; fiore@0: private List> indexesRef; fiore@0: } fiore@0: fiore@0: /* for each property (key) I associate a Couple (value) made out of * fiore@0: * a list of the property values plus a list of possible modifiers */ fiore@0: private LinkedHashMap properties; fiore@0: private final List types; fiore@0: private Map> typeDefinition; fiore@0: fiore@0: private static final LinkedHashMap> EMPTY_PROPERTY_TYPE_DEFINITION = new LinkedHashMap>(); fiore@0: private static final Set EMPTY_MODIFIER_TYPE_DEFINITION = Collections.emptySet(); fiore@0: private static final String ILLEGAL_TYPE_MSG = "argument must be in type definition list: "; fiore@0: /** fiore@0: * A special static instance of the class corresponding to the null NodeProperties. A null NodeProperties instance will have isNull() returning true, fiore@0: * which means it has no property types associated. fiore@3: * @see #isNull() fiore@0: */ fiore@0: public static final NodeProperties NULL_PROPERTIES = new NodeProperties(EMPTY_PROPERTY_TYPE_DEFINITION); fiore@0: fiore@0: private final static String TYPE_ENTRY_SEPARATOR = "\n:"; fiore@0: private final static String VALUE_ENTRY_SEPARATOR = "\n;"; fiore@0: private final static String MODIFIER_ENTRY_SEPARATOR = "\n,"; fiore@0: }