View Javadoc
1   package org.opentrafficsim.base.modelproperties;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.Comparator;
7   import java.util.HashMap;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.Map;
11  
12  /**
13   * Compound property.
14   * <p>
15   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
17   * <p>
18   * $LastChangedDate: 2016-05-28 11:33:31 +0200 (Sat, 28 May 2016) $, @version $Revision: 2051 $, by $Author: averbraeck $,
19   * initial version 30 dec. 2014 <br>
20   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
21   */
22  public class CompoundProperty extends AbstractProperty<List<Property<?>>> implements Serializable
23  {
24      /** */
25      private static final long serialVersionUID = 20150000L;
26  
27      /** Properties directly contained in this one. */
28      private final List<Property<?>> value = new ArrayList<>();
29  
30      /** Map of all AbstractProperties known in this property group. */
31      private Map<String, Property<?>> propertyGroup = new HashMap<>();
32  
33      /**
34       * Construct a CompoundProperty.
35       * @param key String; the unique key of the new property
36       * @param shortName String; the short name of the new CompoundProperty
37       * @param description String; description of the new CompoundProperty (may use HTML mark up)
38       * @param initialValue Integer; the initial value of the new CompoundProperty
39       * @param readOnly boolean; if true this CompoundProperty can not be altered
40       * @param displayPriority int; the display priority of the new CompoundProperty
41       * @throws PropertyException if <cite>key</cite> is already in use
42       */
43      public CompoundProperty(final String key, final String shortName, final String description,
44              final List<Property<?>> initialValue, final boolean readOnly, final int displayPriority) throws PropertyException
45      {
46          super(key, displayPriority, shortName, description);
47          if (null != initialValue)
48          {
49              for (Property<?> ap : initialValue)
50              {
51                  add(ap);
52              }
53          }
54          setReadOnly(readOnly);
55      }
56  
57      /** {@inheritDoc} */
58      @Override
59      public final List<Property<?>> getValue()
60      {
61          return new ArrayList<Property<?>>(this.value); // return a defensive copy
62      }
63  
64      /** {@inheritDoc} */
65      @Override
66      public final void setValue(final List<Property<?>> newValue) throws PropertyException
67      {
68          for (Property<?> ap : getValue())
69          {
70              remove(ap); // make good use of the fact that getValue makes a defensive copy
71          }
72          for (Property<?> ap : newValue)
73          {
74              add(ap);
75          }
76      }
77  
78      /**
79       * Find an embedded Property that has a specified shortName. <br>
80       * Return the first matching one, or null if none of the embedded AbstractProperties has the specified name.
81       * @param key String; the key of the sought embedded Property
82       * @return Property&lt;?&gt;; the first matching embedded AbstractProperty or null if there is no embedded Property with the
83       *         specified name
84       */
85      public final Property<?> findSubPropertyByKey(final String key)
86      {
87          // System.out.println("Searching property " + name);
88          Iterator<Property<?>> i = this.iterator();
89          while (i.hasNext())
90          {
91              Property<?> ap = i.next();
92              // System.out.println("Inspecting " + ap.getKey());
93              if (ap.getKey().equals(key))
94              {
95                  return ap;
96              }
97          }
98          return null;
99      }
100 
101     /**
102      * Add a Property at a specified position.
103      * @param index int; the position where the Property must be added
104      * @param ap Property; the property to add
105      * @throws PropertyException when this CompoundProperty is read-only, or index is out of range
106      */
107     public final void add(final int index, final Property<?> ap) throws PropertyException
108     {
109         if (isReadOnly())
110         {
111             throw new PropertyException("Cannot modify a read-only CompoundProperty");
112         }
113         if (index < 0 || index > this.value.size())
114         {
115             throw new PropertyException("index is out of range");
116         }
117         if (this.propertyGroup.containsKey(ap.getKey()))
118         {
119             throw new PropertyException("AbstractProperty " + ap + " is already registered in property group of " + this);
120         }
121         // Recursively verify that there are no collisions on the key
122         for (Property<?> subProperty : ap)
123         {
124             if (this.propertyGroup.containsKey(subProperty.getKey()))
125             {
126                 throw new PropertyException(
127                         "A property with key " + subProperty.getKey() + " is already known in this property group");
128             }
129         }
130         // Add all sub-properties to this property group
131         for (Property<?> subProperty : ap)
132         {
133             this.propertyGroup.put(subProperty.getKey(), subProperty);
134             if (subProperty instanceof CompoundProperty)
135             {
136                 // Make compound sub properties share our property group map
137                 ((CompoundProperty) subProperty).setPropertyGroup(this.propertyGroup);
138             }
139         }
140         this.value.add(index, ap);
141         ((AbstractProperty<?>) ap).setParent(this);
142     }
143 
144     /**
145      * Add a Property at the end of the list.
146      * @param ap Property; the property to add
147      * @throws PropertyException when this CompoundProperty is read-only
148      */
149     public final void add(final Property<?> ap) throws PropertyException
150     {
151         add(this.value.size(), ap);
152     }
153 
154     /**
155      * Remove a sub property from this CompoundProperty.
156      * @param index int; the position of the sub property to remove
157      * @throws PropertyException when this CompoundProperty is read-only, or index is out of range
158      */
159     public final void remove(final int index) throws PropertyException
160     {
161         if (isReadOnly())
162         {
163             throw new PropertyException("Cannot modify a read-only CompoundProperty");
164         }
165         if (index < 0 || index >= this.value.size())
166         {
167             throw new PropertyException("index is out of range");
168         }
169         this.propertyGroup.remove(this.value.get(index));
170         Property<?> removed = this.value.remove(index);
171         ((AbstractProperty<?>) removed).setParent(null);
172         if (removed instanceof CompoundProperty)
173         {
174             ((CompoundProperty) removed).setPropertyGroup(null); // let child CompoundProperty rebuild its property group
175         }
176     }
177 
178     /**
179      * Remove a property from this CompoundProperty.
180      * @param removeMe AbstractProperty the property that must be removed
181      * @throws PropertyException when the supplied property cannot be removed (probably because it is not part of this
182      *             CompoundProperty)
183      */
184     public final void remove(final Property<?> removeMe) throws PropertyException
185     {
186         int i = this.value.indexOf(removeMe);
187         if (i < 0)
188         {
189             throw new PropertyException(
190                     "Cannot remove property " + removeMe + " because it is not part of this compound property");
191         }
192         remove(i);
193     }
194 
195     /**
196      * Return the number of sub properties of this CompoundProperty.
197      * @return int; the number of sub properties of this CompoundProperty
198      */
199     public final int size()
200     {
201         return this.value.size();
202     }
203 
204     /**
205      * Update the property group when this CompoundProperty is added or removed from another CompoundProperty.
206      * @param newPropertyGroup Map&lt;String, AbstractProperty&lt;?&gt;&gt;; if non-null; this is the property group of the new
207      *            parent which we are now part of and we must use that in lieu of our own; if null; we are being removed from
208      *            our parent and we must rebuild our own property group
209      */
210     protected final void setPropertyGroup(final Map<String, Property<?>> newPropertyGroup)
211     {
212         if (null == newPropertyGroup)
213         {
214             // Rebuild the property group (after removal from parent
215             this.propertyGroup = new HashMap<String, Property<?>>();
216             for (Property<?> ap : this.value)
217             {
218                 this.propertyGroup.put(ap.getKey(), ap);
219             }
220         }
221         else
222         {
223             this.propertyGroup = newPropertyGroup;
224             for (Property<?> ap : this)
225             {
226                 this.propertyGroup.put(ap.getKey(), ap);
227             }
228         }
229     }
230 
231     /**
232      * Return the sub property at a specified index.
233      * @param index int; the index of the property to retrieve
234      * @return AbstractProperty; the sub property at the specified index
235      * @throws PropertyException when index is out of range
236      */
237     public final Property<?> get(final int index) throws PropertyException
238     {
239         if (index < 0 || index >= this.value.size())
240         {
241             throw new PropertyException("index is out of range");
242         }
243         return this.value.get(index);
244     }
245 
246     /**
247      * Return the sub-items in display order.
248      * @return ArrayList&lt;AbstractProperty&lt;?&gt;&gt;; the sub-items in display order
249      */
250     public final List<Property<?>> displayOrderedValue()
251     {
252         List<Property<?>> result = new ArrayList<>(this.value);
253         final List<Property<?>> list = this.value;
254         Collections.sort(result, new Comparator<Property<?>>()
255         {
256 
257             @Override
258             public int compare(final Property<?> arg0, final Property<?> arg1)
259             {
260                 int dp0 = arg0.getDisplayPriority();
261                 int dp1 = arg1.getDisplayPriority();
262                 if (dp0 < dp1)
263                 {
264                     return -1;
265                 }
266                 else if (dp0 > dp1)
267                 {
268                     return 1;
269                 }
270                 int i0 = list.indexOf(arg0);
271                 int i1 = list.indexOf(arg1);
272                 if (i0 < i1)
273                 {
274                     return -1;
275                 }
276                 else if (i0 > i1)
277                 {
278                     return 1;
279                 }
280                 return 0;
281             }
282 
283         });
284         /*-
285         System.out.println("Sorted " + getShortName());
286         int pos = 0;
287         for (AbstractProperty<?> ap : result)
288         {
289             System.out.println(++pos + " - " + ap.getDisplayPriority() + ": " + ap.getShortName());
290         }
291          */
292         return result;
293     }
294 
295     /** {@inheritDoc} */
296     @Override
297     public final String htmlStateDescription()
298     {
299         StringBuilder result = new StringBuilder();
300         result.append("<table border=\"1\">");
301         result.append("<tr><th align=\"left\">" + getShortName() + "</th></tr>\n");
302         for (Property<?> ap : displayOrderedValue())
303         {
304             result.append("<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;" + ap.htmlStateDescription() + "</td></tr>\n");
305         }
306         result.append("</table>\n");
307         return result.toString();
308     }
309 
310     /** {@inheritDoc} */
311     @Override
312     public final CompoundProperty deepCopy()
313     {
314         ArrayList<Property<?>> copyOfValue = new ArrayList<>();
315         for (Property<?> ap : this.value)
316         {
317             copyOfValue.add(ap.deepCopy());
318         }
319         try
320         {
321             return new CompoundProperty(getKey(), getShortName(), getDescription(), copyOfValue, isReadOnly(),
322                     getDisplayPriority());
323         }
324         catch (PropertyException exception)
325         {
326             System.err.println("Cannot happen");
327             exception.printStackTrace();
328         }
329         return null; // NOTREACHED
330     }
331 
332     /**
333      * Retrieve the property group. DO NOT MODIFY the result.
334      * @return Map&lt;String, AbstractProperty&lt;?&gt;&gt;; the property group map
335      */
336     protected final Map<String, Property<?>> getPropertyGroup()
337     {
338         return this.propertyGroup;
339     }
340 
341 }