paperparcel.PaperParcelDescriptor.java Source code

Java tutorial

Introduction

Here is the source code for paperparcel.PaperParcelDescriptor.java

Source

/*
 * Copyright (C) 2016 Bradley Campbell.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package paperparcel;

import com.google.auto.common.MoreElements;
import com.google.auto.common.Visibility;
import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.UnknownTypeException;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/** Represents a {@link PaperParcel} annotated object */
@AutoValue
abstract class PaperParcelDescriptor {
    private static final String GET_PREFIX = "get";
    private static final String IS_PREFIX = "is";
    private static final String HAS_PREFIX = "has";
    private static final String SET_PREFIX = "set";

    /** The original {@link TypeElement} that this class is describing */
    abstract TypeElement element();

    /** All fields that should be passed to the constructor, in order */
    abstract ImmutableList<FieldDescriptor> constructorFields();

    /** Returns true if the constructor is non-private */
    abstract boolean isConstructorVisible();

    /** The fields that should be written directly, or an empty list if none */
    abstract ImmutableList<FieldDescriptor> writableFields();

    /**
     * The fields that should be written via setter methods, mapped to the setter method itself.
     * Returns an empty map if there are no fields to be set via setter methods.
     */
    abstract ImmutableMap<FieldDescriptor, ExecutableElement> setterMethodMap();

    /** A list of all of the non-private fields, or an empty list if none */
    abstract ImmutableList<FieldDescriptor> readableFields();

    /**
     * The fields that should be read from getter methods, mapped to the getter method itself.
     * Returns an empty map if there are no fields to be read from getter methods.
     */
    abstract ImmutableMap<FieldDescriptor, ExecutableElement> getterMethodMap();

    /**
     * Returns all of the adapters required for each field in the annotated class, indexed by the
     * field they are required for
     */
    abstract ImmutableMap<FieldDescriptor, AdapterDescriptor> adapters();

    /**
     * Returns true if this class is a singleton. Singletons are defined as per
     * {@link Utils#isSingleton(Types, TypeElement)}
     */
    abstract boolean isSingleton();

    private static ImmutableSet<String> possibleGetterNames(String name) {
        ImmutableSet.Builder<String> possibleGetterNames = new ImmutableSet.Builder<>();
        possibleGetterNames.add(name);
        possibleGetterNames.add(IS_PREFIX + Strings.capitalizeAsciiOnly(name));
        possibleGetterNames.add(HAS_PREFIX + Strings.capitalizeAsciiOnly(name));
        possibleGetterNames.add(GET_PREFIX + Strings.capitalizeAsciiOnly(name));
        possibleGetterNames.add(GET_PREFIX + Strings.capitalizeFirstWordAsciiOnly(name));
        return possibleGetterNames.build();
    }

    private static ImmutableSet<String> possibleSetterNames(String name) {
        ImmutableSet.Builder<String> possibleSetterNames = new ImmutableSet.Builder<>();
        if (startsWithPrefix(IS_PREFIX, name)) {
            possibleSetterNames.add(SET_PREFIX + name.substring(IS_PREFIX.length()));
        }
        if (startsWithPrefix(HAS_PREFIX, name)) {
            possibleSetterNames.add(SET_PREFIX + name.substring(HAS_PREFIX.length()));
        }
        possibleSetterNames.add(name);
        possibleSetterNames.add(SET_PREFIX + Strings.capitalizeAsciiOnly(name));
        possibleSetterNames.add(SET_PREFIX + Strings.capitalizeFirstWordAsciiOnly(name));
        return possibleSetterNames.build();
    }

    private static boolean startsWithPrefix(String prefix, String name) {
        if (!name.startsWith(prefix))
            return false;
        if (name.length() == prefix.length())
            return false;
        char c = name.charAt(prefix.length());
        return !('a' <= c && c <= 'z');
    }

    @AutoValue
    static abstract class NonReadableFieldsException extends Exception {

        /** A list containing all of the non-readable fields. */
        abstract ImmutableList<VariableElement> nonReadableFields();

        static NonReadableFieldsException create(ImmutableList<VariableElement> nonReadableFields) {
            return new AutoValue_PaperParcelDescriptor_NonReadableFieldsException(nonReadableFields);
        }
    }

    @AutoValue
    static abstract class NonWritableFieldsException extends Exception {

        /**
         * Contains all of the valid constructor elements, mapped to all of the fields that couldn't be
         * written when using that constructor.
         *
         * Valid constructors are found if all parameters had a corresponding field (same name and
         * the types are assignable). Private constructors are not included.
         *
         * If there were no valid constructors, this returns an empty map.
         */
        abstract ImmutableMap<ExecutableElement, ImmutableList<VariableElement>> allNonWritableFieldsMap();

        /**
         * Contains all of the invalid constructor elements, mapped to the list of unassignable fields
         * found when parsing that constructor.
         *
         * Invalid constructors are found if not all parameters had a corresponding field (same name
         * and the types are assignable). Private constructors are not included.
         *
         * If there were no invalid constructors, this returns an empty map.
         */
        abstract ImmutableMap<ExecutableElement, ImmutableList<VariableElement>> unassignableConstructorParameterMap();

        static NonWritableFieldsException create(
                ImmutableMap<ExecutableElement, ImmutableList<VariableElement>> allNonWritableFieldsMap,
                ImmutableMap<ExecutableElement, ImmutableList<VariableElement>> unassignableConstructorParameterMap) {
            return new AutoValue_PaperParcelDescriptor_NonWritableFieldsException(allNonWritableFieldsMap,
                    unassignableConstructorParameterMap);
        }
    }

    static final class Factory {
        private final Elements elements;
        private final Types types;
        private final AdapterDescriptor.Factory adapterFactory;
        private final FieldDescriptor.Factory fieldDescriptorFactory;

        Factory(Elements elements, Types types, AdapterDescriptor.Factory adapterFactory,
                FieldDescriptor.Factory fieldDescriptorFactory) {
            this.elements = elements;
            this.types = types;
            this.adapterFactory = adapterFactory;
            this.fieldDescriptorFactory = fieldDescriptorFactory;
        }

        PaperParcelDescriptor create(TypeElement element, OptionsDescriptor options)
                throws NonWritableFieldsException, NonReadableFieldsException {

            ImmutableList<VariableElement> fields = Utils.getFieldsToParcel(element, options);
            //noinspection deprecation: Support for kapt2
            ImmutableSet<ExecutableElement> methods = MoreElements.getLocalAndInheritedMethods(element, elements);
            ImmutableList<ExecutableElement> constructors = Utils.orderedConstructorsIn(element,
                    options.reflectAnnotations());

            ImmutableList<FieldDescriptor> constructorFields;
            boolean isConstructorVisible;
            ImmutableList<FieldDescriptor> writableFields;
            ImmutableMap<FieldDescriptor, ExecutableElement> setterMethodMap;
            ImmutableList<FieldDescriptor> readableFields;
            ImmutableMap<FieldDescriptor, ExecutableElement> getterMethodMap;
            ImmutableMap<FieldDescriptor, AdapterDescriptor> adapters;

            boolean singleton = Utils.isSingleton(types, element);
            if (!singleton) {
                WriteInfo writeInfo = WriteInfo.create(types, fieldDescriptorFactory, fields, methods, constructors,
                        options.reflectAnnotations());
                constructorFields = writeInfo.constructorFields();
                isConstructorVisible = writeInfo.isConstructorVisible();
                writableFields = writeInfo.writableFields();
                setterMethodMap = writeInfo.setterMethodMap();
                ReadInfo readInfo = ReadInfo.create(types, fieldDescriptorFactory, fields, methods,
                        options.reflectAnnotations());
                readableFields = readInfo.readableFields();
                getterMethodMap = readInfo.getterMethodMap();
                adapters = getAdapterMap(readInfo, options.allowSerializable());

            } else {
                constructorFields = ImmutableList.of();
                isConstructorVisible = false;
                writableFields = ImmutableList.of();
                setterMethodMap = ImmutableMap.of();
                readableFields = ImmutableList.of();
                getterMethodMap = ImmutableMap.of();
                adapters = ImmutableMap.of();
            }

            return new AutoValue_PaperParcelDescriptor(element, constructorFields, isConstructorVisible,
                    writableFields, setterMethodMap, readableFields, getterMethodMap, adapters, singleton);
        }

        private ImmutableMap<FieldDescriptor, AdapterDescriptor> getAdapterMap(ReadInfo readInfo,
                boolean allowSerializable) {
            ImmutableMap.Builder<FieldDescriptor, AdapterDescriptor> fieldAdapterMap = ImmutableMap.builder();
            if (readInfo != null) {
                for (FieldDescriptor field : readInfo.readableFields()) {
                    addAdapterForField(fieldAdapterMap, field, allowSerializable);
                }
                for (FieldDescriptor field : readInfo.getterMethodMap().keySet()) {
                    addAdapterForField(fieldAdapterMap, field, allowSerializable);
                }
            }
            return fieldAdapterMap.build();
        }

        private void addAdapterForField(ImmutableMap.Builder<FieldDescriptor, AdapterDescriptor> fieldAdapterMap,
                FieldDescriptor field, boolean allowSerializable) {
            TypeMirror fieldType = field.type().get();
            //noinspection ConstantConditions
            if (!fieldType.getKind().isPrimitive()) {
                AdapterDescriptor adapter = adapterFactory.create(fieldType, allowSerializable);
                if (adapter != null) {
                    fieldAdapterMap.put(field, adapter);
                } else {
                    throw new UnknownTypeException(fieldType, field.element());
                }
            }
        }
    }

    private static class WriteInfo {
        private final ImmutableList<FieldDescriptor> constructorFields;
        private final boolean isConstructorVisible;
        private final ImmutableList<FieldDescriptor> writableFields;
        private final ImmutableMap<FieldDescriptor, ExecutableElement> setterMethodMap;

        WriteInfo(ImmutableList<FieldDescriptor> constructorFields, boolean isConstructorVisible,
                ImmutableList<FieldDescriptor> writableFields,
                ImmutableMap<FieldDescriptor, ExecutableElement> setterMethodMap) {
            this.constructorFields = constructorFields;
            this.isConstructorVisible = isConstructorVisible;
            this.writableFields = writableFields;
            this.setterMethodMap = setterMethodMap;
        }

        ImmutableList<FieldDescriptor> constructorFields() {
            return constructorFields;
        }

        boolean isConstructorVisible() {
            return isConstructorVisible;
        }

        ImmutableList<FieldDescriptor> writableFields() {
            return writableFields;
        }

        ImmutableMap<FieldDescriptor, ExecutableElement> setterMethodMap() {
            return setterMethodMap;
        }

        static WriteInfo create(Types types, FieldDescriptor.Factory fieldDescriptorFactory,
                ImmutableList<VariableElement> fields, ImmutableSet<ExecutableElement> methods,
                ImmutableList<ExecutableElement> constructors, ImmutableList<String> reflectAnnotations)
                throws NonWritableFieldsException {

            ImmutableMap<String, VariableElement> fieldNamesToField = fieldNamesToField(fields);
            ImmutableMap.Builder<ExecutableElement, ImmutableList<VariableElement>> allNonWritableFieldsMapBuilder = ImmutableMap
                    .builder();
            ImmutableMap.Builder<ExecutableElement, ImmutableList<VariableElement>> unassignableConstructorParameterMapBuilder = ImmutableMap
                    .builder();

            for (ExecutableElement constructor : constructors) {
                // Create a mutable copy of fieldNamesToField so we can remove elements from it as we iterate
                // to keep track of which elements we have seen
                Map<String, VariableElement> nonConstructorFieldsMap = new LinkedHashMap<>(fieldNamesToField);
                ImmutableList.Builder<VariableElement> unassignableParametersBuilder = ImmutableList.builder();
                ImmutableList.Builder<FieldDescriptor> constructorFieldDescriptorsBuilder = ImmutableList.builder();
                // Iterate over parameters and check two things:
                // 1) The parameter has a field with the same name
                // 2) The parameter type is assignable to the field
                List<? extends VariableElement> parameters = constructor.getParameters();
                for (VariableElement parameter : parameters) {
                    String parameterName = parameter.getSimpleName().toString();
                    // All wildcards need to be stripped from the parameter type as a work around
                    // for kotlin data classes generating non-assignable constructor parameters
                    // with generic types.
                    TypeMirror parameterType = Utils.replaceTypeVariablesWithUpperBounds(types, parameter.asType());
                    VariableElement fieldOrNull = fieldNamesToField.get(parameterName);
                    if (fieldOrNull != null && types.isAssignable(parameterType,
                            Utils.replaceTypeVariablesWithUpperBounds(types, fieldOrNull.asType()))) {
                        nonConstructorFieldsMap.remove(parameterName);
                        constructorFieldDescriptorsBuilder.add(fieldDescriptorFactory.create(fieldOrNull));
                    } else {
                        unassignableParametersBuilder.add(parameter);
                    }
                }
                // Check if there were any unassignable parameters in the constructor. If so, skip.
                ImmutableList<VariableElement> unassignableParameters = unassignableParametersBuilder.build();
                if (unassignableParameters.size() > 0) {
                    unassignableConstructorParameterMapBuilder.put(constructor, unassignableParameters);
                    continue;
                }
                // Check that the remaining parameters are assignable directly, or via setters.
                ImmutableList.Builder<VariableElement> nonWritableFieldsBuilder = ImmutableList.builder();
                ImmutableList<VariableElement> nonConstructorFields = ImmutableList
                        .copyOf(nonConstructorFieldsMap.values());
                ImmutableList.Builder<FieldDescriptor> writableFieldsBuilder = ImmutableList.builder();
                ImmutableMap.Builder<FieldDescriptor, ExecutableElement> setterMethodMapBuilder = ImmutableMap
                        .builder();
                for (VariableElement field : nonConstructorFields) {
                    if (isWritableDirectly(field)) {
                        writableFieldsBuilder.add(fieldDescriptorFactory.create(field));
                    } else {
                        Optional<ExecutableElement> setterMethod = getSetterMethod(types, field, methods);
                        if (setterMethod.isPresent()) {
                            setterMethodMapBuilder.put(fieldDescriptorFactory.create(field), setterMethod.get());
                        } else if (Utils.usesAnyAnnotationsFrom(field, reflectAnnotations)) {
                            writableFieldsBuilder.add(fieldDescriptorFactory.create(field));
                        } else {
                            nonWritableFieldsBuilder.add(field);
                        }
                    }
                }
                ImmutableList<VariableElement> nonWritableFields = nonWritableFieldsBuilder.build();
                if (nonWritableFields.size() != 0) {
                    // Map all of the non-writable fields to the corresponding constructor for (potential)
                    // error handling later
                    allNonWritableFieldsMapBuilder.put(constructor, nonWritableFields);
                } else {
                    // All fields are writable using this constructor
                    return new WriteInfo(constructorFieldDescriptorsBuilder.build(),
                            Visibility.ofElement(constructor) != Visibility.PRIVATE, writableFieldsBuilder.build(),
                            setterMethodMapBuilder.build());
                }
            }

            // Throw an error if fields are not writable
            throw NonWritableFieldsException.create(allNonWritableFieldsMapBuilder.build(),
                    unassignableConstructorParameterMapBuilder.build());
        }

        private static Optional<ExecutableElement> getSetterMethod(Types types, VariableElement field,
                ImmutableSet<ExecutableElement> allMethods) {
            String fieldName = field.getSimpleName().toString();
            ImmutableSet<String> possibleSetterNames = possibleSetterNames(fieldName);
            TypeMirror fieldType = Utils.replaceTypeVariablesWithUpperBounds(types, field.asType());
            for (ExecutableElement method : allMethods) {
                List<? extends VariableElement> parameters = method.getParameters();
                if (parameters.size() == 1 && possibleSetterNames.contains(method.getSimpleName().toString())
                        && method.getTypeParameters().size() == 0
                        && types.isAssignable(
                                Utils.replaceTypeVariablesWithUpperBounds(types, parameters.get(0).asType()),
                                fieldType)) {
                    return Optional.of(method);
                }
            }
            return Optional.absent();
        }

        private static ImmutableMap<String, VariableElement> fieldNamesToField(
                ImmutableList<VariableElement> fields) {
            ImmutableMap.Builder<String, VariableElement> fieldNamesToField = ImmutableMap.builder();
            for (VariableElement field : fields) {
                fieldNamesToField.put(field.getSimpleName().toString(), field);
            }
            return fieldNamesToField.build();
        }

        private static boolean isWritableDirectly(VariableElement field) {
            Set<Modifier> fieldModifiers = field.getModifiers();
            return !fieldModifiers.contains(Modifier.PRIVATE) && !fieldModifiers.contains(Modifier.FINAL);
        }
    }

    private static class ReadInfo {
        private final ImmutableList<FieldDescriptor> readableFields;
        private final ImmutableMap<FieldDescriptor, ExecutableElement> getterMethodMap;

        private ReadInfo(ImmutableList<FieldDescriptor> readableFields,
                ImmutableMap<FieldDescriptor, ExecutableElement> getterMethodMap) {
            this.readableFields = readableFields;
            this.getterMethodMap = getterMethodMap;
        }

        ImmutableList<FieldDescriptor> readableFields() {
            return readableFields;
        }

        ImmutableMap<FieldDescriptor, ExecutableElement> getterMethodMap() {
            return getterMethodMap;
        }

        static ReadInfo create(Types types, FieldDescriptor.Factory fieldDescriptorFactory,
                ImmutableList<VariableElement> fields, ImmutableSet<ExecutableElement> methods,
                ImmutableList<String> reflectAnnotations) throws NonReadableFieldsException {

            ImmutableList.Builder<FieldDescriptor> readableFieldsBuilder = ImmutableList.builder();
            ImmutableMap.Builder<FieldDescriptor, ExecutableElement> getterMethodMapBuilder = ImmutableMap
                    .builder();
            ImmutableList.Builder<VariableElement> nonReadableFieldsBuilder = ImmutableList.builder();

            for (VariableElement field : fields) {
                if (isReadableDirectly(field)) {
                    readableFieldsBuilder.add(fieldDescriptorFactory.create(field));
                } else {
                    Optional<ExecutableElement> accessorMethod = getAccessorMethod(types, field, methods);
                    if (accessorMethod.isPresent()) {
                        getterMethodMapBuilder.put(fieldDescriptorFactory.create(field), accessorMethod.get());
                    } else if (Utils.usesAnyAnnotationsFrom(field, reflectAnnotations)) {
                        readableFieldsBuilder.add(fieldDescriptorFactory.create(field));
                    } else {
                        nonReadableFieldsBuilder.add(field);
                    }
                }
            }

            ImmutableList<VariableElement> nonReadableFields = nonReadableFieldsBuilder.build();
            if (nonReadableFields.size() > 0) {
                throw NonReadableFieldsException.create(nonReadableFields);
            }

            return new ReadInfo(readableFieldsBuilder.build(), getterMethodMapBuilder.build());
        }

        private static Optional<ExecutableElement> getAccessorMethod(Types types, VariableElement field,
                ImmutableSet<ExecutableElement> methods) {
            String fieldName = field.getSimpleName().toString();
            ImmutableSet<String> possibleGetterNames = possibleGetterNames(fieldName);
            TypeMirror fieldType = field.asType();
            for (ExecutableElement method : methods) {
                if (method.getParameters().size() == 0
                        && possibleGetterNames.contains(method.getSimpleName().toString())
                        && method.getTypeParameters().size() == 0
                        && types.isAssignable(method.getReturnType(), fieldType)) {
                    return Optional.of(method);
                }
            }
            return Optional.absent();
        }

        private static boolean isReadableDirectly(VariableElement field) {
            Set<Modifier> fieldModifiers = field.getModifiers();
            return !fieldModifiers.contains(Modifier.PRIVATE);
        }
    }
}