All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.convert.DefaultConverter Maven / Gradle / Ivy

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
 * Institute of Molecular Cell Biology and Genetics, University of
 * Konstanz, and KNIME GmbH.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava.convert;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.scijava.Priority;
import org.scijava.plugin.Plugin;
import org.scijava.util.ArrayUtils;
import org.scijava.util.ConversionUtils;
import org.scijava.util.Types;

/**
 * Default {@link Converter} implementation. Provides useful conversion
 * functionality for many common conversion cases.
 * 

* Supported conversions include: *

*
    *
  • Object to Array
  • *
  • Object to Collection
  • *
  • Number to Number
  • *
  • Object to String
  • *
  • String to Character
  • *
  • String to Enum
  • *
  • Objects where the destination Class has a constructor which takes that * Object *
  • *
* * @author Mark Hiner */ @Plugin(type = Converter.class, priority = Priority.EXTREMELY_LOW) public class DefaultConverter extends AbstractConverter { // -- ConversionHandler methods -- @Override public Object convert(final Object src, final Type dest) { // Handle array types, including generic array types. final Type componentType = Types.component(dest); if (componentType != null) { // NB: Destination is an array type. return convertToArray(src, Types.raw(componentType)); } // Handle parameterized collection types. if (dest instanceof ParameterizedType && isCollection(dest)) { return convertToCollection(src, (ParameterizedType) dest); } // This wasn't a collection or array, so convert it as a single element. return convert(src, Types.raw(dest)); } @Override public T convert(final Object src, final Class dest) { // ensure type is well-behaved, rather than a primitive type final Class saneDest = Types.box(dest); // Handle array types if (isArray(dest)) { @SuppressWarnings("unchecked") T array = (T) convertToArray(src, Types.raw(Types.component(dest))); return array; } // special case for conversion from number to number if (src instanceof Number) { final Number number = (Number) src; if (saneDest == Byte.class) { final Byte result = number.byteValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } if (saneDest == Double.class) { final Double result = number.doubleValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } if (saneDest == Float.class) { final Float result = number.floatValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } if (saneDest == Integer.class) { final Integer result = number.intValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } if (saneDest == Long.class) { final Long result = number.longValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } if (saneDest == Short.class) { final Short result = number.shortValue(); @SuppressWarnings("unchecked") final T typedResult = (T) result; return typedResult; } } // special cases for strings if (src instanceof String) { // source type is String final String s = (String) src; if (s.isEmpty()) { // return null for empty strings return Types.nullValue(dest); } // use first character when converting to Character if (saneDest == Character.class) { final Character c = new Character(s.charAt(0)); @SuppressWarnings("unchecked") final T result = (T) c; return result; } // special case for conversion to enum if (dest.isEnum()) { final T result = ConversionUtils.convertToEnum(s, dest); if (result != null) return result; } } if (saneDest == String.class) { // destination type is String; use Object.toString() method final String sValue = src.toString(); @SuppressWarnings("unchecked") final T result = (T) sValue; return result; } // wrap the original object with one of the new type, using a constructor try { final Constructor ctor = getConstructor(saneDest, src.getClass()); if (ctor == null) return null; @SuppressWarnings("unchecked") final T instance = (T) ctor.newInstance(src); return instance; } catch (final Exception exc) { // TODO: Best not to catch blanket Exceptions here. // no known way to convert return null; } } @Override public Class getOutputType() { return Object.class; } @Override public Class getInputType() { return Object.class; } // -- Helper methods -- private Constructor getConstructor(final Class type, final Class argType) { for (final Constructor ctor : type.getConstructors()) { final Class[] params = ctor.getParameterTypes(); if (params.length == 1 && // Types.isAssignable(Types.box(argType), Types.box(params[0]))) { return ctor; } } return null; } private boolean isArray(final Type type) { return Types.component(type) != null; } private boolean isCollection(final Type type) { return Types.isAssignable(Types.raw(type), Collection.class); } private Object convertToArray(final Object value, final Class componentType) { // First we make sure the value is a collection. This provides the simplest // interface for iterating over all the elements. We use SciJava's // PrimitiveArray collection implementations internally, so that this // conversion is always wrapping by reference, for performance. final Collection items = ArrayUtils.toCollection(value); final Object array = Array.newInstance(componentType, items.size()); // Populate the array by converting each item in the value collection // to the component type. int index = 0; for (final Object item : items) { Array.set(array, index++, convert(item, componentType)); } return array; } private Object convertToCollection(final Object value, final ParameterizedType pType) { final Collection collection = createCollection(Types.raw(pType)); if (collection == null) return null; // Populate the collection. final Collection items = ArrayUtils.toCollection(value); // TODO: The following can fail; e.g. "Foo extends ArrayList" final Type collectionType = pType.getActualTypeArguments()[0]; for (final Object item : items) { collection.add(convert(item, collectionType)); } return collection; } private Collection createCollection(final Class type) { // If we were given an interface or abstract class, and not a concrete // class, we attempt to make default implementations. if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { // We don't have a concrete class. If it's a set or a list, we use // the typical default implementation. Otherwise we won't convert. if (Types.isAssignable(type, List.class)) return new ArrayList<>(); if (Types.isAssignable(type, Set.class)) return new HashSet<>(); return null; } // Got a concrete type. Instantiate it. try { @SuppressWarnings("unchecked") final Collection c = (Collection) type.newInstance(); return c; } catch (final InstantiationException exc) { return null; } catch (final IllegalAccessException exc) { return null; } } // -- Deprecated API -- @Override @Deprecated public boolean canConvert(final Class src, final Type dest) { // Handle array types, including generic array types. if (isArray(dest)) return true; // Handle parameterized collection types. if (dest instanceof ParameterizedType && isCollection(dest) && createCollection(Types.raw(dest)) != null) { return true; } return super.canConvert(src, dest); } @Override @Deprecated public boolean canConvert(final Class src, final Class dest) { // ensure type is well-behaved, rather than a primitive type final Class saneDest = Types.box(dest); // OK for numerical conversions if (Types.isAssignable(Types.box(src), Number.class) && // (Types.isByte(dest) || Types.isDouble(dest) || Types.isFloat(dest) || Types.isInteger(dest) || Types.isLong(dest) || Types.isShort(dest))) { return true; } // OK if string if (saneDest == String.class) return true; if (Types.isAssignable(src, String.class)) { // OK if source type is string and destination type is character // (in this case, the first character of the string would be used) if (saneDest == Character.class) return true; // OK if source type is string and destination type is an enum if (dest.isEnum()) return true; } // OK if appropriate wrapper constructor exists try { return getConstructor(saneDest, src) != null; } catch (final Exception exc) { // TODO: Best not to catch blanket Exceptions here. // no known way to convert return false; } } }