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