blob: df5a9a516ec832408218f73a27133647879ad41a [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.metadata;
import java.util.Map;
import java.util.IdentityHashMap;
import org.opengis.annotation.UML;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.internal.system.Modules;
/**
* Information about an Apache SIS metadata standard implementation.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
* @version 0.3
* @module
*/
final class StandardImplementation extends MetadataStandard {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 855786625369724248L;
/**
* The root packages for metadata implementations, or {@code null} if none.
* If non-null, then this string must ends with a trailing {@code "."}.
*/
private final String implementationPackage;
/**
* The prefixes that implementation classes may have.
* The most common prefixes should be first, since the prefixes will be tried in that order.
*/
private final String[] prefix;
/**
* The acronyms that implementation classes may have, or {@code null} if none. If non-null,
* then this array shall contain (<var>full text</var>, <var>acronym</var>) pairs. The full
* text shall appear to the end of the class name, otherwise it is not replaced. This is
* necessary in order to avoid the replacement of {@code "DefaultCoordinateSystemAxis"} by
* {@code "DefaultCSAxis"}.
*/
private final String[] acronyms;
/**
* Implementations for a given interface, computed when first needed then cached.
*
* <div class="note"><b>Implementation note:</b>
* In the particular case of {@code Class} keys, {@code IdentityHashMap} and {@code HashMap} have identical
* behavior since {@code Class} is final and does not override the {@code equals(Object)} and {@code hashCode()}
* methods. The {@code IdentityHashMap} Javadoc claims that it is faster than the regular {@code HashMap}.
* But maybe the most interesting property is that it allocates less objects since {@code IdentityHashMap}
* implementation doesn't need the chain of objects created by {@code HashMap}.</div>
*/
private final transient Map<Class<?>,Class<?>> implementations; // written by reflection on deserialization.
/**
* Creates a new instance working on implementation of interfaces defined in the
* specified package. This constructor is used only for the pre-defined constants.
*
* @param citation The title of the standard.
* @param interfacePackage The root package for metadata interfaces, with a trailing {@code '.'}.
* @param implementationPackage The root package for metadata implementations. with a trailing {@code '.'}.
* @param prefix The prefix of implementation class. This array is not cloned.
* @param acronyms An array of (full text, acronyms) pairs. This array is not cloned.
* @param dependencies The dependencies to other metadata standards, or {@code null} if none.
*/
StandardImplementation(final String citation, final String interfacePackage, final String implementationPackage,
final String[] prefix, final String[] acronyms, final MetadataStandard[] dependencies)
{
super(citation, interfacePackage, dependencies);
this.implementationPackage = implementationPackage;
this.prefix = prefix;
this.acronyms = acronyms;
this.implementations = new IdentityHashMap<Class<?>,Class<?>>();
}
/**
* Accepts Apache SIS implementation classes as "pseudo-interfaces" if they are annotated with {@link UML}.
* We use this feature for example in the transition from ISO 19115:2003 to ISO 19115:2014, when new API is
* defined in Apache SIS but not yet available in GeoAPI interfaces.
*/
@Override
boolean isPendingAPI(final Class<?> type) {
return type.getName().startsWith(implementationPackage) && type.isAnnotationPresent(UML.class);
}
/**
* Returns the implementation class for the given interface, or {@code null} if none.
* This class uses heuristic rules based on naming conventions.
*
* @param <T> The compile-time {@code type}.
* @param type The interface, typically from the {@code org.opengis.metadata} package.
* @return The implementation class, or {@code null} if none.
*/
@Override
public <T> Class<? extends T> getImplementation(final Class<T> type) {
/*
* We require the type to be an interface in order to exclude
* CodeLists, Enums and Exceptions.
*/
if (type != null && type.isInterface()) {
String classname = type.getName();
if (isSupported(classname)) {
synchronized (implementations) {
Class<?> candidate = implementations.get(type);
if (candidate != null) {
return (candidate != Void.TYPE) ? candidate.asSubclass(type) : null;
}
/*
* Prepares a buffer with a copy of the class name in which the interface
* package has been replaced by the implementation package, and some text
* have been replaced by their acronym (if any).
*/
final StringBuilder buffer = new StringBuilder(implementationPackage)
.append(classname, interfacePackage.length(), classname.length());
if (acronyms != null) {
for (int i=0; i<acronyms.length; i+=2) {
final String acronym = acronyms[i];
if (CharSequences.endsWith(buffer, acronym, false)) {
buffer.setLength(buffer.length() - acronym.length());
buffer.append(acronyms[i+1]);
break;
}
}
}
/*
* Try to insert a prefix in front of the class name, until a match is found.
*/
final int prefixPosition = buffer.lastIndexOf(".") + 1;
int length = 0;
for (final String p : prefix) {
classname = buffer.replace(prefixPosition, prefixPosition + length, p).toString();
try {
candidate = Class.forName(classname);
} catch (ClassNotFoundException e) {
Logging.recoverableException(Logging.getLogger(Modules.METADATA),
MetadataStandard.class, "getImplementation", e);
length = p.length();
continue;
}
if (candidate.isAnnotationPresent(Deprecated.class)) {
// Skip deprecated implementations.
candidate = candidate.getSuperclass();
if (!type.isAssignableFrom(candidate) || candidate.isAnnotationPresent(Deprecated.class)) {
length = p.length();
continue;
}
}
implementations.put(type, candidate);
return candidate.asSubclass(type);
}
implementations.put(type, Void.TYPE); // Marker for "class not found".
}
}
}
return null;
}
/**
* Invoked on deserialization. Returns one of the pre-existing constants if possible.
*/
Object readResolve() {
if (ISO_19111.citation.equals(citation)) return ISO_19111;
if (ISO_19115.citation.equals(citation)) return ISO_19115;
/*
* Following should not occurs, unless we are deserializing an instance created by a
* newer version of the Apache SIS library. The newer version could contains constants
* not yet declared in this older SIS version, so we have to use this instance.
*/
setMapForField(StandardImplementation.class, "implementations");
return this;
}
}