blob: 048e8404165bc879d18f736d869e369d1337461a [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.internal.converter;
import java.util.Arrays;
import java.util.Set;
import java.util.EnumSet;
import java.util.Iterator;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.Classes;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.ArgumentChecks;
/**
* Fallback to be used when the first converter failed.
* In case of failure, the error of the first (primary) converter is reported.
*
* <p>The primary converter is expected more generic than the fallback converter. We try the generic
* converter first because we expect that if the user wanted the specific subclass, he would have
* asked explicitly for it. Trying the generic converter first is both closer to what the user
* asked and less likely to throw many exceptions before we found a successful conversion.</p>
*
* <p>All converters in a {@code FallbackConverter} tree have the same source class {@code <S>},
* and different target classes {@code <? extends T>} <strong>not</strong> equal to {@code <T>}.
* The tree should never have two classes {@code <T1>} and {@code <T2>} such as one is assignable
* from the other.</p>
*
* <p>Instances are created by the {@link #merge(ObjectConverter, ObjectConverter)} method.
* It is invoked when a new converter is {@linkplain ConverterRegistry#register(ObjectConverter)
* registered} for the same source and target class than an existing converter.</p>
*
* <h2>Immutability and thread safety</h2>
* This class is immutable, and thus inherently thread-safe,
* if the converters given to the static factory method are also immutable.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
*
* @param <S> the base type of source objects.
* @param <T> the base type of converted objects.
*
* @since 0.3
* @module
*/
final class FallbackConverter<S,T> extends SystemConverter<S,T> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 6331789192804695560L;
/**
* The primary converter, to be tried first.
*/
final ObjectConverter<S, ? extends T> primary;
/**
* The fallback converter. Its target type should not be assignable from the primary target
* type, except if both converters have the same target type. We intend {@linkplain #primary}
* to be the most generic converter, because we assume that if the user wanted a more specific
* type he would have asked explicitly for it. In addition this layout reduces the amount of
* exceptions to be thrown and caught before we found a successful conversion.
*/
final ObjectConverter<S, ? extends T> fallback;
/**
* Creates a converter using the given primary and fallback converters. This method may
* interchange the two converters in order to met the {@linkplain #fallback} contract.
*
* @param sourceClass the {@linkplain #getSourceClass() source class}.
* @param targetClass the {@linkplain #getTargetClass() target class}.
* @param primary a first converter.
* @param fallback a second converter.
*
* @see #merge(ObjectConverter, ObjectConverter)
*/
private FallbackConverter(final Class<S> sourceClass, final Class<T> targetClass,
final ObjectConverter<S, ? extends T> primary,
final ObjectConverter<S, ? extends T> fallback)
{
super(sourceClass, targetClass);
if (needSwap(primary, fallback.getClass())) {
this.primary = fallback;
this.fallback = primary;
} else {
this.primary = primary;
this.fallback = fallback;
}
}
/**
* Returns {@code true} if the given primary and fallback converters should be interchanged.
* This method may invoke itself recursively.
*
* @param primary the primary converter to test.
* @param fallbackClass the target class of the fallback converter to test.
* @return {@code true} if the given primary and fallback converters should be interchanged.
*/
private static <S> boolean needSwap(final ObjectConverter<S,?> primary, final Class<?> fallbackClass) {
if (primary instanceof FallbackConverter<?,?>) {
final FallbackConverter<S,?> candidate = (FallbackConverter<S,?>) primary;
return needSwap(candidate.primary, fallbackClass) &&
needSwap(candidate.fallback, fallbackClass);
} else {
final Class<?> targetClass = primary.getTargetClass();
return fallbackClass.isAssignableFrom(targetClass) && // This condition is more likely to fail first.
!targetClass.isAssignableFrom(fallbackClass);
}
}
/**
* Appends the given {@code converter} in the given tree of fallback converters.
* This method may create a new {@code FallbackConverter} if the given converter
* can not be inserted in the given tree.
*
* <p>This method has no information about {@code <T>} type because of parameterized types
* erasure, and should not need that information if we didn't made a mistake in this class.
* Nevertheless for safety, callers are encouraged to verify themselves as below:</p>
*
* {@preformat java
* Class<T> targetClass = ...;
* FallbackConverter<S, ? extends T> converter = merge(...);
* assert targetClass.isAssignableFrom(converter.getTargetClass()) : converter;
* }
*
* In the current implementation, the {@code primary} converter can be either an arbitrary
* {@code ObjectConverter}, or a previously created {@code FallbackConverter}. However the
* {@code fallback} converter shall <strong>not</strong> be a {@code FallbackConverter}.
* This restriction exists because the tree built in such case would probably not be the
* desired one. It should be okay if only SIS code deal with {@code FallbackConverter}.
*
* @param <S> the base type of source objects.
* @param <T> the base type of converted objects.
* @param primary the first converter, which may be a {@code Fallback} tree.
* @param fallback a new fallback to insert in the converters tree.
* @return a tree of converters which contains the given {@code converter}. May be either
* {@code existing}, {@code converter} or a new {@code FallbackConverter} instance.
*/
public static <S,T> ObjectConverter<S, ? extends T> merge(
final ObjectConverter<S, ? extends T> primary,
final ObjectConverter<S, ? extends T> fallback)
{
ArgumentChecks.ensureNonNull("primary", primary);
ArgumentChecks.ensureNonNull("fallback", fallback);
assert !(fallback instanceof FallbackConverter<?,?>) : fallback; // See javadoc
final ObjectConverter<S, ? extends T> candidate = mergeIfSubtype(primary, fallback, null);
if (candidate != null) {
return candidate;
}
final Class<S> source = primary .getSourceClass();
final Class<? extends T> target1 = primary .getTargetClass();
final Class<? extends T> target2 = fallback.getTargetClass();
Class<?> target = Classes.findCommonClass(target1, target2);
if (target == Object.class) {
/*
* If there is no common parent class other than Object, looks for a common interface.
* We perform this special processing for Object.class because this class is handled
* in a special way by the Java language anyway: all interfaces are specialization of
* Object (in the sense "are assignable to"), so Object can be considered as a common
* root for both classes and interfaces.
*/
final Set<Class<?>> interfaces = Classes.findCommonInterfaces(target1, target2);
interfaces.removeAll(Arrays.asList(Classes.getAllInterfaces(source)));
final Iterator<Class<?>> it = interfaces.iterator();
if (it.hasNext()) {
/*
* Arbitrarily retains the first interface. At this point there is hopefully
* only one occurrence anyway. If there is more than one interface, they appear
* in declaration order so the first one is assumed the "main" interface.
*/
target = it.next();
}
}
/*
* We perform an unchecked cast because in theory <T> is the common super class.
* However we can not check at run time because generic types are implemented by
* erasure. If there is no logical error in our algorithm, the cast should be ok.
* Nevertheless callers are encouraged to verify as documented in the Javadoc.
*/
assert target.isAssignableFrom(target1) : target1;
assert target.isAssignableFrom(target2) : target2;
@SuppressWarnings({"unchecked","rawtypes"})
final FallbackConverter<S, ? extends T> converter =
new FallbackConverter(source, target, primary, fallback);
return converter;
}
/**
* Merges if the {@code converter} target class of is a subtype of the {@code branch}
* target class. Otherwise returns {@code null}.
*
* <p>The {@code branch} can be either an arbitrary {@code ObjectConverter}, or a previously
* created {@code FallbackConverter}. However the {@code converter} shall be a new instance,
* <strong>not</strong> a {@code FallbackConverter} instance.
* See {@link #merge(ObjectConverter, ObjectConverter)} javadoc for more information.</p>
*
* @param <S> the source class of the {@code branch} converter.
* @param <T> the target class of the {@code branch} converter
* @param branch the converter to eventually merge with {@code converter}.
* @param converter the converter to eventually merge with {@code branch}.
* @param parentTarget to be given verbatim to {@link #merge(ObjectConverter, Class)}.
* @return the merged converter, or {@code null} if the {@code converter}
* target class is not a subtype of the {@code branch} target class.
*/
private static <S,T> ObjectConverter<S, ? extends T> mergeIfSubtype(
final ObjectConverter<S,T> branch,
final ObjectConverter<S,?> converter,
final Class<? super T> parentTarget)
{
if (branch.equals(converter)) {
return branch;
}
final Class<T> targetClass = branch.getTargetClass();
if (!targetClass.isAssignableFrom(converter.getTargetClass())) {
return null;
}
/*
* At this point we know that 'converter.targetClass' is <T> or a subtype of <T>,
* so the cast below is safe. If the branch is an instance of FallbackConverter,
* continue to follow that branch.
*/
@SuppressWarnings("unchecked")
final ObjectConverter<S, ? extends T> checked = (ObjectConverter<S, ? extends T>) converter;
if (branch instanceof FallbackConverter<?,?>) {
/*
* Will follow either 'branch.fallback' or 'branch.primary', depending which one
* is the most appropriate. If none can be followed, then the result will be the
* same than in the 'else' block.
*/
return ((FallbackConverter<S,T>) branch).merge(checked, parentTarget);
} else {
/*
* Both 'branch' and 'checked' are ordinary converters (not FallbackConverter).
*/
return new FallbackConverter<>(branch.getSourceClass(), targetClass, branch, checked);
}
}
/**
* Merge {@code this} with an other converter whose target class is a subtype of
* this {@link #targetClass}. If either {@link #fallback} or {@link #primary} are
* other {@code FallbackConverter} instances, then this method will follow those
* branches.
*
* @param converter the converter to merge with {@code this}.
* @param parentTarget if this method is invoked recursively, the target class
* of the parent {@code FallbackConverter}. Otherwise {@code null}.
* @return the merged converter.
*/
private ObjectConverter<S, ? extends T> merge(final ObjectConverter<S, ? extends T> converter,
final Class<? super T> parentTarget)
{
ObjectConverter<S, ? extends T> candidate;
final ObjectConverter<S, ? extends T> newPrimary, newFallback;
candidate = mergeIfSubtype(fallback, converter, targetClass);
if (candidate != null) {
newPrimary = primary;
newFallback = candidate;
} else {
candidate = mergeIfSubtype(primary, converter, targetClass);
if (candidate != null) {
newPrimary = candidate;
newFallback = fallback;
} else if (targetClass != parentTarget) {
newPrimary = this;
newFallback = converter;
} else {
/*
* If the we can not follow any of the 'primary' and 'fallback' branch,
* and if the target class of this FallbackConverter is the same than
* the target class of the parent, then do not create. We will let the
* parent FallbackConverter do the creation itself in order to chain the
* converters in the order they have been declared.
*/
return null;
}
}
return new FallbackConverter<>(sourceClass, targetClass, newPrimary, newFallback);
}
/**
* Returns the manner in which source values (<var>S</var>) are mapped to target values.
* This is the intersection of the properties of the primary and fallback converters.
*/
@Override
public final Set<FunctionProperty> properties() {
Set<FunctionProperty> properties = primary.properties();
if (!(primary instanceof FallbackConverter<?,?>)) {
properties = EnumSet.copyOf(properties);
properties.remove(FunctionProperty.INVERTIBLE);
}
properties.retainAll(fallback.properties());
return properties;
}
/**
* Converts the given object, using the fallback if needed.
*/
@Override
public T apply(final S source) throws UnconvertibleObjectException {
try {
return primary.apply(source);
} catch (UnconvertibleObjectException exception) {
try {
return fallback.apply(source);
} catch (UnconvertibleObjectException failure) {
exception.addSuppressed(failure);
throw exception;
}
}
}
/**
* Creates a node for the given converter and adds it to the given tree.
* This method invokes itself recursively for scanning through fallbacks.
*
* @param converter the converter for which to create a tree.
* @param addTo the node in which to add the converter.
*/
private void toTree(final ObjectConverter<?,?> converter, TreeTable.Node addTo) {
if (converter instanceof FallbackConverter<?,?>) {
final boolean isNew = converter.getTargetClass() != targetClass;
if (isNew) {
addTo = addTo.newChild();
}
((FallbackConverter<?,?>) converter).toTree(addTo, isNew);
} else {
Column.toTree(converter, addTo);
}
}
/**
* Adds a simplified tree representation of this {@code FallbackConverter} to the given node.
*
* @param addTo the node in which to add the converter.
* @param isNew {@code true} if {@code addTo} is a newly created node.
*/
final void toTree(final TreeTable.Node addTo, final boolean isNew) {
if (isNew) {
addTo.setValue(Column.SOURCE, sourceClass);
addTo.setValue(Column.TARGET, targetClass);
}
toTree(primary, addTo);
toTree(fallback, addTo);
}
/**
* Returns a tree representation of this converter.
* The tree leaves represent the backing converters.
*/
@Override
public String toString() {
final TreeTable table = Column.createTable();
toTree(table.getRoot(), true);
return Column.format(table);
}
}