/*
* 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);
}
}