| /* |
| * 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.referencing; |
| |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.Collection; |
| import java.io.Serializable; |
| import org.opengis.util.GenericName; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.referencing.datum.Datum; |
| import org.opengis.referencing.ReferenceSystem; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.operation.CoordinateOperation; |
| import org.opengis.referencing.operation.OperationMethod; |
| import org.opengis.referencing.operation.SingleOperation; |
| import org.opengis.metadata.quality.PositionalAccuracy; |
| import org.apache.sis.internal.util.AbstractMap; |
| import org.apache.sis.internal.referencing.CoordinateOperations; |
| import org.apache.sis.util.Deprecable; |
| |
| |
| /** |
| * An immutable map fetching all properties from the specified identified object. |
| * Calls to {@code get} methods are forwarded to the appropriate {@link IdentifiedObject} method. |
| * This map does not contain null value. Collections are converted to arrays. |
| * |
| * <p>This map is read-only. Whether it is serializable, immutable or thread-safe depends if the |
| * underlying {@code IdentifiedObject} instance is itself serializable, immutable or thread-safe.</p> |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.1 |
| * @since 0.4 |
| * @module |
| */ |
| final class Properties extends AbstractMap<String,Object> implements Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 836068852472172370L; |
| |
| /** |
| * The keys to search for. The index of each element in this array must matches the index searched by |
| * {@link #getAt(int)}. In other words, this array performs the reverse mapping of {@link #INDICES}. |
| */ |
| private static final String[] KEYS = { |
| /*[ 0]*/ IdentifiedObject .NAME_KEY, |
| /*[ 1]*/ IdentifiedObject .IDENTIFIERS_KEY, |
| /*[ 2]*/ IdentifiedObject .ALIAS_KEY, |
| /*[ 3]*/ IdentifiedObject .REMARKS_KEY, |
| /*[ 4]*/ CoordinateOperation .SCOPE_KEY, // same in Datum and ReferenceSystem |
| /*[ 5]*/ CoordinateOperation .DOMAIN_OF_VALIDITY_KEY, // same in Datum and ReferenceSystem |
| /*[ 6]*/ CoordinateOperation .OPERATION_VERSION_KEY, |
| /*[ 7]*/ CoordinateOperation .COORDINATE_OPERATION_ACCURACY_KEY, |
| /*[ 8]*/ OperationMethod .FORMULA_KEY, |
| /*[ 9]*/ CoordinateOperations .PARAMETERS_KEY, |
| /*[10]*/ AbstractIdentifiedObject.DEPRECATED_KEY |
| |
| /* |
| * The current implementation does not look for minimum and maximum values in ParameterDescriptor |
| * and CoordinateSystemAxis, because their interpretation depends on the unit of measurement. |
| * Including those properties in this map causes more harm than good. |
| */ |
| }; |
| |
| /** |
| * The mapping from key names to the index expected by the {@link #getAt(int)} method. |
| * This map shall not be modified after construction (for multi-thread safety without synchronization). |
| */ |
| private static final Map<String,Integer> INDICES = new HashMap<>(16); |
| static { |
| for (int i=0; i<KEYS.length; i++) { |
| if (INDICES.put(KEYS[i], i) != null) { |
| throw new AssertionError(i); |
| } |
| } |
| } |
| |
| /** |
| * The object where all properties come from. |
| */ |
| final IdentifiedObject object; |
| |
| /** |
| * The bitmask of properties to exclude. |
| */ |
| final int excludeMask; |
| |
| /** |
| * Creates new properties from the specified identified object. |
| */ |
| Properties(final IdentifiedObject object, final String[] excludes) { |
| this.object = object; |
| int excludeMask = 0; |
| for (final String exclude : excludes) { |
| final Integer i = INDICES.get(exclude); |
| if (i != null) { |
| excludeMask |= (1 << i); |
| } |
| } |
| this.excludeMask = excludeMask; |
| } |
| |
| /** |
| * Returns the value to which this map maps the specified index. |
| * Returns null if the map contains no mapping for the given index. |
| * |
| * @param key the property index, as one of the values in the {@link #INDICES} map. |
| */ |
| final Object getAt(final int key) { |
| if ((excludeMask & (1 << key)) == 0) { |
| switch (key) { |
| case 0: { // NAME_KEY |
| return object.getName(); |
| } |
| case 1: { // IDENTIFIERS_KEY |
| final Collection<Identifier> c = object.getIdentifiers(); |
| if (c != null) { |
| final int size = c.size(); |
| if (size != 0) { |
| return c.toArray(new Identifier[size]); |
| } |
| } |
| break; |
| } |
| case 2: { // ALIAS_KEY |
| final Collection<GenericName> c = object.getAlias(); |
| if (c != null) { |
| final int size = c.size(); |
| if (size != 0) { |
| return c.toArray(new GenericName[size]); |
| } |
| } |
| break; |
| } |
| case 3: { // REMARKS_KEY |
| return object.getRemarks(); |
| } |
| case 4: { // SCOPE_KEY |
| if (object instanceof ReferenceSystem) { |
| return ((ReferenceSystem) object).getScope(); |
| } else if (object instanceof Datum) { |
| return ((Datum) object).getScope(); |
| } else if (object instanceof CoordinateOperation) { |
| return ((CoordinateOperation) object).getScope(); |
| } |
| break; |
| } |
| case 5: { // DOMAIN_OF_VALIDITY_KEY |
| if (object instanceof ReferenceSystem) { |
| return ((ReferenceSystem) object).getDomainOfValidity(); |
| } else if (object instanceof Datum) { |
| return ((Datum) object).getDomainOfValidity(); |
| } else if (object instanceof CoordinateOperation) { |
| return ((CoordinateOperation) object).getDomainOfValidity(); |
| } |
| break; |
| } |
| case 6: { // OPERATION_VERSION_KEY |
| if (object instanceof CoordinateOperation) { |
| return ((CoordinateOperation) object).getOperationVersion(); |
| } |
| break; |
| } |
| case 7: { // COORDINATE_OPERATION_ACCURACY_KEY |
| if (object instanceof CoordinateOperation) { |
| final Collection<PositionalAccuracy> c = ((CoordinateOperation) object).getCoordinateOperationAccuracy(); |
| if (c != null) { |
| final int size = c.size(); |
| if (size != 0) { |
| return c.toArray(new PositionalAccuracy[size]); |
| } |
| } |
| } |
| break; |
| } |
| case 8: { // FORMULA_KEY |
| if (object instanceof OperationMethod) { |
| return ((OperationMethod) object).getFormula(); |
| } |
| break; |
| } |
| case 9: { // PARAMETERS_KEY |
| if (object instanceof SingleOperation) { |
| return ((SingleOperation) object).getParameterValues(); |
| } |
| break; |
| } |
| case 10: { // DEPRECATED_KEY |
| if (object instanceof Deprecable) { |
| return ((Deprecable) object).isDeprecated(); |
| } |
| break; |
| } |
| default: throw new AssertionError(key); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns {@code false} if this map contains at least one element, or {@code true} otherwise. |
| */ |
| @Override |
| public boolean isEmpty() { |
| for (int i=0; i<KEYS.length; i++) { |
| if (getAt(i) != null) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the number of non-null properties in this map. |
| */ |
| @Override |
| public int size() { |
| int n = 0; |
| for (int i=0; i<KEYS.length; i++) { |
| if (getAt(i) != null) { |
| n++; |
| } |
| } |
| return n; |
| } |
| |
| /** |
| * Returns the value to which this map maps the specified key. |
| * Returns {@code null} if the map contains no mapping for this key. |
| */ |
| @Override |
| public Object get(final Object key) { |
| final Integer i = INDICES.get(key); |
| return (i != null) ? getAt(i) : null; |
| } |
| |
| /** |
| * Iterates over the {@link #KEYS}, returning only the entry having a non-null value. |
| */ |
| @Override |
| protected EntryIterator<String,Object> entryIterator() { |
| return new EntryIterator<String,Object>() { |
| /** |
| * Index of the next element to inspect. |
| */ |
| private int nextIndex; |
| |
| /** |
| * Index of the value to be returned by {@link #next()}, or {@code null} if not yet computed. |
| */ |
| private Object value; |
| |
| /** |
| * Returns {@code true} if there is a value to return. |
| */ |
| @Override |
| protected boolean next() { |
| while (nextIndex < KEYS.length) { |
| value = getAt(nextIndex++); |
| if (value != null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the key at the current position. |
| */ |
| @Override |
| protected String getKey() { |
| return KEYS[nextIndex - 1]; |
| } |
| |
| /** |
| * Returns the value at the current position. |
| */ |
| @Override |
| protected Object getValue() { |
| return value; |
| } |
| }; |
| } |
| } |