/*
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/
package ch.njol.skript.registrations;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ChainedConverter;
import ch.njol.skript.classes.Converter;
import ch.njol.skript.classes.Converter.ConverterInfo;
import ch.njol.skript.classes.Converter.ConverterUtils;
import ch.njol.util.Pair;
/**
* @author Peter Güttinger
*/
public abstract class Converters {
private Converters() {}
private static List<ConverterInfo<?, ?>> converters = new ArrayList<ConverterInfo<?, ?>>(50);
@SuppressWarnings("null")
public static List<ConverterInfo<?, ?>> getConverters() {
return Collections.unmodifiableList(converters);
}
/**
* Registers a converter.
*
* @param from
* @param to
* @param converter
*/
public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final Converter<F, T> converter) {
registerConverter(from, to, converter, 0);
}
@Deprecated
public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final ch.njol.skript.classes.SerializableConverter<F, T> converter) {
registerConverter(from, to, (Converter<F, T>) converter);
}
public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final Converter<F, T> converter, final int options) {
Skript.checkAcceptRegistrations();
final ConverterInfo<F, T> info = new ConverterInfo<F, T>(from, to, converter, options);
for (int i = 0; i < converters.size(); i++) {
final ConverterInfo<?, ?> info2 = converters.get(i);
if (info2.from.isAssignableFrom(from) && to.isAssignableFrom(info2.to)) {
converters.add(i, info);
return;
}
}
converters.add(info);
}
@Deprecated
public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final ch.njol.skript.classes.SerializableConverter<F, T> converter, final int options) {
registerConverter(from, to, (Converter<F, T>) converter, options);
}
// REMIND how to manage overriding of converters? - shouldn't actually matter
public static void createMissingConverters() {
for (int i = 0; i < converters.size(); i++) {
final ConverterInfo<?, ?> info = converters.get(i);
for (int j = 0; j < converters.size(); j++) {// not from j = i+1 since new converters get added during the loops
final ConverterInfo<?, ?> info2 = converters.get(j);
if ((info.options & Converter.NO_RIGHT_CHAINING) == 0 && (info2.options & Converter.NO_LEFT_CHAINING) == 0
&& info2.from.isAssignableFrom(info.to) && !converterExistsSlow(info.from, info2.to)) {
converters.add(createChainedConverter(info, info2));
} else if ((info.options & Converter.NO_LEFT_CHAINING) == 0 && (info2.options & Converter.NO_RIGHT_CHAINING) == 0
&& info.from.isAssignableFrom(info2.to) && !converterExistsSlow(info2.from, info.to)) {
converters.add(createChainedConverter(info2, info));
}
}
}
}
private final static boolean converterExistsSlow(final Class<?> from, final Class<?> to) {
for (final ConverterInfo<?, ?> i : converters) {
if ((i.from.isAssignableFrom(from) || from.isAssignableFrom(i.from)) && (i.to.isAssignableFrom(to) || to.isAssignableFrom(i.to))) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private static <F, M, T> ConverterInfo<F, T> createChainedConverter(final ConverterInfo<?, ?> first, final ConverterInfo<?, ?> second) {
return new ConverterInfo<F, T>((Class<F>) first.from, (Class<T>) second.to, new ChainedConverter<F, M, T>((Converter<F, M>) first.converter, (Converter<M, T>) second.converter), first.options | second.options);
}
/**
* Converts the given value to the desired type. If you want to convert multiple values of the same type you should use {@link #getConverter(Class, Class)} to get a
* converter to convert the values.
*
* @param o
* @param to
* @return The converted value or null if no converter exists or the converter returned null for the given value.
*/
@SuppressWarnings("unchecked")
@Nullable
public static <F, T> T convert(final @Nullable F o, final Class<T> to) {
if (o == null)
return null;
if (to.isInstance(o))
return (T) o;
@SuppressWarnings("null")
final Converter<? super F, ? extends T> conv = getConverter((Class<F>) o.getClass(), to);
if (conv == null)
return null;
return conv.convert(o);
}
/**
* Converts an object into one of the given types.
* <p>
* This method does not convert the object if it is already an instance of any of the given classes.
*
* @param o
* @param to
* @return The converted object
*/
@SuppressWarnings("unchecked")
@Nullable
public final static <F, T> T convert(final @Nullable F o, final Class<? extends T>[] to) {
if (o == null)
return null;
for (final Class<? extends T> t : to)
if (t.isInstance(o))
return (T) o;
final Class<F> c = (Class<F>) o.getClass();
for (final Class<? extends T> t : to) {
@SuppressWarnings("null")
final Converter<? super F, ? extends T> conv = getConverter(c, t);
if (conv != null)
return conv.convert(o);
}
return null;
}
/**
* Converts all entries in the given array to the desired type, using {@link #convert(Object, Class)} to convert every single value. If you want to convert an array of values
* of a known type, consider using {@link #convert(Object[], Class, Converter)} for much better performance.
*
* @param o
* @param to
* @return A T[] array without null elements
*/
@SuppressWarnings("unchecked")
@Nullable
public static <T> T[] convertArray(final @Nullable Object[] o, final Class<T> to) {
assert to != null;
if (o == null)
return null;
if (to.isAssignableFrom(o.getClass().getComponentType()))
return (T[]) o;
final List<T> l = new ArrayList<T>(o.length);
for (final Object e : o) {
final T c = convert(e, to);
if (c != null)
l.add(c);
}
return l.toArray((T[]) Array.newInstance(to, l.size()));
}
/**
* Converts multiple objects into any of the given classes.
*
* @param o
* @param to
* @param superType The component type of the returned array
* @return The converted array
*/
@SuppressWarnings("unchecked")
public static <T> T[] convertArray(final @Nullable Object[] o, final Class<? extends T>[] to, final Class<T> superType) {
if (o == null) {
final T[] r = (T[]) Array.newInstance(superType, 0);
assert r != null;
return r;
}
for (final Class<? extends T> t : to)
if (t.isAssignableFrom(o.getClass().getComponentType()))
return (T[]) o;
final List<T> l = new ArrayList<T>(o.length);
for (final Object e : o) {
final T c = convert(e, to);
if (c != null)
l.add(c);
}
final T[] r = l.toArray((T[]) Array.newInstance(superType, l.size()));
assert r != null;
return r;
}
private final static Map<Pair<Class<?>, Class<?>>, Converter<?, ?>> convertersCache = new HashMap<Pair<Class<?>, Class<?>>, Converter<?, ?>>();
/**
* Tests whether a converter between the given classes exists.
*
* @param from
* @param to
* @return Whether a converter exists
*/
public final static boolean converterExists(final Class<?> from, final Class<?> to) {
if (to.isAssignableFrom(from) || from.isAssignableFrom(to))
return true;
return getConverter(from, to) != null;
}
public final static boolean converterExists(final Class<?> from, final Class<?>... to) {
for (final Class<?> t : to) {
assert t != null;
if (converterExists(from, t))
return true;
}
return false;
}
/**
* Gets a converter
*
* @param from
* @param to
* @return the converter or null if none exist
*/
@SuppressWarnings("unchecked")
@Nullable
public final static <F, T> Converter<? super F, ? extends T> getConverter(final Class<F> from, final Class<T> to) {
final Pair<Class<?>, Class<?>> p = new Pair<Class<?>, Class<?>>(from, to);
if (convertersCache.containsKey(p)) // can contain null to denote nonexistence of a converter
return (Converter<? super F, ? extends T>) convertersCache.get(p);
final Converter<? super F, ? extends T> c = getConverter_i(from, to);
convertersCache.put(p, c);
return c;
}
@SuppressWarnings("unchecked")
@Nullable
private final static <F, T> Converter<? super F, ? extends T> getConverter_i(final Class<F> from, final Class<T> to) {
for (final ConverterInfo<?, ?> conv : converters) {
if (conv.from.isAssignableFrom(from) && to.isAssignableFrom(conv.to))
return (Converter<? super F, ? extends T>) conv.converter;
}
for (final ConverterInfo<?, ?> conv : converters) {
if (conv.from.isAssignableFrom(from) && conv.to.isAssignableFrom(to)) {
return (Converter<? super F, ? extends T>) ConverterUtils.createInstanceofConverter(conv.converter, to);
} else if (from.isAssignableFrom(conv.from) && to.isAssignableFrom(conv.to)) {
return (Converter<? super F, ? extends T>) ConverterUtils.createInstanceofConverter(conv);
}
}
for (final ConverterInfo<?, ?> conv : converters) {
if (from.isAssignableFrom(conv.from) && conv.to.isAssignableFrom(to)) {
return (Converter<? super F, ? extends T>) ConverterUtils.createDoubleInstanceofConverter(conv, to);
}
}
return null;
}
/**
* @param from
* @param to
* @param conv
* @return The converted array
* @throws ArrayStoreException if the given class is not a superclass of all objects returned by the converter
*/
@SuppressWarnings("unchecked")
public final static <F, T> T[] convertUnsafe(final F[] from, final Class<?> to, final Converter<? super F, ? extends T> conv) {
return convert(from, (Class<T>) to, conv);
}
public final static <F, T> T[] convert(final F[] from, final Class<T> to, final Converter<? super F, ? extends T> conv) {
@SuppressWarnings("unchecked")
T[] ts = (T[]) Array.newInstance(to, from.length);
int j = 0;
for (int i = 0; i < from.length; i++) {
final F f = from[i];
final T t = f == null ? null : conv.convert(f);
if (t != null)
ts[j++] = t;
}
if (j != ts.length)
ts = Arrays.copyOf(ts, j);
assert ts != null;
return ts;
}
}