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: }