org.immutables.value.processor.encode.Instantiation.java Source code

Java tutorial

Introduction

Here is the source code for org.immutables.value.processor.encode.Instantiation.java

Source

/*
   Copyright 2016 Immutables Authors and Contributors
    
   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 org.immutables.value.processor.encode;

import com.google.common.base.Predicate;
import org.immutables.value.processor.meta.ValueType;
import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.immutables.generator.Templates;
import org.immutables.generator.Templates.Invokable;
import org.immutables.generator.Templates.Invokation;
import org.immutables.value.processor.encode.Code.Binding;
import org.immutables.value.processor.encode.Code.Term;
import org.immutables.value.processor.encode.EncodedElement.Param;
import org.immutables.value.processor.encode.EncodedElement.TypeParam;
import org.immutables.value.processor.encode.Type.Defined;
import org.immutables.value.processor.encode.Type.Parameters;
import org.immutables.value.processor.encode.Type.Variable;
import org.immutables.value.processor.encode.Type.VariableResolver;
import org.immutables.value.processor.meta.Styles;
import org.immutables.value.processor.meta.Styles.UsingName.AttributeNames;

public final class Instantiation {
    private static final String BUILDER_RESERVED_IN_CONSTRUCTOR = "builder";
    private final Map<Binding, String> bindings;
    private final Map<Binding, String> builderBindings;

    final Type type;
    final EncodingInfo encoding;
    final EncodedElement expose;

    // these exist as functions that can be applied from a template
    final VariableResolver typer;
    final Function<EncodedElement, String> namer;

    private final AttributeNames names;
    private final ValueType containingType;

    Instantiation(EncodingInfo encoding, EncodedElement expose, Type exposedType,
            Styles.UsingName.AttributeNames names, VariableResolver resolver, ValueType containingType) {
        this.encoding = encoding;
        this.expose = expose;
        this.type = exposedType;
        this.names = names;
        this.typer = resolver;
        this.containingType = containingType;

        this.bindings = new HashMap<>(encoding.element().size());
        this.builderBindings = new HashMap<>(encoding.element().size());

        populateBindings(resolver);

        this.namer = new Function<EncodedElement, String>() {
            @Override
            public String apply(EncodedElement input) {
                return input.inBuilder() ? builderBindings.get(input.asBinding()) : bindings.get(input.asBinding());
            }
        };
    }

    private void populateBindings(VariableResolver resolver) {
        for (EncodedElement e : encoding.element()) {
            if (e.isStatic()) {
                if (e.inBuilder()) {
                    builderBindings.put(e.asBinding(), generateProperName(e));
                } else {
                    // statics from value are visible in builder
                    builderBindings.put(e.asBinding(), generateProperName(e));
                    bindings.put(e.asBinding(), generateProperName(e));
                }
            }
        }

        for (EncodedElement e : encoding.element()) {
            if (!e.isStatic()) {
                if (e.inBuilder()) {
                    builderBindings.put(e.asBinding(), generateProperName(e));
                } else {
                    bindings.put(e.asBinding(), generateProperName(e));
                }
            }
        }

        for (Variable v : resolver.variables()) {
            Binding binding = Binding.newTop(v.name);
            String value = resolver.apply(v).toString();
            bindings.put(binding, value);
            builderBindings.put(binding, value);
        }
    }

    public boolean isVirtualNotOfSameType() {
        return hasVirtualImpl() && (!encoding.from().firstParam().type().equals(encoding.impl().type())
                || names.var.equals(BUILDER_RESERVED_IN_CONSTRUCTOR));
    }

    public boolean isTrivialOf() {
        return isInlined(encoding.from()) && encoding.from().oneLiner()
                .equals(ImmutableList.of(Binding.newTop(encoding.from().firstParam().name())));
    }

    public String getDecoratedImplFieldName() {
        return bindings.get(encoding.impl().asBinding()) + "$impl";
    }

    public boolean hasVirtualImpl() {
        return encoding.impl().isVirtual();
    }

    public boolean supportsInternalImplConstructor() {
        return encoding.build().type().equals(encoding.impl().type());
    }

    public boolean supportsDefaultValue() {
        return !encoding.impl().code().isEmpty();
    }

    private String generateProperName(EncodedElement element) {
        if (element.isImplField()) {
            return names.var;
        }

        if (element.isExpose()) {
            return names.get;
        }

        if (element.standardNaming() != StandardNaming.NONE) {
            switch (element.standardNaming()) {
            case GET:
                return names.get;
            case INIT:
                return names.init;
            case ADD:
                return names.add();
            case ADD_ALL:
                return names.addAll();
            case PUT:
                return names.put();
            case PUT_ALL:
                return names.putAll();
            case WITH:
                return names.with;
            default:
            }
        }

        if (isDefaultUnspecifiedValue(element)) {
            if (element.isCopy()) {
                return names.with;
            }
            if (element.isInit()) {
                return names.init;
            }
        }

        if (element.isStaticField() && element.isFinal()) {
            String base = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, rawName());
            return element.naming().apply(base);
        }

        return names.apply(element.naming(), element.depluralize());
    }

    private String rawName() {
        return names.raw;
    }

    final Predicate<EncodedElement> isInlined = new Predicate<EncodedElement>() {
        @Override
        public boolean apply(EncodedElement input) {
            return isInlined(input);
        }
    };

    private boolean isInlined(EncodedElement el) {
        return !el.oneLiner().isEmpty() && !encoding.crossReferencedMethods().contains(el.name())
                && !entangledBuildMethod(el);
    }

    private boolean entangledBuildMethod(EncodedElement el) {
        return el.isBuild() && containingType.isGenerateBuilderConstructor();
    }

    private boolean isDefaultUnspecifiedValue(EncodedElement element) {
        return element.naming().isIdentity() && !element.depluralize();
    }

    final Templates.Invokable fragmentOf = new Templates.Invokable() {
        @Override
        public @Nullable Invokable invoke(Invokation invokation, Object... parameters) {
            final EncodedElement el = (EncodedElement) parameters[0];

            @Nullable
            Map<Binding, String> overrideBindings = null;
            if (el.params().size() == 1 && parameters.length > 1) {
                overrideBindings = ImmutableMap.of(Binding.newTop(el.firstParam().name()),
                        parameters[1].toString());
            }

            Map<Binding, String> contextBindings = el.inBuilder() ? builderBindings : bindings;
            Code.Interpolator interpolator = new Code.Interpolator(rawName(), contextBindings, overrideBindings);

            if (isInlined(el)) {
                printWithIndentation(invokation, interpolator.apply(el.oneLiner()));
            } else {
                invokation.out(contextBindings.get(el.asBinding())).out("(");
                boolean notFirst = false;
                for (Param p : el.params()) {
                    if (notFirst) {
                        invokation.out(", ");
                    }
                    notFirst = true;
                    Binding binding = Code.Binding.newTop(p.name());
                    invokation.out(interpolator.dereference(binding));
                }
                invokation.out(")");
            }
            return null;
        }

    };

    final Templates.Invokable codeOf = new Templates.Invokable() {
        @Override
        public @Nullable Invokable invoke(Invokation invokation, Object... parameters) {
            EncodedElement el = (EncodedElement) parameters[0];

            Map<Binding, String> contextBindings = el.inBuilder() ? builderBindings : bindings;

            List<Term> code = el.code();

            if (parameters.length >= 2) {
                String param = parameters[1].toString();
                code = Code.replaceReturn(code, param);
            }

            Code.Interpolator interpolator = new Code.Interpolator(rawName(), contextBindings, null);

            printWithIndentation(invokation, interpolator.apply(code));
            return null;
        }
    };

    // for aux fields we can override impl field binding
    final Templates.Invokable codeOfAuxFieldVirtualDecorated = new Templates.Invokable() {
        @Override
        public @Nullable Invokable invoke(Invokation invokation, Object... parameters) {
            EncodedElement el = (EncodedElement) parameters[0];

            Map<Binding, String> contextBindings = el.inBuilder() ? builderBindings : bindings;
            Map<Binding, String> overrideBindings = ImmutableMap.of(Binding.newField(encoding.impl().name()),
                    getDecoratedImplFieldName());

            Code.Interpolator interpolator = new Code.Interpolator(rawName(), contextBindings, overrideBindings);

            printWithIndentation(invokation, interpolator.apply(el.code()));
            return null;
        }
    };

    private static void printWithIndentation(Invokation invokation, List<Term> terms) {
        int indentLevel = 0;
        int indentWrap = 0;
        boolean nextNewline = false;

        for (Code.Term t : terms) {
            if (t.isWhitespace() && t.is('\n')) {
                nextNewline = true;
                continue;
            }

            // decrease indent level before writing a newline
            if (t.isDelimiter() && t.is('}')) {
                indentLevel--;
            }

            if (nextNewline) {
                nextNewline = false;
                invokation.ln();

                for (int i = 0; i < indentLevel + indentWrap; i++) {
                    invokation.out("  ");
                }
            }

            if (t.isDelimiter() && (t.is(';') || t.is('}') || t.is('{'))) {
                indentWrap = 0;
            } else if (!t.isIgnorable()) {
                // auto-increase indent wrap unless semicolon will return it back
                indentWrap = 2;
            }

            // increase indent level after writing a newline
            if (t.isDelimiter() && t.is('{')) {
                indentLevel++;
            }

            // outputing actual token after any indents
            invokation.out(t);
        }
    }

    final Function<EncodedElement, String> ownTypeParams = new Function<EncodedElement, String>() {
        @Override
        public String apply(EncodedElement input) {
            Parameters parameters = Type.Producer.emptyParameters();

            // if our method have the same named type parameters as
            // encoding, when instantiating it for specific
            // attribute, some may resolve to concrete types, some
            // may end up value-type specific type parameter
            // we need to write only type-specific type parameters omiting those
            // which resolves to specific type.
            // note that some methods are to be inlined, so this is not needed
            // then, it is only needed when non-inlined references are present.

            if (input.isFrom()) {
                // from has implied type parameters, the same as encoding
                for (Variable v : typer.variables()) {
                    parameters = introduceAsEncodingVar(parameters, v);
                }
            } else {
                for (TypeParam p : input.typeParams()) {
                    @Nullable
                    Variable encodingVar = typer.byName(p.name());
                    if (encodingVar != null) {
                        parameters = introduceAsEncodingVar(parameters, encodingVar);
                    } else {
                        parameters = parameters.introduce(p.name(), transformBounds(p.bounds()));
                    }
                }
            }

            if (parameters.names().isEmpty()) {
                return "";
            }

            return parameters + " ";
        }

        private Parameters introduceAsEncodingVar(Parameters parameters, Variable encodingVar) {
            Type t = typer.apply(encodingVar);
            final Parameters[] pHolder = new Parameters[] { parameters };
            t.accept(new Type.Transformer() {
                @Override
                public Type variable(Variable v) {
                    pHolder[0] = pHolder[0].introduce(v.name, transformBounds(v.upperBounds));
                    return v;
                }
            });
            parameters = pHolder[0];
            return parameters;
        }

        private ImmutableList<Defined> transformBounds(List<Defined> bounds) {
            return FluentIterable.from(bounds).transform(typer).filter(Defined.class).toList();
        }
    };

    @Override
    public String toString() {
        return type + "(by " + encoding.name() + ")";
    }
}