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