blob: e368ddc591f4de64453912f44ae08f3a16008e72 [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.Set;
import java.util.EnumSet;
import java.io.ObjectStreamException;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.resources.Errors;
/**
* Base class of all converters defined in the {@code org.apache.sis.internal} package.
* Those converters are returned by system-wide {@link ConverterRegistry}, and cached for reuse.
*
* <div class="section">Immutability and thread safety</div>
* This base class is immutable, and thus inherently thread-safe. Subclasses should be immutable
* and thread-safe too if they are intended to be cached in {@link ConverterRegistry}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
*
* @param <S> the base type of source objects.
* @param <T> the base type of converted objects.
*
* @since 0.3
* @module
*/
abstract class SystemConverter<S,T> extends ClassPair<S,T> implements ObjectConverter<S,T> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -5003169442214901702L;
/**
* Creates a new converter for the given source and target classes.
*
* @param sourceClass the {@linkplain #getSourceClass() source class}.
* @param targetClass the {@linkplain #getTargetClass() target class}.
*/
SystemConverter(final Class<S> sourceClass, final Class<T> targetClass) {
super(sourceClass, targetClass);
}
/**
* Returns the source class given at construction time.
*
* @return the type of objects to convert.
*/
@Override
public final Class<S> getSourceClass() {
return sourceClass;
}
/**
* Returns the target class given at construction time.
*
* @return the type of converted objects.
*/
@Override
public final Class<T> getTargetClass() {
return targetClass;
}
/**
* Convenience method for {@link #properties()} implementation of bijective converters
* between comparable objects. The converter is presumed invertible and to preserve order.
*/
static Set<FunctionProperty> bijective() {
return EnumSet.of(FunctionProperty.INJECTIVE, FunctionProperty.SURJECTIVE,
FunctionProperty.ORDER_PRESERVING, FunctionProperty.INVERTIBLE);
}
/**
* Default to non-invertible conversion. Must be overridden by subclasses that support inversions.
*
* @return a converter for converting instances of <var>T</var> back to instances of <var>S</var>.
*/
@Override
public ObjectConverter<T,S> inverse() throws UnsupportedOperationException {
throw new UnsupportedOperationException(Errors.format(Errors.Keys.NonInvertibleConversion));
}
/**
* Performs the comparisons documented in {@link ClassPair#equals(Object)} with an additional
* check: if <strong>both</strong> objects to compare are {@code SystemConverter}, then also
* requires the two objects to be of the same class. We do that in order to differentiate the
* "ordinary" converters from the {@link FallbackConverter}.
*
* <div class="section">Implementation note</div>
* This is admittedly a little bit convolved. A cleaner approach would have been to not allow
* the {@code ConverterRegister} hash map to contain anything else than {@code ClassPair} keys,
* but the current strategy of using the same instance for keys and values reduces a little bit
* the number of objects to create in the JVM. An other cleaner approach would have been to
* compare {@code ObjectConverter}s in a separated method, but users invoking {@code equals}
* on our system converters could be surprised.
*
* <p>Our {@code equals(Object)} definition have the following implications regarding
* the way to use the {@link ConverterRegistry#converters} map:</p>
* <ul>
* <li>When searching for a converter of the same class than the key (as in the
* {@link ConverterRegistry#findEquals(SystemConverter)} method), then there
* is no restriction on the key that can be given to the {@code Map.get(K)}
* method. The {@code Map} is "normal".</li>
* <li>When searching for a converter for a pair of source and target classes
* (as in {@link ConverterRegistry#find(Class, Class)}), the key shall be
* an instance of {@code ClassPair} instance (not a subclass).</li>
* </ul>
*
* @param other the object to compare with this {@code SystemConverter}.
* @return {@code true} if the given object is a {@code ClassPair} or a converter of the
* same class than {@code this}, and both have the same source and target classes.
*/
@Override
public final boolean equals(final Object other) {
if (super.equals(other)) {
final Class<?> type = other.getClass();
return type == ClassPair.class || type == getClass();
}
return false;
}
/**
* Returns an unique instance of this converter if one exists. If a converter already
* exists for the same source an target classes, then this converter is returned.
* Otherwise this converter is returned <strong>without</strong> being cached.
*
* @return the unique instance, or {@code this} if no unique instance can be found.
*
* @see ObjectToString#unique()
*/
public ObjectConverter<S,T> unique() {
/*
* On deserialization, some fields are not yet assigned a value at the moment of this call.
* This happen when this unique() method is invoked by inverse().readResolve() — not by the
* readResolve() method of this class — in which case the deserialization process is not yet
* fully completed. In such cases, we can not determine if an existing instance is available.
* We return the current instance as a fallback, leaving to inverse().readResolve() the task
* of returning a unique instance after it finished its own deserialization process.
*/
if (sourceClass != null && targetClass != null) {
final ObjectConverter<S,T> existing = SystemRegistry.INSTANCE.findEquals(this);
if (existing != null) return existing;
}
return this;
}
/**
* Returns the singleton instance on deserialization, if any. If no instance already exist
* in the virtual machine, we do not cache the instance (for now) for security reasons.
*
* @return the object to use after deserialization.
* @throws ObjectStreamException if the serialized object defines an unknown data type.
*/
protected final Object readResolve() throws ObjectStreamException {
return unique();
}
/**
* Formats an error message for a value that can not be converted.
*
* @param value the value that can not be converted.
* @return the error message.
*/
final String formatErrorMessage(final S value) {
return Errors.format(Errors.Keys.CanNotConvertValue_2, value, targetClass);
}
}