blob: dc1519dae37ae9c9415dce1db62fc59f82448f6c [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.io.Serializable;
import org.apache.sis.util.ObjectConverter;
/**
* Holds explicit {@link #sourceClass} and {@link #targetClass} values. Used as key in a hash
* map of converters. Also used as the base class for converters defined in this package.
*
* <p>The only direct subtype allowed is {@link SystemConverter}.
* <strong>No other direct subtype shall exist</strong>.
* See {@link #equals(Object)} for an explanation.</p>
*
* <h2>Immutability and thread safety</h2>
* This base class is immutable and thus inherently thread-safe. {@code ClassPair} immutability is necessary
* for {@link ConverterRegistry}. Subclasses should also be immutable, but this requirement is not as strong
* as for {@code ClassPair} (because subclasses are not used as keys in hash map).
*
* @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
*/
class ClassPair<S,T> implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -5214470401299470687L;
/**
* The source class.
*/
final Class<S> sourceClass;
/**
* The target class.
*/
final Class<T> targetClass;
/**
* Creates an entry for the given source and target classes.
*
* @param sourceClass the {@linkplain ObjectConverter#getSourceClass() source class}.
* @param targetClass the {@linkplain ObjectConverter#getTargetClass() target class}.
*/
ClassPair(final Class<S> sourceClass, final Class<T> targetClass) {
this.sourceClass = sourceClass;
this.targetClass = targetClass;
}
/**
* Returns a key for the parent source, or {@code null} if none.
* This method applies the following rules:
*
* <ul>
* <li>If {@link #sourceClass} is a class and have a parent class, then returns a new
* {@code ClassPair} having that parent class as the source.</li>
* <li>Otherwise if {@link #sourceClass} is an interface extending at least one interface,
* then returns a new {@code ClassPair} having the first parent interface as the source.
* we select the first interface on the assumption that it is the mean one.</li>
* <li>Otherwise (i.e. if there is no parent class or interface), returns {@code null}.</li>
* </ul>
*
* The target class is left unchanged.
*
* @return a key for the parent source, or {@code null}.
*/
final ClassPair<? super S, T> parentSource() {
final Class<? super S> source;
if (sourceClass.isInterface()) {
@SuppressWarnings({"unchecked","rawtypes"})
final Class<? super S>[] interfaces = (Class[]) sourceClass.getInterfaces();
if (interfaces.length == 0) {
return null;
}
source = interfaces[0]; // Take only the first interface declaration; ignore others.
} else {
source = sourceClass.getSuperclass();
if (source == null) {
return null;
}
}
return new ClassPair<>(source, targetClass);
}
/**
* Casts the given converter to the source and target classes of this {@code ClassPair}.
* This method is not public because the checks are performed using assertions only.
* If this method was to goes public, the assertions would need to be replaced by
* unconditional checks.
*
* <p>This method is used by {@link ConverterRegistry} after fetching a value from a hash
* map using this {@code ClassPair} as a key. In this context, the cast should never fail
* (assuming that the converters do not change their source and target classes).</p>
*/
@SuppressWarnings("unchecked")
final ObjectConverter<? super S, ? extends T> cast(final ObjectConverter<?,?> converter) {
if (converter != null) {
assert converter.getSourceClass().isAssignableFrom(sourceClass) : sourceClass;
assert targetClass.isAssignableFrom(converter.getTargetClass()) : targetClass;
}
return (ObjectConverter<? super S, ? extends T>) converter;
}
/**
* Compares the given object with this {@code ClassPair} for equality. Two {@code ClassPair}
* instances are considered equal if they have the same source and target classes, ignoring
* all other properties eventually defined in subclasses.
*
* <p>This method is designed for use by {@link ConverterRegistry} as {@link java.util.HashMap}
* keys. Its primary purpose is <strong>not</strong> to determine if two objects are converters
* doing the same conversions. However the {@link SystemConverter} subclass overrides this
* method with an additional safety check.</p>
*
* @param other the object to compare with this {@code ClassPair}.
* @return {@code true} if the given object is a {@code ClassPair}
* having the same source and target classes.
*/
@Override
public boolean equals(final Object other) {
if (other instanceof ClassPair<?,?>) {
final ClassPair<?,?> that = (ClassPair<?,?>) other;
return sourceClass == that.sourceClass &&
targetClass == that.targetClass;
}
return false;
}
/**
* Returns a hash code value for this {@code ClassPair}.
* See {@link #equals(Object)} javadoc for information on the scope of this method.
*/
@Override
public final int hashCode() {
return sourceClass.hashCode() + 31*targetClass.hashCode();
}
/**
* Returns a string representation for this entry.
* Used for formatting error messages.
*/
@Override
public String toString() {
return targetClass.getSimpleName() + " ← " + sourceClass.getSimpleName();
}
}