| /* |
| * 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.Iterator; |
| import java.util.Collection; |
| import org.opengis.util.GenericName; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.apache.sis.internal.jaxb.Context; |
| import org.apache.sis.internal.metadata.NameMeaning; |
| import org.apache.sis.internal.referencing.NilReferencingObject; |
| |
| import static org.apache.sis.internal.util.Strings.appendUnicodeIdentifier; |
| |
| |
| /** |
| * An iterator over the {@linkplain IdentifiedObject#getName() name} of an identified object followed by |
| * {@linkplain IdentifiedObject#getAlias() aliases} which are instance of {@link ReferenceIdentifier}. |
| * This iterator is used for {@link AbstractIdentifiedObject} XML marshalling because GML merges the name |
| * and aliases in a single {@code <gml:name>} property. However this iterator is useful only if the aliases |
| * are instances of {@link NamedIdentifier}, or any other implementation which is both a name and an identifier. |
| * |
| * <p>This class also opportunistically provide helper methods for {@link AbstractIdentifiedObject} marshalling.</p> |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.7 |
| * @since 0.4 |
| * @module |
| */ |
| final class NameIterator implements Iterator<ReferenceIdentifier> { |
| /** |
| * The next element to return, or {@code null} if we reached the end of iteration. |
| */ |
| private ReferenceIdentifier next; |
| |
| /** |
| * An iterator over the aliases. |
| */ |
| private final Iterator<GenericName> alias; |
| |
| /** |
| * Creates a new iterator over the name and aliases of the given object. |
| */ |
| NameIterator(final IdentifiedObject object) { |
| alias = object.getAlias().iterator(); |
| next = object.getName(); |
| // Should never be null in a well-formed IdentifiedObject, but let be safe. |
| if (isUnnamed(next)) { |
| next(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if the given identifier is null or the {@link NilReferencingObject#UNNAMED} instance. |
| */ |
| static boolean isUnnamed(final ReferenceIdentifier name) { |
| return (name == null) || (name == NilReferencingObject.UNNAMED); |
| } |
| |
| /** |
| * Returns {@code true} if there is an other name or alias to return. |
| */ |
| @Override |
| public boolean hasNext() { |
| return next != null; |
| } |
| |
| /** |
| * Returns the next name or alias in the iteration. |
| * |
| * Note: we do not bother checking for {@code NoSuchElementException} because this iterator |
| * will be used only by JAXB, which is presumed checking for {@link #hasNext()} correctly. |
| */ |
| @Override |
| public ReferenceIdentifier next() { |
| final ReferenceIdentifier n = next; |
| while (alias.hasNext()) { |
| final GenericName c = alias.next(); |
| if (c instanceof ReferenceIdentifier) { |
| next = (ReferenceIdentifier) c; |
| return n; |
| } |
| } |
| next = null; |
| return n; |
| } |
| |
| /* |
| * remove() is an unsupported operation since this iterator is read-only. |
| * So we inherit the default implementation from Iterator. |
| */ |
| |
| /** |
| * Returns the number of name and aliases in the given object. |
| */ |
| public static int count(final IdentifiedObject object) { |
| int c = 0; |
| final NameIterator it = new NameIterator(object); |
| while (it.hasNext()) { |
| it.next(); |
| c++; |
| } |
| return c; |
| } |
| |
| /** |
| * Implementation of {@link AbstractIdentifiedObject#getID()}, provided here for reducing the amount of code |
| * to load in the common case where XML support is not needed. |
| * |
| * <p>The current implementation searches for the first identifier, regardless its authority. |
| * If no identifier is found, then the name and aliases are used. |
| * Then, this method returns the concatenation of the following elements separated by hyphens:</p> |
| * <ul> |
| * <li>The code space in lower case, retaining only characters that are valid for Unicode identifiers.</li> |
| * <li>The object type as defined in OGC's URN (see {@link org.apache.sis.internal.util.DefinitionURI})</li> |
| * <li>The object code, retaining only characters that are valid for Unicode identifiers.</li> |
| * </ul> |
| * |
| * Example: {@code "epsg-crs-4326"}. |
| * |
| * <p>The returned ID needs to be unique only in the XML document being marshalled. |
| * Consecutive invocations of this method do not need to return the same value, |
| * since it may depends on the marshalling context.</p> |
| * |
| * @param context the (un)marshalling context. |
| * @param object the object for which to get a {@code gml:id}. |
| * @param name the identified object name, or {@code null} if none. |
| * @param alias the identified object aliases, or {@code null} if none. |
| * @param identifiers the identifiers, or {@code null} if none. |
| * @return proposed value for {@code gml:id} attribute, or {@code null} if none. |
| */ |
| static String getID(final Context context, final IdentifiedObject object, final ReferenceIdentifier name, |
| final Collection<? extends GenericName> alias, final Collection<? extends ReferenceIdentifier> identifiers) |
| { |
| String candidate = Context.getObjectID(context, object); |
| if (candidate == null) { |
| final StringBuilder id = new StringBuilder(); |
| /* |
| * We will iterate over the identifiers first. Only after the iteration is over, |
| * if we found no suitable ID, then we will use the primary name as a last resort. |
| */ |
| if (identifiers != null) { |
| for (final ReferenceIdentifier identifier : identifiers) { |
| if (appendUnicodeIdentifier(id, '-', identifier.getCodeSpace(), "", true) | // Really |, not || |
| appendUnicodeIdentifier(id, '-', NameMeaning.toObjectType(object.getClass()), "", false) | |
| appendUnicodeIdentifier(id, '-', identifier.getCode(), "", true)) |
| { |
| /* |
| * Check for ID uniqueness. If the ID is rejected, then we just need to clear |
| * the buffer and let the iteration continue the search for another ID. |
| */ |
| candidate = id.toString(); |
| if (Context.setObjectForID(context, object, candidate)) { |
| return candidate; |
| } |
| } |
| id.setLength(0); // Clear the buffer for another try. |
| } |
| } |
| /* |
| * In last ressort, use the name or an alias. The name will be used without codespace since |
| * names are often verbose. If that name is also used, append a number until we find a free ID. |
| */ |
| if (isUnnamed(name) || !appendUnicodeIdentifier(id, '-', name.getCode(), "", false)) { |
| if (alias != null) { |
| for (final GenericName a : alias) { |
| if (appendUnicodeIdentifier(id, '-', a.toString(), "", false)) { |
| break; |
| } |
| } |
| } |
| } |
| if (id.length() != 0) { |
| candidate = id.toString(); |
| if (!Context.setObjectForID(context, object, candidate)) { |
| final int s = id.append('-').length(); |
| int n = 0; |
| do { |
| if (++n == 100) return null; // Arbitrary limit. |
| candidate = id.append(n).toString(); |
| id.setLength(s); |
| } while (!Context.setObjectForID(context, object, candidate)); |
| } |
| } |
| } |
| return candidate; |
| } |
| } |