/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.attrs.collection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.attrs.attr.Attribute; import org.arakhne.afc.attrs.attr.AttributeImpl; import org.arakhne.afc.attrs.attr.AttributeType; import org.arakhne.afc.attrs.attr.AttributeValue; import org.arakhne.afc.attrs.attr.AttributeValueImpl; import org.arakhne.afc.references.SoftValueTreeMap; /** * This class contains a collection of attribute containers and * tries to gather the data. * This class contains a collection of AttributeContainers and * exhibites the values of the attributes of all these containers. * This class follows the following rules (in that order) * to retreive the value of an attribute:<ol> * <li>If the attribute is defined in none of the containers, throws the standard exception;</li> * <li>If the attribute is defined in only one of the containers, replies the attribute value itself;</li> * <li>If the attribute is defined in more than one container:<ol> * <li>if all the values are equal, then replies one of the attribute values;</li> * <li>if the values are not equal and all the values have equivalent types (as replied * by {@link AttributeType#isAssignableFrom(AttributeType)}), then replies an * attribute value with a "undefined" value and of the type of one of the values;</li> * <li>if the values are not equal and one of the value has not an equivalent type to * the others (as replied by {@link AttributeType#isAssignableFrom(AttributeType)}), * then replies an attribute value with a "undefined" value and of the type OBJECT.</li> * </ol></li> * </ol> * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 4.0 */ public class MultiAttributeProvider extends AbstractAttributeProvider { private static final long serialVersionUID = -2673023673767450220L; /** Cache of the attribute values. */ transient Map<String, AttributeValue> cache = new SoftValueTreeMap<>(); /** Cache of the names of the attributes. */ Set<String> names; private Collection<AttributeProvider> containers = new ArrayList<>(); @Override public void toMap(Map<String, Object> mapToFill) { for (final AttributeProvider provider : this.containers) { mapToFill.putAll(provider.toMap()); } } /** Replies the value associated to the specified name. */ private Attribute extract(String name) { final AttributeValue value; if (this.cache.containsKey(name)) { value = this.cache.get(name); } else { final ManyValueAttributeValue result = new ManyValueAttributeValue(); AttributeValue attrValue; for (final AttributeProvider c : this.containers) { attrValue = c.getAttribute(name); assign(result, attrValue); } value = canonize(result); this.cache.put(name, value); } return (value != null) ? new AttributeImpl(name, value) : null; } /** Assign the value v2 to v1 and change v1 according * to the types of v1 and v2. * Do not forget to invoke {@link #canonize(ManyValueAttributeValue)} on * {@code v1}. * * @param v1 first value. * @param v2 second value. */ static void assign(ManyValueAttributeValue v1, AttributeValue v2) { if (v2 != null) { v1.setTopType(v2.getType()); } if (v2 == null || !v2.isAssigned()) { v1.setMultipleValues(true); } else { assert v2.isAssigned(); if (!v1.isAssigned()) { v1.setValue(v2); } else if (!v2.equals(v1)) { v1.setMultipleValues(true); if (!v1.isAssignableFrom(v2)) { if (!v2.isAssignableFrom(v1)) { v1.setInternalValue(null, AttributeType.OBJECT); } else { v1.setInternalValue(null, v2.getType()); } } } } } /** Replace any indicator put by {@link #assign(ManyValueAttributeValue, AttributeValue)} * to retreive a standard attribute value. * * @param v is the value to canonize. * @return the canonized value. */ static AttributeValue canonize(ManyValueAttributeValue v) { final AttributeValueImpl attr; if (v.hasMultipleValues()) { if (v.getTopType() == null) { return null; } attr = new AttributeValueImpl(v.getTopType(), null); } else { attr = new AttributeValueImpl(); attr.setValue(v); } return attr; } /** Replies a collection on the containers. * * @return a collection on the containers. */ @Pure protected Collection<AttributeProvider> containers() { return Collections.unmodifiableCollection(this.containers); } /** Add a container in this set. * * @param container the container. * @return <code>true</code> if the container has been added, * otherwise <code>false</code> */ public boolean addAttributeContainer(AttributeProvider container) { if (this.containers.add(container)) { freeMemory(); return true; } return false; } /** Remove a container in this set. * * @param container the container. * @return <code>true</code> if the container has been removed, * otherwise <code>false</code> */ public boolean removeAttributeContainer(AttributeProvider container) { if (this.containers.remove(container)) { freeMemory(); return true; } return false; } /** Replies the number of attribute containers in this MultiAttributeContainer. * * @return the number of attribute containers in this MultiAttributeContainer. */ @Pure public int getAttributeContainerCount() { return this.containers.size(); } @Pure @Override public MultiAttributeProvider clone() { final MultiAttributeProvider clone = (MultiAttributeProvider) super.clone(); clone.cache = new SoftValueTreeMap<>(); for (final Entry<String, AttributeValue> e : this.cache.entrySet()) { clone.cache.put(e.getKey(), new AttributeValueImpl(e.getValue())); } clone.containers = new ArrayList<>(this.containers); if (this.names != null) { clone.names = new TreeSet<>(new AttributeNameStringComparator()); clone.names.addAll(this.names); } return clone; } @Override public void freeMemory() { this.cache.clear(); if (this.names != null) { this.names.clear(); } this.names = null; } @Pure @Override public Collection<Attribute> getAllAttributes() { final List<Attribute> list = new ArrayList<>(getAttributeCount()); Attribute newAttr; for (final String name : getAllAttributeNames()) { if (name != null) { newAttr = extract(name); if (newAttr != null) { list.add(newAttr); } } } return list; } @Pure @Override public Map<AttributeType, Collection<Attribute>> getAllAttributesByType() { final Map<AttributeType, Collection<Attribute>> map = new TreeMap<>(); Attribute newAttr; for (final String name : getAllAttributeNames()) { if (name != null) { newAttr = extract(name); if (newAttr != null) { Collection<Attribute> list = map.get(newAttr.getType()); if (list == null) { list = new ArrayList<>(); map.put(newAttr.getType(), list); } list.add(newAttr); } } } return map; } @Pure @Override public AttributeValue getAttribute(String name) { return extract(name); } @Pure @Override public AttributeValue getAttribute(String name, AttributeValue defaultValue) { AttributeValue value = extract(name); if (value == null) { value = defaultValue; } return value; } @Pure @Override public int getAttributeCount() { return getAllAttributeNames().size(); } @Pure @Override public Collection<String> getAllAttributeNames() { if (this.names == null) { final Set<String> names = new TreeSet<>(new AttributeNameStringComparator()); for (final AttributeProvider c : this.containers) { names.addAll(c.getAllAttributeNames()); } this.names = names; } return Collections.unmodifiableSet(this.names); } @Pure @Override public Attribute getAttributeObject(String name) { return extract(name); } @Pure @Override public boolean hasAttribute(String name) { for (final AttributeProvider c : this.containers) { if (c.hasAttribute(name)) { return true; } } return false; } /** This class provides an implementation of attribute value * that may be marked with an indicators that many * values are possibles for the attribute. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 4.0 */ static class ManyValueAttributeValue extends AttributeValueImpl { private static final long serialVersionUID = -5153126369737554181L; private boolean hasMultipleValues; private AttributeType topType; /** Replies the type type associated to this attribute value. * * @return the top type associated to this attribute value. */ @Pure public AttributeType getTopType() { return this.topType; } /** Set the top type associated to this attribute value. * * @param type is the top type associated to this attribute value. */ public void setTopType(AttributeType type) { if (this.topType == null) { this.topType = type; } else if (this.topType != type && !this.topType.isAssignableFrom(type)) { if (type.isAssignableFrom(this.topType)) { this.topType = type; } else { this.topType = AttributeType.OBJECT; } } } @Override public final void setInternalValue(Object value) { super.setInternalValue(value); } @Override public final void setInternalValue(Object value, AttributeType type) { super.setInternalValue(value, type); } /** Replies if this attribute has multiple values. * * @return <code>true</code> if this attribute has multiple * values, otherwise <code>false</code>. */ @Pure public boolean hasMultipleValues() { return this.hasMultipleValues; } /** Set if this attribute has multiple values. * * @param v is <code>true</code> if this attribute has multiple * values, otherwise <code>false</code>. */ public void setMultipleValues(boolean v) { this.hasMultipleValues = v; } } }