| /* |
| * 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.util.function.IntFunction; |
| import java.io.Serializable; |
| import org.opengis.util.GenericName; |
| 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.util.Deprecable; |
| import org.apache.sis.util.privy.AbstractMap; |
| import org.apache.sis.referencing.privy.CoordinateOperations; |
| |
| // Specific to the main and geoapi-3.1 branches: |
| import org.opengis.referencing.ReferenceSystem; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.opengis.referencing.datum.Datum; |
| |
| // Specific to the geoapi-3.1 and geoapi-4.0 branches: |
| import org.opengis.referencing.ObjectDomain; |
| |
| |
| /** |
| * 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) |
| */ |
| 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 .DOMAINS_KEY, |
| /*[ 4]*/ IdentifiedObject .REMARKS_KEY, |
| /*[ 5]*/ ObjectDomain .SCOPE_KEY, |
| /*[ 6]*/ ObjectDomain .DOMAIN_OF_VALIDITY_KEY, |
| /*[ 7]*/ CoordinateOperation .OPERATION_VERSION_KEY, |
| /*[ 8]*/ CoordinateOperation .COORDINATE_OPERATION_ACCURACY_KEY, |
| /*[ 9]*/ OperationMethod .FORMULA_KEY, |
| /*[10]*/ CoordinateOperations .PARAMETERS_KEY, |
| /*[11]*/ 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. |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| 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) { |
| @SuppressWarnings("LocalVariableHidesMemberVariable") |
| int excludeMask = 0; |
| for (final String exclude : excludes) { |
| final Integer i = INDICES.get(exclude); |
| if (i != null) { |
| excludeMask |= (1 << i); |
| } |
| } |
| this.excludeMask = excludeMask; |
| this.object = object; |
| } |
| |
| /** |
| * 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: return object.getName(); // NAME_KEY |
| case 1: return toArray(object.getIdentifiers(), ReferenceIdentifier[]::new); // IDENTIFIERS_KEY |
| case 2: return toArray(object.getAlias(), GenericName[]::new); // ALIAS_KEY |
| case 3: return toArray(object.getDomains(), ObjectDomain[]::new); // DOMAINS_KEY |
| case 4: return object.getRemarks(); // REMARKS_KEY |
| case 5: { // 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 6: { // 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 7: { // OPERATION_VERSION_KEY |
| if (object instanceof CoordinateOperation) { |
| return ((CoordinateOperation) object).getOperationVersion(); |
| } |
| break; |
| } |
| case 8: { // COORDINATE_OPERATION_ACCURACY_KEY |
| if (object instanceof CoordinateOperation) { |
| return toArray(((CoordinateOperation) object).getCoordinateOperationAccuracy(), PositionalAccuracy[]::new); |
| } |
| break; |
| } |
| case 9: { // FORMULA_KEY |
| if (object instanceof OperationMethod) { |
| return ((OperationMethod) object).getFormula(); |
| } |
| break; |
| } |
| case 10: { // PARAMETERS_KEY |
| if (object instanceof SingleOperation) { |
| return ((SingleOperation) object).getParameterValues(); |
| } |
| break; |
| } |
| case 11: { // DEPRECATED_KEY |
| if (object instanceof Deprecable) { |
| return ((Deprecable) object).isDeprecated(); |
| } |
| break; |
| } |
| default: throw new AssertionError(key); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the collection content as an array if the collection is non-null and non-empty. |
| * Otherwise returns {@code null}. |
| * |
| * @param <E> type of elements in the collection. |
| * @param c the collection, or {@code null}. |
| * @param generator function to invoke for creating an initially empty array. |
| * @return array of collection elements, or {@code null}. |
| */ |
| private static <E> E[] toArray(final Collection<E> c, final IntFunction<E[]> generator) { |
| return (c == null || c.isEmpty()) ? null : c.toArray(generator); |
| } |
| |
| /** |
| * 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; |
| } |
| }; |
| } |
| } |