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