Skip to content

Commit

Permalink
Merge pull request #1545 from gavra0/master
Browse files Browse the repository at this point in the history
Add support for finding parent bindings in classpath
  • Loading branch information
JakeWharton authored Sep 4, 2019
2 parents 2409a1d + 700890e commit 6d618c3
Show file tree
Hide file tree
Showing 3 changed files with 391 additions and 22 deletions.
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

0 comments on commit 6d618c3

Please sign in to comment.