com.google.devtools.j2objc.util.CaptureInfo.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.j2objc.util.CaptureInfo.java

Source

/*
 * 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 com.google.devtools.j2objc.util;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.devtools.j2objc.types.GeneratedVariableElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;

/**
 * CaptureInfo encapsulates all the implicitly captured fields and constructor params of inner and
 * local classes.
 */
public class CaptureInfo {

    // The implicit outer reference from a non-static inner class to its outer class.
    private final Map<TypeElement, Capture> outerCaptures = new HashMap<>();

    // The captured result of the receiver expression of a method reference. For example:
    // Supplier<String> s = foo::toString;
    // In this code, the expression "foo" must be captured by the generated lambda type.
    private final Map<TypeElement, Capture> receiverCaptures = new HashMap<>();

    // Captures for local variables that are referenced from within the local class or lambda.
    private final ListMultimap<TypeElement, LocalCapture> localCaptures = MultimapBuilder.hashKeys()
            .arrayListValues().build();

    private final List<VariableElement> implicitEnumParams;

    private final TypeUtil typeUtil;

    public CaptureInfo(TypeUtil typeUtil) {
        implicitEnumParams = ImmutableList.of(
                GeneratedVariableElement.newParameter("__name", typeUtil.getJavaString().asType(), null),
                GeneratedVariableElement.newParameter("__ordinal", typeUtil.getInt(), null));
        this.typeUtil = typeUtil;
    }

    /**
     * Contains the construction parameter and field associated with a captured value.
     */
    public static class Capture {

        protected final VariableElement param;
        protected VariableElement field;

        private Capture(VariableElement param) {
            this.param = param;
        }

        public VariableElement getParam() {
            return param;
        }

        public boolean hasField() {
            return field != null;
        }

        public VariableElement getField() {
            return field;
        }
    }

    private static class LocalCapture extends Capture {

        private final VariableElement var;

        private LocalCapture(VariableElement var, VariableElement param) {
            super(param);
            this.var = var;
        }
    }

    public boolean needsOuterReference(TypeElement type) {
        return getOuterField(type) != null;
    }

    public boolean needsOuterParam(TypeElement type) {
        return outerCaptures.containsKey(type) || automaticOuterParam(type);
    }

    private Capture getOuterCapture(TypeElement type) {
        return automaticOuterParam(type) ? getOrCreateOuterCapture(type) : outerCaptures.get(type);
    }

    public VariableElement getOuterParam(TypeElement type) {
        Capture outerCapture = getOuterCapture(type);
        return outerCapture != null ? outerCapture.param : null;
    }

    public VariableElement getOuterField(TypeElement type) {
        Capture outerCapture = outerCaptures.get(type);
        return outerCapture != null ? outerCapture.field : null;
    }

    public VariableElement getReceiverField(TypeElement type) {
        Capture capture = receiverCaptures.get(type);
        return capture != null ? capture.field : null;
    }

    private <T> void maybeAdd(List<T> list, T elem) {
        if (elem != null) {
            list.add(elem);
        }
    }

    public List<Capture> getCaptures(TypeElement type) {
        List<Capture> captures = new ArrayList<>();
        maybeAdd(captures, getOuterCapture(type));
        maybeAdd(captures, receiverCaptures.get(type));
        captures.addAll(localCaptures.get(type));
        return captures;
    }

    public Iterable<VariableElement> getCaptureFields(TypeElement type) {
        return Iterables.transform(Iterables.filter(getCaptures(type), Capture::hasField), Capture::getField);
    }

    public Iterable<VariableElement> getCapturedVars(TypeElement type) {
        return Iterables.transform(localCaptures.get(type), capture -> capture.var);
    }

    public Iterable<VariableElement> getLocalCaptureFields(TypeElement type) {
        List<LocalCapture> captures = localCaptures.get(type);
        return captures == null ? Collections.emptyList()
                : Iterables.transform(Iterables.filter(captures, Capture::hasField), Capture::getField);
    }

    public List<VariableElement> getImplicitEnumParams() {
        return implicitEnumParams;
    }

    /**
     * Returns all the implicit params that come before explicit params in a constructor.
     */
    public Iterable<VariableElement> getImplicitPrefixParams(TypeElement type) {
        return Iterables.transform(getCaptures(type), Capture::getParam);
    }

    /**
     * returns all the implicit params that come after explicit params in a constructor.
     */
    public Iterable<VariableElement> getImplicitPostfixParams(TypeElement type) {
        if (ElementUtil.isEnum(type)) {
            return implicitEnumParams;
        }
        return Collections.emptyList();
    }

    public boolean isCapturing(TypeElement type) {
        return !Iterables.isEmpty(Iterables.filter(getCaptures(type), Capture::hasField));
    }

    private static boolean automaticOuterParam(TypeElement type) {
        return ElementUtil.hasOuterContext(type) && !ElementUtil.isLocal(type);
    }

    private static TypeMirror getDeclaringType(TypeElement type) {
        TypeElement declaringClass = ElementUtil.getDeclaringClass(type);
        assert declaringClass != null : "Cannot find declaring class for " + type;
        return declaringClass.asType();
    }

    private String getOuterFieldName(TypeElement type) {
        // Ensure that the new outer field does not conflict with a field in a superclass.
        TypeElement typeElement = ElementUtil.getSuperclass(type);
        int suffix = 0;
        while (typeElement != null) {
            if (ElementUtil.hasOuterContext(typeElement)) {
                suffix++;
            }
            typeElement = ElementUtil.getSuperclass(typeElement);
        }
        return "this$" + suffix;
    }

    private String getCaptureFieldName(VariableElement var, TypeElement type) {
        int suffix = 0;
        while ((type = ElementUtil.getSuperclass(type)) != null && ElementUtil.isLocal(type)) {
            suffix++;
        }
        return "val" + (suffix > 0 ? suffix : "") + "$" + var.getSimpleName().toString();
    }

    private Capture getOrCreateOuterCapture(TypeElement type) {
        Capture capture = outerCaptures.get(type);
        if (capture == null) {
            capture = new Capture(
                    GeneratedVariableElement.newParameter("outer$", getDeclaringType(type), type).setNonnull(true));
            outerCaptures.put(type, capture);
        }
        return capture;
    }

    public VariableElement getOrCreateOuterParam(TypeElement type) {
        return getOrCreateOuterCapture(type).param;
    }

    public VariableElement getOrCreateOuterField(TypeElement type) {
        // Create the outer param since it is required to initialize the field.
        Capture capture = getOrCreateOuterCapture(type);
        if (capture.field == null) {
            capture.field = GeneratedVariableElement.newField(getOuterFieldName(type), getDeclaringType(type), type)
                    .addModifiers(Modifier.PRIVATE, Modifier.FINAL).setNonnull(true)
                    .setIsWeak(typeUtil.elementUtil().isWeakOuterType(type));
        }
        return capture.field;
    }

    private LocalCapture getOrCreateLocalCapture(VariableElement var, TypeElement declaringType) {
        List<LocalCapture> capturesForType = localCaptures.get(declaringType);
        for (LocalCapture localCapture : capturesForType) {
            if (var.equals(localCapture.var)) {
                return localCapture;
            }
        }
        LocalCapture newCapture = new LocalCapture(var, GeneratedVariableElement
                .newParameter("capture$" + capturesForType.size(), var.asType(), declaringType));
        capturesForType.add(newCapture);
        return newCapture;
    }

    public VariableElement getOrCreateCaptureParam(VariableElement var, TypeElement declaringType) {
        return getOrCreateLocalCapture(var, declaringType).param;
    }

    public VariableElement getOrCreateCaptureField(VariableElement var, TypeElement declaringType) {
        LocalCapture capture = getOrCreateLocalCapture(var, declaringType);
        if (capture.field == null) {
            capture.field = GeneratedVariableElement
                    .newField(getCaptureFieldName(var, declaringType), var.asType(), declaringType)
                    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                    .addAnnotationMirrors(var.getAnnotationMirrors());
        }
        return capture.field;
    }

    public void addMethodReferenceReceiver(TypeElement type, TypeMirror receiverType) {
        assert !outerCaptures.containsKey(type);
        Capture capture = new Capture(
                GeneratedVariableElement.newParameter("outer$", receiverType, type).setNonnull(true));
        capture.field = GeneratedVariableElement.newField("target$", receiverType, type)
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL).setNonnull(true);
        receiverCaptures.put(type, capture);
    }
}