package de.lmu.ifi.dbs.elki.utilities;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
Copyright (C) 2011
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException;
import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
/**
* Utils for handling class instantiation especially with respect to Java
* generics.
*
* <p>
* Due to the way generics are implemented - via erasure - type safety cannot be
* guaranteed properly at compile time here. These classes collect such cases
* using helper functions, so that we have to suppress these warnings only in
* one place.
* </p>
*
* <p>
* Note that many of these situations are still type safe, i.e. an <i>empty</i>
* array of List<List<?>> can indeed be cast into a List<List<Whatever>>.
* </p>
*
* <p>
* The only one potentially unsafe is {@link #instantiateGenerics}, since we
* can't verify that the runtime type 'type' adhers to the compile time
* restriction T. When T is not generic, such a check is possible, and then the
* developer should use {@link #instantiate} instead.
* </p>
*
*/
public final class ClassGenericsUtil {
/**
* Static logger to use.
*/
private static final Logging logger = Logging.getLogger(ClassGenericsUtil.class);
/**
* Name for a static "parameterize" factory method.
*/
public static final String FACTORY_METHOD_NAME = "parameterize";
/**
* <p>
* Returns a new instance of the given type for the specified className.
* </p>
*
* <p>
* If the Class for className is not found, the instantiation is tried using
* the package of the given type as package of the given className.
* </p>
*
* @param <T> Class type for compile time type checking
* @param type desired Class type of the Object to retrieve
* @param className name of the class to instantiate
* @return a new instance of the given type for the specified className
* @throws UnableToComplyException if the instantiation cannot be performed
* successfully
*/
public static <T> T instantiate(Class<T> type, String className) throws UnableToComplyException {
T instance;
try {
try {
instance = type.cast(Class.forName(className).newInstance());
}
catch(ClassNotFoundException e) {
// try package of type
instance = type.cast(Class.forName(type.getPackage().getName() + "." + className).newInstance());
}
}
catch(InstantiationException e) {
throw new UnableToComplyException(e);
}
catch(IllegalAccessException e) {
throw new UnableToComplyException(e);
}
catch(ClassNotFoundException e) {
throw new UnableToComplyException(e);
}
catch(ClassCastException e) {
throw new UnableToComplyException(e);
}
return instance;
}
/**
* <p>
* Returns a new instance of the given type for the specified className.
* </p>
*
* <p>
* If the Class for className is not found, the instantiation is tried using
* the package of the given type as package of the given className.
* </p>
*
* <p>
* This is a weaker type checked version of "{@link #instantiate}" for use
* with Generics.
* </p>
*
* @param <T> Class type for compile time type checking
* @param type desired Class type of the Object to retrieve
* @param className name of the class to instantiate
* @return a new instance of the given type for the specified className
* @throws UnableToComplyException if the instantiation cannot be performed
* successfully
*/
@SuppressWarnings("unchecked")
public static <T> T instantiateGenerics(Class<?> type, String className) throws UnableToComplyException {
T instance;
// TODO: can we do a verification that type conforms to T somehow?
// (probably not because generics are implemented via erasure.
try {
try {
instance = ((Class<T>) type).cast(Class.forName(className).newInstance());
}
catch(ClassNotFoundException e) {
// try package of type
instance = ((Class<T>) type).cast(Class.forName(type.getPackage().getName() + "." + className).newInstance());
}
}
catch(InstantiationException e) {
throw new UnableToComplyException(e);
}
catch(IllegalAccessException e) {
throw new UnableToComplyException(e);
}
catch(ClassNotFoundException e) {
throw new UnableToComplyException(e);
}
catch(ClassCastException e) {
throw new UnableToComplyException(e);
}
return instance;
}
/**
* Inspect the class for a static "parameterize" method that satisfies certain
* constraints.
*
* @param <C> Return class type
* @param c Class to inspect.
* @param ret Expected return type
* @return factory method that can be called with
* {@code factory(null, Parameterization)}.
* @throws NoSuchMethodException When no factory method was found, or it
* doesn't fit the constraints.
* @throws Exception On other errors such as security exceptions
*/
public static <C> Method getParameterizationFactoryMethod(Class<C> c, Class<?> ret) throws NoSuchMethodException, Exception {
Method m = c.getMethod(FACTORY_METHOD_NAME, Parameterization.class);
if(m == null) {
throw new NoSuchMethodException("No parameterization method found.");
}
if(!ret.isAssignableFrom(m.getReturnType())) {
throw new NoSuchMethodException("Return type doesn't match: " + m.getReturnType().getName() + ", expected: " + ret.getName());
}
if(!java.lang.reflect.Modifier.isStatic(m.getModifiers())) {
throw new NoSuchMethodException("Factory method is not static.");
}
return m;
}
/**
* Get a parameterizer for the given class.
*
* @param c Class
* @return Parameterizer or null.
*/
public static Parameterizer getParameterizer(Class<?> c) {
for(Class<?> inner : c.getDeclaredClasses()) {
if(Parameterizer.class.isAssignableFrom(inner)) {
try {
return inner.asSubclass(Parameterizer.class).newInstance();
}
catch(Exception e) {
logger.warning("Non-usable Parameterizer in class: " + c.getName());
}
}
}
return null;
}
/**
* Instantiate a parameterizable class. When using this, consider using
* {@link Parameterization#descend}!
*
* @param <C> base type
* @param r Base (restriction) class
* @param c Class to instantiate
* @param config Configuration to use for instantiation.
* @return Instance
* @throws InvocationTargetException when an exception occurred within the
* constructor
* @throws NoSuchMethodException when no suitable constructor was found
* @throws Exception when other instantiation errors occurred
*/
public static <C> C tryInstantiate(Class<C> r, Class<?> c, Parameterization config) throws InvocationTargetException, NoSuchMethodException, Exception {
if(c == null) {
// TODO: better class? AbortException maybe?
throw new UnsupportedOperationException("Trying to instantiate 'null' class!");
}
// Try a V3 parameterization class
Parameterizer par = getParameterizer(c);
// TODO: API good?
if(par != null && par instanceof AbstractParameterizer) {
final Object instance = ((AbstractParameterizer) par).make(config);
return r.cast(instance);
}
// Try a V2 static parameterization method
try {
final Method factory = getParameterizationFactoryMethod(c, r);
final Object instance = factory.invoke(null, config);
return r.cast(instance);
}
catch(NoSuchMethodException e) {
// continue.
}
// Try a regular "parameterization" constructor
try {
final Constructor<?> constructor = c.getConstructor(Parameterization.class);
final Object instance = constructor.newInstance(config);
return r.cast(instance);
}
catch(NoSuchMethodException e) {
// continue
}
// Try a default constructor.
final Object instance = c.getConstructor().newInstance();
return r.cast(instance);
}
/**
* Force parameterization method.
*
* Please use this only in "runner" classes such as unit tests, since the
* error handling is not very flexible.
*
* @param <C> Type
* @param c Class to instantiate
* @param config Parameters
* @return Instance or throw an AbortException
*/
@SuppressWarnings("unchecked")
public static <C> C parameterizeOrAbort(Class<?> c, Parameterization config) {
try {
return tryInstantiate((Class<C>) c, c, config);
}
catch(Exception e) {
throw new AbortException("Instantiation failed", e);
}
}
/**
* Create an array (of null values)
*
* This is a common unchecked cast we have to do due to Java Generics
* limitations.
*
* @param <T> Type the array elements have
* @param len array size
* @param base template class for array creation.
* @return new array of null pointers.
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArrayOfNull(int len, Class<T> base) {
return (T[]) java.lang.reflect.Array.newInstance(base, len);
}
/**
* Convert a collection to an array.
*
* @param <B> Base type
* @param <T> Type the array elements have
* @param coll collection to convert.
* @param base Template class for array creation.
* @return new array with the collection contents.
*/
@SuppressWarnings("unchecked")
public static <B, T extends B> T[] toArray(Collection<T> coll, Class<B> base) {
return coll.toArray((T[]) newArray(base, 0));
}
/**
* Create an array of <code>len</code> empty ArrayLists.
*
* This is a common unchecked cast we have to do due to Java Generics
* limitations.
*
* @param <T> Type the list elements have
* @param len array size
* @return new array of ArrayLists
*/
@SuppressWarnings("unchecked")
public static <T> ArrayList<T>[] newArrayOfEmptyArrayList(int len) {
ArrayList<T>[] result = new ArrayList[len];
for(int i = 0; i < len; i++) {
result[i] = new ArrayList<T>();
}
return result;
}
/**
* Create an array of <code>len</code> empty HashSets.
*
* This is a common unchecked cast we have to do due to Java Generics
* limitations.
*
* @param <T> Type the set elements have
* @param len array size
* @return new array of HashSets
*/
@SuppressWarnings("unchecked")
public static <T> HashSet<T>[] newArrayOfEmptyHashSet(int len) {
HashSet<T>[] result = new HashSet[len];
for(int i = 0; i < len; i++) {
result[i] = new HashSet<T>();
}
return result;
}
/**
* Cast the (erased) generics onto a class.
*
* Note: this function is a hack - notice that it would allow you to up-cast
* any class! Still it is preferable to have this cast in one place than in
* dozens without any explanation.
*
* The reason this is needed is the following: There is no
* Class<Set<String>>.class. This method allows you to do <code>
* Class<Set<String>> setclass = uglyCastIntoSubclass(Set.class);
* </code>
*
* We can't type check at runtime, since we don't have T.
*
* @param cls Class type
* @param <D> Base type
* @param <T> Supertype
* @return {@code cls} parameter, but cast to {@code Class<T>}
*/
@SuppressWarnings("unchecked")
public static <D, T extends D> Class<T> uglyCastIntoSubclass(Class<D> cls) {
return (Class<T>) cls;
}
/**
* This class performs an ugly cast, from <code>Class<F></code> to
* <code>Class<T></code>, where both F and T need to extend B.
*
* The restrictions are there to avoid misuse of this cast helper.
*
* While this sounds really ugly, the common use case will be something like
*
* <pre>
* BASE = Class<Database>
* FROM = Class<Database>
* TO = Class<Database<V>>
* </pre>
*
* i.e. the main goal is to add missing Generics to the compile time type.
*
* @param <BASE> Base type
* @param <TO> Destination type
* @param <FROM> Source type
* @param cls Class to be cast
* @param base Base class for type checking.
* @return Casted class.
*/
@SuppressWarnings("unchecked")
public static <BASE, FROM extends BASE, TO extends BASE> Class<TO> uglyCrossCast(Class<FROM> cls, Class<BASE> base) {
if(!base.isAssignableFrom(cls)) {
if(cls == null) {
throw new ClassCastException("Attempted to use 'null' as class.");
}
throw new ClassCastException(cls.getName() + " is not a superclass of " + base);
}
return (Class<TO>) cls;
}
/**
* Cast an object at a base class, but return a subclass (for Generics!).
*
* The main goal of this is to allow casting an object from e.g. "
* <code>List</code>" to "<code>List<Something></code>" without having
* to add SuppressWarnings everywhere.
*
* @param <B> Base type to cast at
* @param <T> Derived type returned
* @param base Base class to cast at
* @param obj Object
* @return Cast object or null.
*/
@SuppressWarnings("unchecked")
public static <B, T extends B> T castWithGenericsOrNull(Class<B> base, Object obj) {
try {
return (T) base.cast(obj);
}
catch(ClassCastException e) {
return null;
}
}
/**
* Generic newInstance that tries to clone an object.
*
* @param <T> Object type, generic
* @param obj Master copy - must not be null.
* @return New instance, if possible
* @throws InstantiationException on error
* @throws IllegalAccessException on error
* @throws InvocationTargetException on error
* @throws NoSuchMethodException on error
*/
@SuppressWarnings("unchecked")
public static <T> T newInstance(T obj) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
try {
Object n = obj.getClass().getConstructor().newInstance();
return (T) n;
}
catch(NullPointerException e) {
throw new IllegalArgumentException("Null pointer exception in newInstance()", e);
}
}
/**
* Retrieve the component type of a given array. For cloning.
*
* @param <T> Array type, generic
* @param a Existing array
* @return Component type of the given array.
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> getComponentType(T[] a) {
Class<?> k = a.getClass().getComponentType();
return (Class<? extends T>) k;
}
/**
* Make a new array of the given class and size.
*
* @param <T> Generic type
* @param k Class
* @param size Size
* @return new array of the given type
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArray(Class<? extends T> k, int size) {
if(k.isPrimitive()) {
throw new IllegalArgumentException("Argument cannot be primitive: " + k);
}
Object a = java.lang.reflect.Array.newInstance(k, size);
return (T[]) a;
}
/**
* Clone an array of the given type.
*
* @param <T> Generic type
* @param a existing array
* @param size array size
* @return new array
*/
public static <T> T[] newArray(T[] a, int size) {
return newArray(getComponentType(a), size);
}
/**
* Clone a collection. Collection must have an empty constructor!
*
* @param <T> Data type
* @param <C> Collection type
* @param coll Existing collection
* @return Cloned collection
*/
public static <T, C extends Collection<T>> C cloneCollection(C coll) {
try {
C copy = newInstance(coll);
copy.addAll(coll);
return copy;
}
catch(InstantiationException e) {
throw new RuntimeException(e);
}
catch(IllegalAccessException e) {
throw new RuntimeException(e);
}
catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
catch(NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Transform a collection to an Array
*
* @param <T> object type
* @param c Collection
* @param a Array to write to or replace (i.e. sample array)
* @return new array containing the collection elements
*/
public static <T> T[] collectionToArray(Collection<T> c, T[] a) {
if(a.length < c.size()) {
a = newArray(a, c.size());
}
int i = 0;
for(T x : c) {
a[i] = x;
i++;
}
if(i < a.length) {
a[i] = null;
}
return a;
}
}