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<?>; 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<String, AbstractProperty<?>>; 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<AbstractProperty<?>>; 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> " + 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<String, AbstractProperty<?>>; the property group map
336 */
337 protected final Map<String, Property<?>> getPropertyGroup()
338 {
339 return this.propertyGroup;
340 }
341
342 }