CompoundProperty.java
package org.opentrafficsim.base.modelproperties;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.opentrafficsim.base.logger.CategoryLogger;
/**
* Compound property.
* <p>
* Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* <p>
* $LastChangedDate: 2016-05-28 11:33:31 +0200 (Sat, 28 May 2016) $, @version $Revision: 2051 $, by $Author: averbraeck $,
* initial version 30 dec. 2014 <br>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
public class CompoundProperty extends AbstractProperty<List<Property<?>>> implements Serializable
{
/** */
private static final long serialVersionUID = 20150000L;
/** Properties directly contained in this one. */
private final List<Property<?>> value = new ArrayList<>();
/** Map of all AbstractProperties known in this property group. */
private Map<String, Property<?>> propertyGroup = new HashMap<>();
/**
* Construct a CompoundProperty.
* @param key String; the unique key of the new property
* @param shortName String; the short name of the new CompoundProperty
* @param description String; description of the new CompoundProperty (may use HTML mark up)
* @param initialValue Integer; the initial value of the new CompoundProperty
* @param readOnly boolean; if true this CompoundProperty can not be altered
* @param displayPriority int; the display priority of the new CompoundProperty
* @throws PropertyException if <cite>key</cite> is already in use
*/
public CompoundProperty(final String key, final String shortName, final String description,
final List<Property<?>> initialValue, final boolean readOnly, final int displayPriority) throws PropertyException
{
super(key, displayPriority, shortName, description);
if (null != initialValue)
{
for (Property<?> ap : initialValue)
{
add(ap);
}
}
setReadOnly(readOnly);
}
/** {@inheritDoc} */
@Override
public final List<Property<?>> getValue()
{
return new ArrayList<Property<?>>(this.value); // return a defensive copy
}
/** {@inheritDoc} */
@Override
public final void setValue(final List<Property<?>> newValue) throws PropertyException
{
for (Property<?> ap : getValue())
{
remove(ap); // make good use of the fact that getValue makes a defensive copy
}
for (Property<?> ap : newValue)
{
add(ap);
}
}
/**
* Find an embedded Property that has a specified shortName. <br>
* Return the first matching one, or null if none of the embedded AbstractProperties has the specified name.
* @param key String; the key of the sought embedded Property
* @return Property<?>; the first matching embedded AbstractProperty or null if there is no embedded Property with the
* specified name
*/
public final Property<?> findSubPropertyByKey(final String key)
{
// CategoryLogger.trace(Cat.BASE, "Searching property {}", key);
Iterator<Property<?>> i = this.iterator();
while (i.hasNext())
{
Property<?> ap = i.next();
// CategoryLogger.trace(Cat.BASE, "Inspecting {}", ap.getKey());
if (ap.getKey().equals(key))
{
return ap;
}
}
return null;
}
/**
* Add a Property at a specified position.
* @param index int; the position where the Property must be added
* @param ap Property; the property to add
* @throws PropertyException when this CompoundProperty is read-only, or index is out of range
*/
public final void add(final int index, final Property<?> ap) throws PropertyException
{
if (isReadOnly())
{
throw new PropertyException("Cannot modify a read-only CompoundProperty");
}
if (index < 0 || index > this.value.size())
{
throw new PropertyException("index is out of range");
}
if (this.propertyGroup.containsKey(ap.getKey()))
{
throw new PropertyException("AbstractProperty " + ap + " is already registered in property group of " + this);
}
// Recursively verify that there are no collisions on the key
for (Property<?> subProperty : ap)
{
if (this.propertyGroup.containsKey(subProperty.getKey()))
{
throw new PropertyException(
"A property with key " + subProperty.getKey() + " is already known in this property group");
}
}
// Add all sub-properties to this property group
for (Property<?> subProperty : ap)
{
this.propertyGroup.put(subProperty.getKey(), subProperty);
if (subProperty instanceof CompoundProperty)
{
// Make compound sub properties share our property group map
((CompoundProperty) subProperty).setPropertyGroup(this.propertyGroup);
}
}
this.value.add(index, ap);
((AbstractProperty<?>) ap).setParent(this);
}
/**
* Add a Property at the end of the list.
* @param ap Property; the property to add
* @throws PropertyException when this CompoundProperty is read-only
*/
public final void add(final Property<?> ap) throws PropertyException
{
add(this.value.size(), ap);
}
/**
* Remove a sub property from this CompoundProperty.
* @param index int; the position of the sub property to remove
* @throws PropertyException when this CompoundProperty is read-only, or index is out of range
*/
public final void remove(final int index) throws PropertyException
{
if (isReadOnly())
{
throw new PropertyException("Cannot modify a read-only CompoundProperty");
}
if (index < 0 || index >= this.value.size())
{
throw new PropertyException("index is out of range");
}
this.propertyGroup.remove(this.value.get(index));
Property<?> removed = this.value.remove(index);
((AbstractProperty<?>) removed).setParent(null);
if (removed instanceof CompoundProperty)
{
((CompoundProperty) removed).setPropertyGroup(null); // let child CompoundProperty rebuild its property group
}
}
/**
* Remove a property from this CompoundProperty.
* @param removeMe AbstractProperty the property that must be removed
* @throws PropertyException when the supplied property cannot be removed (probably because it is not part of this
* CompoundProperty)
*/
public final void remove(final Property<?> removeMe) throws PropertyException
{
int i = this.value.indexOf(removeMe);
if (i < 0)
{
throw new PropertyException(
"Cannot remove property " + removeMe + " because it is not part of this compound property");
}
remove(i);
}
/**
* Return the number of sub properties of this CompoundProperty.
* @return int; the number of sub properties of this CompoundProperty
*/
public final int size()
{
return this.value.size();
}
/**
* Update the property group when this CompoundProperty is added or removed from another CompoundProperty.
* @param newPropertyGroup Map<String, AbstractProperty<?>>; if non-null; this is the property group of the new
* parent which we are now part of and we must use that in lieu of our own; if null; we are being removed from
* our parent and we must rebuild our own property group
*/
protected final void setPropertyGroup(final Map<String, Property<?>> newPropertyGroup)
{
if (null == newPropertyGroup)
{
// Rebuild the property group (after removal from parent
this.propertyGroup = new HashMap<String, Property<?>>();
for (Property<?> ap : this.value)
{
this.propertyGroup.put(ap.getKey(), ap);
}
}
else
{
this.propertyGroup = newPropertyGroup;
for (Property<?> ap : this)
{
this.propertyGroup.put(ap.getKey(), ap);
}
}
}
/**
* Return the sub property at a specified index.
* @param index int; the index of the property to retrieve
* @return AbstractProperty; the sub property at the specified index
* @throws PropertyException when index is out of range
*/
public final Property<?> get(final int index) throws PropertyException
{
if (index < 0 || index >= this.value.size())
{
throw new PropertyException("index is out of range");
}
return this.value.get(index);
}
/**
* Return the sub-items in display order.
* @return ArrayList<AbstractProperty<?>>; the sub-items in display order
*/
public final List<Property<?>> displayOrderedValue()
{
List<Property<?>> result = new ArrayList<>(this.value);
final List<Property<?>> list = this.value;
Collections.sort(result, new Comparator<Property<?>>()
{
@Override
public int compare(final Property<?> arg0, final Property<?> arg1)
{
int dp0 = arg0.getDisplayPriority();
int dp1 = arg1.getDisplayPriority();
if (dp0 < dp1)
{
return -1;
}
else if (dp0 > dp1)
{
return 1;
}
int i0 = list.indexOf(arg0);
int i1 = list.indexOf(arg1);
if (i0 < i1)
{
return -1;
}
else if (i0 > i1)
{
return 1;
}
return 0;
}
});
/*-
CategoryLogger.trace(Cat.BASE, "Sorted {}", getShortName());
int pos = 0;
for (AbstractProperty<?> ap : result)
{
CategoryLogger.trace(Cat.BASE, ++pos + " - " + ap.getDisplayPriority() + ": " + ap.getShortName());
}
*/
return result;
}
/** {@inheritDoc} */
@Override
public final String htmlStateDescription()
{
StringBuilder result = new StringBuilder();
result.append("<table border=\"1\">");
result.append("<tr><th align=\"left\">" + getShortName() + "</th></tr>\n");
for (Property<?> ap : displayOrderedValue())
{
result.append("<tr><td> " + ap.htmlStateDescription() + "</td></tr>\n");
}
result.append("</table>\n");
return result.toString();
}
/** {@inheritDoc} */
@Override
public final CompoundProperty deepCopy()
{
ArrayList<Property<?>> copyOfValue = new ArrayList<>();
for (Property<?> ap : this.value)
{
copyOfValue.add(ap.deepCopy());
}
try
{
return new CompoundProperty(getKey(), getShortName(), getDescription(), copyOfValue, isReadOnly(),
getDisplayPriority());
}
catch (PropertyException exception)
{
CategoryLogger.always().error(exception, "PropertyException in deepCopy. Cannot happen");
}
return null; // NOTREACHED
}
/**
* Retrieve the property group. DO NOT MODIFY the result.
* @return Map<String, AbstractProperty<?>>; the property group map
*/
protected final Map<String, Property<?>> getPropertyGroup()
{
return this.propertyGroup;
}
}