org.lenskit.util.TypeUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.lenskit.util.TypeUtils.java

Source

/*
 * LensKit, an open-source toolkit for recommender systems.
 * Copyright 2014-2017 LensKit contributors (see CONTRIBUTORS.md)
 * Copyright 2010-2014 Regents of the University of Minnesota
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.lenskit.util;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.text.StringTokenizer;
import org.joda.convert.FromStringConverter;
import org.joda.convert.StringConvert;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Various type utilities used in LensKit.
 *
 * @author <a href="http://www.grouplens.org">GroupLens Research</a>
 */
public class TypeUtils {
    private TypeUtils() {
    }

    /**
     * Return the supertype closure of a type (the type and all its transitive
     * supertypes).
     *
     * @param type The type.
     * @return All supertypes of the type, including the type itself.
     */
    public static Set<Class<?>> typeClosure(Class<?> type) {
        if (type == null) {
            return Collections.emptySet();
        }

        Set<Class<?>> supertypes = new HashSet<>();
        supertypes.add(type);
        supertypes.addAll(typeClosure(type.getSuperclass()));
        for (Class<?> iface : type.getInterfaces()) {
            supertypes.addAll(typeClosure(iface));
        }

        return supertypes;
    }

    /**
     * Make a type token for a list of a particular element type.
     * @param element The element type.
     * @param <T> The element type.
     * @return A type token representing {@code List<T>}.
     */
    public static <T> TypeToken<List<T>> makeListType(TypeToken<T> element) {
        return new TypeToken<List<T>>() {
        }.where(new TypeParameter<T>() {
        }, element);
    }

    /**
     * Extract the element type from a type token representing a list.
     * @param token The type token.
     * @param <T> The list element type.
     * @return The type token for the list's element type.
     */
    @SuppressWarnings("unchecked")
    public static <T> TypeToken<T> listElementType(TypeToken<? extends List<T>> token) {
        Type t = token.getType();
        Preconditions.checkArgument(t instanceof ParameterizedType, "list type not resolved");
        ParameterizedType pt = (ParameterizedType) t;
        Type[] args = pt.getActualTypeArguments();
        assert args.length == 1;
        return (TypeToken<T>) TypeToken.of(args[0]);
    }

    /**
     * Resolve a type name into a type.  This is like class lookup with a few additions:
     *
     * - Aliases for common types (`string`, `int`, `long`, `double`)
     * - Lists are handled with an array syntax (`string[]` becomes {@code List<String>})
     *
     * @param type The type name to resolve.
     * @return The type.
     */
    public static TypeToken<?> resolveTypeName(String type) {
        Preconditions.checkArgument(type.length() > 0, "type name is empty");
        if (type.endsWith("[]")) {
            String nt = type.substring(0, type.length() - 2);
            TypeToken<?> inner = resolveTypeName(nt);
            return makeListType(inner);
        }
        switch (type) {
        case "string":
        case "String":
            return TypeToken.of(String.class);
        case "int":
        case "Integer":
            return TypeToken.of(Integer.class);
        case "long":
        case "Long":
            return TypeToken.of(Long.class);
        case "double":
        case "real":
        case "Double":
            return TypeToken.of(Double.class);
        case "text":
        case "Text":
            return TypeToken.of(Text.class);
        default:
            try {
                return TypeToken.of(ClassUtils.getClass(type));
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Cannot load type name ", e);
            }
        }
    }

    /**
     * Turn a type token into a parsable type name.
     * @param type The type token.
     * @param <T> The type.
     * @return A string that can be parsed by {@link #resolveTypeName(String)}.
     */
    @SuppressWarnings("unchecked")
    public static <T> String makeTypeName(TypeToken<T> type) {
        Class<?> raw = type.getRawType();
        if (raw.equals(List.class)) {
            return makeTypeName(listElementType((TypeToken) type)) + "[]";
        } else if (raw.equals(String.class)) {
            return "string";
        } else if (raw.equals(Double.class)) {
            return "double";
        } else if (raw.equals(Integer.class)) {
            return "int";
        } else if (raw.equals(Long.class)) {
            return "long";
        } else {
            return raw.getName();
        }
    }

    /**
     * Look up a converter to convert strings to the specified type.  List types are converted from comma-separated
     * values.
     *
     * @param type The type.
     * @param <T> The type.
     * @return A converter to parse objects of type {@code type} from strings.
     */
    @SuppressWarnings("unchecked")
    public static <T> FromStringConverter<T> lookupFromStringConverter(TypeToken<T> type) {
        Class<? super T> rt = type.getRawType();
        if (rt.equals(List.class)) {
            TypeToken elt = listElementType((TypeToken) type);
            FromStringConverter inner = lookupFromStringConverter(elt);
            return new ListParser(elt.getRawType(), inner);
        } else {
            return (FromStringConverter) StringConvert.INSTANCE.findConverter(rt);
        }
    }

    private static class ListParser<T> implements FromStringConverter<List<T>> {
        private final Class<T> elementType;
        private final FromStringConverter<T> elementConverter;

        public ListParser(Class<T> et, FromStringConverter<T> ec) {
            elementType = et;
            elementConverter = ec;
        }

        @Override
        public List<T> convertFromString(Class<? extends List<T>> cls, String str) {
            assert cls != null && cls.isAssignableFrom(List.class);
            List<T> list = new ArrayList<>();
            StringTokenizer tok = StringTokenizer.getCSVInstance(str);
            while (tok.hasNext()) {
                String next = tok.next();
                list.add(elementConverter.convertFromString(elementType, next));
            }

            return list;
        }
    }
}