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