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