blob: af664d144e17f5b110a6662651190c5e518e33ac [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.sis.feature;
import java.util.Map;
import java.util.HashMap;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.apache.sis.internal.util.AbstractMap;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.collection.WeakValueHashMap;
import org.apache.sis.util.resources.Errors;
import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
// Branch-dependent imports
import org.opengis.feature.AttributeType;
/**
* Implementation of the map returned by {@link DefaultAttributeType#characteristics()}.
* Information provided by this implementation are also used by {@link CharacteristicMap}.
*
* <h2>Comparison with standard hash map</h2>
* The straightforward approach would be to store the attributes directly as values in a standard {@code HashMap}.
* But instead of that, we store attributes in an array and the array indices in a {@code HashMap}. This level of
* indirection is useless if we consider only the {@link DefaultAttributeType#characteristics()} method, since a
* standard {@code HashMap<String,AttributeType>} would work as well or better. However this level of indirection
* become useful for {@link CharacteristicMap} (the map returned by {@link AbstractAttribute#characteristics()}),
* since it allows a more efficient storage. We do this effort because some applications may create a very large
* amount of attribute instances.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.5
* @module
*/
final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>> {
/**
* For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
* having the same characteristics.
*/
@SuppressWarnings("unchecked")
private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap> SHARED =
new WeakValueHashMap<>((Class) AttributeType[].class);
/*
* This class has intentionally no reference to the AttributeType for which we are providing characteristics.
* This allows us to use the same CharacteristicTypeMap instance for various attribute types having the same
* characteristic (e.g. many measurements may have an "accuracy" characteristic).
*/
/**
* Characteristics of an other attribute type (the {@code source} attribute given to the constructor).
* This array shall not be modified.
*/
final AttributeType<?>[] characterizedBy;
/**
* The names of attribute types listed in the {@link #characterizedBy} array,
* together where their index in the array. This map shall not be modified.
*/
final Map<String,Integer> indices;
/**
* Creates a new map or return an existing map for the given attribute characteristics.
*
* <p>This method does not clone the {@code characterizedBy} array. If that array
* is a user-provided argument, then cloning that array is caller responsibility.</p>
*
* @param source the attribute which is characterized by {@code characterizedBy}.
* @param characterizedBy characteristics of {@code source}. Should not be empty.
* @return a map for this given characteristics.
* @throws IllegalArgumentException if two characteristics have the same name.
*/
static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
CharacteristicTypeMap map;
synchronized (SHARED) {
map = SHARED.get(characterizedBy);
if (map == null) {
map = new CharacteristicTypeMap(source, characterizedBy);
SHARED.put(characterizedBy, map);
}
}
return map;
}
/**
* Creates a new map for the given attribute characteristics.
*
* <p>This constructor does not clone the {@code characterizedBy} array. If that array
* is a user-provided argument, then cloning that array is caller responsibility.</p>
*
* @param source the attribute which is characterized by {@code characterizedBy}.
* @param characterizedBy characteristics of {@code source}. Should not be empty.
* @throws IllegalArgumentException if two characteristics have the same name.
*/
private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
this.characterizedBy = characterizedBy;
int index = 0;
final Map<String,Integer> indices = new HashMap<>(Containers.hashMapCapacity(characterizedBy.length));
final Map<String,Integer> aliases = new HashMap<>();
for (int i=0; i<characterizedBy.length; i++) {
final AttributeType<?> attribute = characterizedBy[i];
ensureNonNullElement("characterizedBy", i, attribute);
GenericName name = attribute.getName();
String key = AbstractIdentifiedType.toString(name, source, "characterizedBy", i);
final Integer value = index++;
if (indices.put(key, value) != null) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.DuplicatedIdentifier_1, key));
}
/*
* If some characteristics use long name of the form "head:tip", creates short aliases containing
* only the "tip" name for convenience, provided that it does not create ambiguity. If an alias
* could map to two or more characteristics, then that alias is not added. Those ambiguous aliases
* are identified by the -1 value in the 'aliases' map.
*/
while (name instanceof ScopedName) {
if (name == (name = ((ScopedName) name).tail())) break; // Safety against broken implementations.
key = name.toString();
if (key == null || (key = key.trim()).isEmpty()) break; // Safety against broken implementations.
if (aliases.put(key, value) != null) {
aliases.put(key, -1);
}
}
}
/*
* Copy the aliases only after we finished to create the list of all fully-qualified names.
* The copy operation shall exclude all ambiguous names.
*/
for (final Map.Entry<String,Integer> entry : aliases.entrySet()) {
final Integer value = entry.getValue();
if (value >= 0) {
indices.putIfAbsent(entry.getKey(), value);
}
}
this.indices = CollectionsExt.compact(indices);
}
/**
* Returns {@code true} if there is no attribute characteristics.
*/
@Override
public boolean isEmpty() {
return characterizedBy.length == 0;
}
/**
* Returns the number of attribute characteristics.
*/
@Override
public int size() {
return characterizedBy.length;
}
/**
* Returns {@code true} if this map contains an attribute characteristic of the given name.
*/
@Override
public boolean containsKey(final Object key) {
return indices.containsKey(key);
}
/**
* Returns {@code true} if this map contains the given attribute characteristic.
*/
@Override
public boolean containsValue(final Object key) {
for (final AttributeType<?> type : characterizedBy) {
if (type.equals(key)) {
return true;
}
}
return false;
}
/**
* Returns the attribute characteristic for the given name, or {@code null} if none.
*/
@Override
public AttributeType<?> get(final Object key) {
final Integer index = indices.get(key);
return (index != null) ? characterizedBy[index] : null;
}
/**
* Returns an iterator over the entries.
* This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
*/
@Override
protected EntryIterator<String, AttributeType<?>> entryIterator() {
return new EntryIterator<String, AttributeType<?>>() {
/** Index of the next element to return in the iteration. */
private int index;
/** Value of current entry. */
private AttributeType<?> value;
/**
* Returns {@code true} if there is more entries in the iteration.
*/
@Override
protected boolean next() {
if (index < characterizedBy.length) {
value = characterizedBy[index++];
return true;
}
return false;
}
/**
* Returns the attribute characteristic name.
*/
@Override
protected String getKey() {
return value.getName().toString();
}
/**
* Returns the attribute characteristic contained in this entry.
*/
@Override
protected AttributeType<?> getValue() {
return value;
}
};
}
}