Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for finding parent bindings in classpath #1545

Merged
merged 1 commit into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import static javax.lang.model.element.Modifier.PUBLIC;

/** A set of all the bindings requested by a single type. */
final class BindingSet {
final class BindingSet implements BindingInformationProvider {
static final ClassName UTILS = ClassName.get("butterknife.internal", "Utils");
private static final ClassName VIEW = ClassName.get("android.view", "View");
private static final ClassName CONTEXT = ClassName.get("android.content", "Context");
Expand All @@ -66,12 +66,13 @@ final class BindingSet {
private final ImmutableList<ViewBinding> viewBindings;
private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
private final ImmutableList<ResourceBinding> resourceBindings;
private final @Nullable BindingSet parentBinding;
private final @Nullable BindingInformationProvider parentBinding;

private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
ImmutableList<FieldCollectionViewBinding> collectionBindings,
ImmutableList<ResourceBinding> resourceBindings, @Nullable BindingSet parentBinding) {
ImmutableList<ResourceBinding> resourceBindings,
@Nullable BindingInformationProvider parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
Expand All @@ -84,6 +85,11 @@ private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean
this.parentBinding = parentBinding;
}

@Override
public ClassName getBindingClassName() {
return bindingClassName;
}

JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
Expand All @@ -99,7 +105,7 @@ private TypeSpec createType(int sdk, boolean debuggable) {
}

if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
result.superclass(parentBinding.getBindingClassName());
} else {
result.addSuperinterface(UNBINDER);
}
Expand Down Expand Up @@ -667,7 +673,8 @@ private boolean hasViewLocal() {
}

/** True if this binding requires a view. Otherwise only a context is needed. */
private boolean constructorNeedsView() {
@Override
public boolean constructorNeedsView() {
return hasViewBindings() //
|| (parentBinding != null && parentBinding.constructorNeedsView());
}
Expand All @@ -692,15 +699,19 @@ static Builder newBuilder(TypeElement enclosingElement) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}

String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
ClassName bindingClassName = getBindingClassName(enclosingElement);

boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

static ClassName getBindingClassName(TypeElement typeElement) {
String packageName = getPackage(typeElement).getQualifiedName().toString();
String className = typeElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
return ClassName.get(packageName, className + "_ViewBinding");
}

static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
Expand All @@ -709,7 +720,7 @@ static final class Builder {
private final boolean isActivity;
private final boolean isDialog;

private @Nullable BindingSet parentBinding;
private @Nullable BindingInformationProvider parentBinding;

private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
Expand Down Expand Up @@ -751,7 +762,7 @@ void addResource(ResourceBinding binding) {
resourceBindings.add(binding);
}

void setParent(BindingSet parent) {
void setParent(BindingInformationProvider parent) {
this.parentBinding = parent;
}

Expand Down Expand Up @@ -787,3 +798,28 @@ BindingSet build() {
}
}
}

interface BindingInformationProvider {
boolean constructorNeedsView();
ClassName getBindingClassName();
}

final class ClasspathBindingSet implements BindingInformationProvider {
private boolean constructorNeedsView;
private ClassName className;

ClasspathBindingSet(boolean constructorNeedsView, TypeElement classElement) {
this.constructorNeedsView = constructorNeedsView;
this.className = BindingSet.getBindingClassName(classElement);
}

@Override
public ClassName getBindingClassName() {
return className;
}

@Override
public boolean constructorNeedsView() {
return constructorNeedsView;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import butterknife.internal.ListenerMethod;
import com.google.auto.common.SuperficialValidation;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeName;
Expand All @@ -48,6 +49,7 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -342,6 +344,9 @@ private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}

Map<TypeElement, ClasspathBindingSet> classpathBindings =
findAllSupertypeBindings(builderMap, erasedTargetNames);

// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
Expand All @@ -353,11 +358,14 @@ private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();

TypeElement parentType = findParentType(type, erasedTargetNames);
TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
BindingInformationProvider parentBinding = bindingMap.get(parentType);
if (parentBinding == null) {
parentBinding = classpathBindings.get(parentType);
}
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
Expand Down Expand Up @@ -1264,21 +1272,88 @@ private BindingSet.Builder getOrCreateBindingBuilder(
return builder;
}

/** Finds the parent binder type in the supplied set, if any. */
private @Nullable TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) {
TypeMirror type;
/** Finds the parent binder type in the supplied sets, if any. */
private @Nullable TypeElement findParentType(
TypeElement typeElement, Set<TypeElement> parents, Set<TypeElement> classpathParents) {
while (true) {
type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
return null;
}
typeElement = (TypeElement) ((DeclaredType) type).asElement();
if (parents.contains(typeElement)) {
typeElement = getSuperClass(typeElement);
if (typeElement == null || parents.contains(typeElement)
|| classpathParents.contains(typeElement)) {
return typeElement;
}
}
}

private Map<TypeElement, ClasspathBindingSet> findAllSupertypeBindings(
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> processedInThisRound) {
Map<TypeElement, ClasspathBindingSet> classpathBindings = new HashMap<>();

Set<Class<? extends Annotation>> supportedAnnotations = getSupportedAnnotations();
Set<Class<? extends Annotation>> requireViewInConstructor =
ImmutableSet.<Class<? extends Annotation>>builder()
.addAll(LISTENERS).add(BindView.class).add(BindViews.class).build();
supportedAnnotations.removeAll(requireViewInConstructor);

for (TypeElement typeElement : builderMap.keySet()) {
// Make sure to process superclass before subclass. This is because if there is a class that
// requires a View in the constructor, all subclasses need it as well.
Deque<TypeElement> superClasses = new ArrayDeque<>();
TypeElement superClass = getSuperClass(typeElement);
while (superClass != null && !processedInThisRound.contains(superClass)
&& !classpathBindings.containsKey(superClass)) {
superClasses.addFirst(superClass);
superClass = getSuperClass(superClass);
}

boolean parentHasConstructorWithView = false;
while (!superClasses.isEmpty()) {
TypeElement superclass = superClasses.removeFirst();
ClasspathBindingSet classpathBinding =
findBindingInfoForType(superclass, requireViewInConstructor, supportedAnnotations,
parentHasConstructorWithView);
if (classpathBinding != null) {
parentHasConstructorWithView |= classpathBinding.constructorNeedsView();
classpathBindings.put(superclass, classpathBinding);
}
}
}
return ImmutableMap.copyOf(classpathBindings);
}

private @Nullable ClasspathBindingSet findBindingInfoForType(
TypeElement typeElement, Set<Class<? extends Annotation>> requireConstructorWithView,
Set<Class<? extends Annotation>> otherAnnotations, boolean needsConstructorWithView) {
boolean foundSupportedAnnotation = false;
for (Element enclosedElement : typeElement.getEnclosedElements()) {
for (Class<? extends Annotation> bindViewAnnotation : requireConstructorWithView) {
if (enclosedElement.getAnnotation(bindViewAnnotation) != null) {
return new ClasspathBindingSet(true, typeElement);
}
}
for (Class<? extends Annotation> supportedAnnotation : otherAnnotations) {
if (enclosedElement.getAnnotation(supportedAnnotation) != null) {
if (needsConstructorWithView) {
return new ClasspathBindingSet(true, typeElement);
}
foundSupportedAnnotation = true;
}
}
}
if (foundSupportedAnnotation) {
return new ClasspathBindingSet(false, typeElement);
} else {
return null;
}
}

private @Nullable TypeElement getSuperClass(TypeElement typeElement) {
TypeMirror type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
return null;
}
return (TypeElement) ((DeclaredType) type).asElement();
}

@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
Expand Down
Loading