org.grouplens.lenskit.data.text.Fields.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.lenskit.data.text.Fields.java

Source

/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2014 LensKit Contributors.  See CONTRIBUTORS.md.
 * Work on LensKit has been funded by the National Science Foundation under
 * grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.lenskit.data.text;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.convert.StringConvert;
import org.joda.convert.StringConverter;
import org.lenskit.data.events.EventBuilder;
import org.lenskit.data.events.LikeBatchBuilder;
import org.lenskit.data.ratings.RatingBuilder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

@SuppressWarnings("rawtypes")
public final class Fields {
    private Fields() {
    }

    /**
     * The user ID field.
     * @return A field definition for the user ID.
     */
    public static Field user() {
        return CommonFields.USER;
    }

    /**
     * The item ID field.
     * @return A field definition for the item ID.
     */
    public static Field item() {
        return CommonFields.ITEM;
    }

    /**
     * A required timestamp field.
     * @return A field definition for a required timestamp field.
     */
    public static Field timestamp() {
        return timestamp(true);
    }

    /**
     * Get a common field by name.  Common fields are 'user', 'item', and 'timestamp'.
     * @return The field, or `null` if `name` is an unknown field.
     */
    public static Field commonField(String name) {
        if (name == null) {
            return ignored();
        }
        switch (name) {
        case "user":
            return user();
        case "item":
            return item();
        case "timestamp":
            return timestamp();
        case "timestamp?":
            return timestamp(false);
        default:
            return null;
        }
    }

    /**
     * Get a field by name.  It first looks up the common fields, and if none of them apply, creates
     * a reflection-based field.
     *
     * @param eb The event builder.
     * @param name The field name.  The name can be suffixed with "?" to make it optional.
     * @return The field, or `null` if no such field can be defined.
     */
    public static Field byName(Class<? extends EventBuilder> eb, String name) {
        Field field = commonField(name);
        if (field != null) {
            return field;
        }

        boolean optional = false;
        if (name.endsWith("?")) {
            optional = true;
            name = name.substring(0, name.length() - 1);
        }

        String setterName = "set" + StringUtils.capitalize(name);
        Method setter = null;
        Method annotated = null;
        for (Method m : eb.getMethods()) {
            FieldName nameAnnot = m.getAnnotation(FieldName.class);
            if (nameAnnot != null) {
                if (nameAnnot.value().equals(name)) {
                    annotated = m;
                }
            } else if (m.getName().equals(setterName) && !m.isBridge()) {
                if (setter == null) {
                    setter = m;
                } else {
                    throw new IllegalArgumentException("Multiple methods named " + setterName);
                }
            }
        }
        if (annotated != null) {
            setter = annotated;
        }

        if (setter == null) {
            throw new IllegalArgumentException("No method found for field " + name);
        }
        Class<?>[] atypes = setter.getParameterTypes();
        if (atypes.length != 1) {
            throw new IllegalArgumentException("Method " + setter.getName() + " takes too many arguments");
        }
        final Method theSetter = setter;
        Class<?> atype = atypes[0];
        if (atype.isPrimitive()) {
            atype = ClassUtils.primitiveToWrapper(atype);
        }
        StringConverter<Object> convert = StringConvert.INSTANCE.findConverterNoGenerics(atype);
        if (convert == null) {
            throw new IllegalArgumentException("Field type " + atypes[0] + " not allowed.");
        }
        return new ReflectionField(name, theSetter, eb, atype, convert, optional);
    }

    /**
     * A field that is ignored.
     * @return A field definition for a field to ignore.
     */
    public static Field ignored() {
        return CommonFields.IGNORED;
    }

    /**
     * A field that is ignored and may be optional.
     * @param optional {@code true} if the field is optional (it may or may not appear).
     * @return A field definition for a field to ignore.
     */
    public static Field ignored(boolean optional) {
        if (optional) {
            return CommonFields.OPTIONAL_IGNORE;
        } else {
            return CommonFields.IGNORED;
        }
    }

    public static Field rating() {
        return ValueFields.RATING;
    }

    /**
     * Create a list of fields.  This helper is structured to aid in type inference.
     *
     * @param fields The fields to put in the list.
     * @return The field list.
     */
    public static List<Field> list(Field... fields) {
        return ImmutableList.copyOf(fields);
    }

    /**
     * A timestamp field.
     * @param required {@code true} if the timestamp is required, {@code false} if it is optional.
     * @return A field definition for a required timestamp field.
     */
    public static Field timestamp(boolean required) {
        if (required) {
            return CommonFields.TIMESTAMP;
        } else {
            return CommonFields.OPTIONAL_TIMESTAMP;
        }
    }

    /**
     * A plus count field.
     * @return A field definition for a required plus count field.
     */
    public static Field likeCount() {
        return ValueFields.LIKE_COUNT;
    }

    private static enum CommonFields implements Field {
        IGNORED(null) {
            @Override
            public void apply(String token, EventBuilder builder) {
                /* do nothing */
            }
        },
        OPTIONAL_IGNORE(null) {
            @Override
            public boolean isOptional() {
                return true;
            }

            @Override
            public void apply(String token, EventBuilder builder) {
                /* do nothing */
            }
        },
        USER("user") {
            @Override
            public void apply(String token, EventBuilder builder) {
                builder.setUserId(Long.parseLong(token));
            }
        },

        ITEM("item") {
            @Override
            public void apply(String token, EventBuilder builder) {
                builder.setItemId(Long.parseLong(token));
            }
        },
        TIMESTAMP("timestamp") {
            @Override
            public void apply(String token, EventBuilder builder) {
                builder.setTimestamp(Long.parseLong(token));
            }
        },
        OPTIONAL_TIMESTAMP("timestamp?") {
            @Override
            public void apply(String token, EventBuilder builder) {
                if (token == null) {
                    builder.setTimestamp(-1);
                } else {
                    builder.setTimestamp(Long.parseLong(token));
                }
            }

            @Override
            public boolean isOptional() {
                return true;
            }
        };

        private final String name;

        CommonFields(String name) {
            this.name = name;
        }

        @Override
        public Class<? extends EventBuilder> getBuilderType() {
            return EventBuilder.class;
        }

        @Override
        public boolean isOptional() {
            return false;
        }

        @Override
        public String getName() {
            return name;
        }
    }

    private static enum ValueFields implements Field {
        RATING(RatingBuilder.class, "rating") {
            @Override
            public void apply(String token, EventBuilder builder) {
                RatingBuilder rb = (RatingBuilder) builder;
                if (token == null || token.equals("")) {
                    rb.clearRating();
                } else {
                    double v = Double.parseDouble(token);
                    if (Double.isNaN(v)) {
                        rb.clearRating();
                    } else {
                        rb.setRating(v);
                    }
                }
            }
        },

        LIKE_COUNT(LikeBatchBuilder.class, "count") {
            @Override
            public void apply(String token, EventBuilder builder) {
                ((LikeBatchBuilder) builder).setCount(Integer.parseInt(token));
            }
        };

        private final Class<? extends EventBuilder> builderType;
        private final String name;

        ValueFields(Class<? extends EventBuilder> bt, String name) {
            builderType = bt;
            this.name = name;
        }

        @Override
        public Class<? extends EventBuilder> getBuilderType() {
            return builderType;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean isOptional() {
            return false;
        }
    }

    private static class ReflectionField implements Field {
        private final String fieldName;
        private final Method setter;
        private final Class<? extends EventBuilder> builderType;
        private final Class<?> argType;
        private final StringConverter<Object> converter;
        private final boolean optional;

        public ReflectionField(String name, Method theSetter, Class<? extends EventBuilder> btype, Class<?> atype,
                StringConverter<Object> convert, boolean optional) {
            fieldName = name;
            this.setter = theSetter;
            builderType = btype;
            this.argType = atype;
            this.converter = convert;
            this.optional = optional;
        }

        @Override
        public Class<? extends EventBuilder> getBuilderType() {
            return builderType;
        }

        @Override
        public boolean isOptional() {
            return optional;
        }

        @Override
        public String getName() {
            return fieldName;
        }

        @Override
        public void apply(String token, EventBuilder builder) {
            if (token == null) {
                if (!optional) {
                    throw new IllegalArgumentException("null string provided for optional value");
                }
            } else {
                try {
                    setter.invoke(builder, converter.convertFromString(argType, token));
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw Throwables.propagate(e);
                }
            }
        }
    }
}