|  | // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | package com.android.tools.r8.shaking; | 
|  |  | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexClassAndField; | 
|  | import com.android.tools.r8.graph.DexClassAndMethod; | 
|  | import com.android.tools.r8.graph.DexField; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards; | 
|  | import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder; | 
|  | import com.android.tools.r8.utils.StringUtils; | 
|  | import com.google.common.collect.Iterables; | 
|  | import java.util.Collections; | 
|  | import java.util.List; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.stream.Collectors; | 
|  | import java.util.stream.StreamSupport; | 
|  |  | 
|  | public class ProguardMemberRule { | 
|  |  | 
|  | public static class Builder { | 
|  |  | 
|  | private List<ProguardTypeMatcher> annotations = Collections.emptyList(); | 
|  | private ProguardAccessFlags accessFlags = new ProguardAccessFlags(); | 
|  | private ProguardAccessFlags negatedAccessFlags = new ProguardAccessFlags(); | 
|  | private ProguardMemberType ruleType; | 
|  | private ProguardTypeMatcher type; | 
|  | private ProguardNameMatcher name; | 
|  | private List<ProguardTypeMatcher> arguments; | 
|  | private ProguardMemberRuleReturnValue returnValue; | 
|  |  | 
|  | private Builder() {} | 
|  |  | 
|  | public void setAnnotations(List<ProguardTypeMatcher> annotations) { | 
|  | assert annotations != null; | 
|  | this.annotations = annotations; | 
|  | } | 
|  |  | 
|  | public ProguardAccessFlags getAccessFlags() { | 
|  | return accessFlags; | 
|  | } | 
|  |  | 
|  | public Builder setAccessFlags(ProguardAccessFlags flags) { | 
|  | accessFlags = flags; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public ProguardAccessFlags getNegatedAccessFlags() { | 
|  | return negatedAccessFlags; | 
|  | } | 
|  |  | 
|  | public void setNegatedAccessFlags(ProguardAccessFlags flags) { | 
|  | negatedAccessFlags = flags; | 
|  | } | 
|  |  | 
|  | public Builder setRuleType(ProguardMemberType ruleType) { | 
|  | this.ruleType = ruleType; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public ProguardTypeMatcher getTypeMatcher() { | 
|  | return type; | 
|  | } | 
|  |  | 
|  | public Builder setTypeMatcher(ProguardTypeMatcher type) { | 
|  | this.type = type; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setName(IdentifierPatternWithWildcards identifierPatternWithWildcards) { | 
|  | this.name = ProguardNameMatcher.create(identifierPatternWithWildcards); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setArguments(List<ProguardTypeMatcher> arguments) { | 
|  | this.arguments = arguments; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setReturnValue(ProguardMemberRuleReturnValue value) { | 
|  | returnValue = value; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public boolean isValid() { | 
|  | return ruleType != null; | 
|  | } | 
|  |  | 
|  | public ProguardMemberRule build() { | 
|  | assert isValid(); | 
|  | return new ProguardMemberRule( | 
|  | annotations, | 
|  | accessFlags, | 
|  | negatedAccessFlags, | 
|  | ruleType, | 
|  | type, | 
|  | name, | 
|  | arguments, | 
|  | returnValue); | 
|  | } | 
|  | } | 
|  |  | 
|  | private final List<ProguardTypeMatcher> annotations; | 
|  | private final ProguardAccessFlags accessFlags; | 
|  | private final ProguardAccessFlags negatedAccessFlags; | 
|  | private final ProguardMemberType ruleType; | 
|  | private final ProguardTypeMatcher type; | 
|  | private final ProguardNameMatcher name; | 
|  | private final List<ProguardTypeMatcher> arguments; | 
|  | private final ProguardMemberRuleReturnValue returnValue; | 
|  |  | 
|  | private ProguardMemberRule( | 
|  | List<ProguardTypeMatcher> annotations, | 
|  | ProguardAccessFlags accessFlags, | 
|  | ProguardAccessFlags negatedAccessFlags, | 
|  | ProguardMemberType ruleType, | 
|  | ProguardTypeMatcher type, | 
|  | ProguardNameMatcher name, | 
|  | List<ProguardTypeMatcher> arguments, | 
|  | ProguardMemberRuleReturnValue returnValue) { | 
|  | this.annotations = annotations; | 
|  | this.accessFlags = accessFlags; | 
|  | this.negatedAccessFlags = negatedAccessFlags; | 
|  | this.ruleType = ruleType; | 
|  | this.type = type; | 
|  | this.name = name; | 
|  | this.arguments = arguments != null ? Collections.unmodifiableList(arguments) : null; | 
|  | this.returnValue = returnValue; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new empty builder. | 
|  | */ | 
|  | public static Builder builder() { | 
|  | return new Builder(); | 
|  | } | 
|  |  | 
|  | public List<ProguardTypeMatcher> getAnnotations() { | 
|  | return annotations; | 
|  | } | 
|  |  | 
|  | public ProguardAccessFlags getAccessFlags() { | 
|  | return accessFlags; | 
|  | } | 
|  |  | 
|  | public ProguardAccessFlags getNegatedAccessFlags() { | 
|  | return negatedAccessFlags; | 
|  | } | 
|  |  | 
|  | public ProguardMemberType getRuleType() { | 
|  | return ruleType; | 
|  | } | 
|  |  | 
|  | public ProguardTypeMatcher getType() { | 
|  | return type; | 
|  | } | 
|  |  | 
|  | public ProguardNameMatcher getName() { | 
|  | return name; | 
|  | } | 
|  |  | 
|  | public List<ProguardTypeMatcher> getArguments() { | 
|  | return arguments; | 
|  | } | 
|  |  | 
|  | public boolean hasReturnValue() { | 
|  | return returnValue != null; | 
|  | } | 
|  |  | 
|  | public ProguardMemberRuleReturnValue getReturnValue() { | 
|  | return returnValue; | 
|  | } | 
|  |  | 
|  | public ProguardTypeMatcher getTypeMatcher() { | 
|  | return type; | 
|  | } | 
|  |  | 
|  | public boolean matches( | 
|  | DexClassAndField field, | 
|  | AppView<?> appView, | 
|  | Consumer<AnnotationMatchResult> matchedAnnotationsConsumer, | 
|  | DexStringCache stringCache) { | 
|  | DexField originalSignature = | 
|  | appView.graphLens().getOriginalFieldSignature(field.getReference()); | 
|  | switch (getRuleType()) { | 
|  | case ALL: | 
|  | case ALL_FIELDS: | 
|  | { | 
|  | // Access flags check. | 
|  | if (!getAccessFlags().containsAll(field.getAccessFlags()) | 
|  | || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) { | 
|  | break; | 
|  | } | 
|  | // Annotations check. | 
|  | return RootSetBuilder.containsAllAnnotations( | 
|  | annotations, field, matchedAnnotationsConsumer); | 
|  | } | 
|  |  | 
|  | case FIELD: | 
|  | { | 
|  | // Name check. | 
|  | String name = stringCache.lookupString(originalSignature.name); | 
|  | if (!getName().matches(name)) { | 
|  | break; | 
|  | } | 
|  | // Access flags check. | 
|  | if (!getAccessFlags().containsAll(field.getAccessFlags()) | 
|  | || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) { | 
|  | break; | 
|  | } | 
|  | // Type check. | 
|  | if (!getType().matches(originalSignature.type, appView)) { | 
|  | break; | 
|  | } | 
|  | // Annotations check | 
|  | return RootSetBuilder.containsAllAnnotations( | 
|  | annotations, field, matchedAnnotationsConsumer); | 
|  | } | 
|  |  | 
|  | case ALL_METHODS: | 
|  | case CLINIT: | 
|  | case INIT: | 
|  | case CONSTRUCTOR: | 
|  | case METHOD: | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public boolean matches( | 
|  | DexClassAndMethod method, | 
|  | AppView<?> appView, | 
|  | Consumer<AnnotationMatchResult> matchedAnnotationsConsumer, | 
|  | DexStringCache stringCache) { | 
|  | DexMethod originalSignature = | 
|  | appView.graphLens().getOriginalMethodSignature(method.getReference()); | 
|  | switch (getRuleType()) { | 
|  | case ALL_METHODS: | 
|  | if (method.getDefinition().isClassInitializer()) { | 
|  | break; | 
|  | } | 
|  | // Fall through for all other methods. | 
|  |  | 
|  | case ALL: | 
|  | { | 
|  | // Access flags check. | 
|  | if (!getAccessFlags().containsAll(method.getAccessFlags()) | 
|  | || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) { | 
|  | break; | 
|  | } | 
|  | // Annotations check. | 
|  | return RootSetBuilder.containsAllAnnotations( | 
|  | annotations, method, matchedAnnotationsConsumer); | 
|  | } | 
|  |  | 
|  | case METHOD: | 
|  | // Check return type. | 
|  | if (!type.matches(originalSignature.getReturnType(), appView)) { | 
|  | break; | 
|  | } | 
|  | // Fall through for access flags, name and arguments. | 
|  |  | 
|  | case CONSTRUCTOR: | 
|  | case INIT: | 
|  | case CLINIT: | 
|  | { | 
|  | // Name check. | 
|  | String name = stringCache.lookupString(originalSignature.name); | 
|  | if (!getName().matches(name)) { | 
|  | break; | 
|  | } | 
|  | // Access flags check. | 
|  | if (!getAccessFlags().containsAll(method.getAccessFlags()) | 
|  | || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) { | 
|  | break; | 
|  | } | 
|  | // Annotations check. | 
|  | if (!RootSetBuilder.containsAllAnnotations( | 
|  | annotations, method, matchedAnnotationsConsumer)) { | 
|  | return false; | 
|  | } | 
|  | // Parameter types check. | 
|  | List<ProguardTypeMatcher> arguments = getArguments(); | 
|  | if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) { | 
|  | return true; | 
|  | } | 
|  | DexType[] parameters = originalSignature.getParameters().values; | 
|  | if (parameters.length != arguments.size()) { | 
|  | break; | 
|  | } | 
|  | for (int i = 0; i < parameters.length; i++) { | 
|  | if (!arguments.get(i).matches(parameters[i], appView)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | // All parameters matched. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | case ALL_FIELDS: | 
|  | case FIELD: | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public boolean isSpecific() { | 
|  | switch (getRuleType()) { | 
|  | case ALL: | 
|  | // fall through | 
|  | case ALL_FIELDS: | 
|  | // fall through | 
|  | case ALL_METHODS: | 
|  | return false; | 
|  | default: | 
|  | return Iterables.size(getWildcards()) == 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | Iterable<ProguardWildcard> getWildcards() { | 
|  | return Iterables.concat( | 
|  | ProguardTypeMatcher.getWildcardsOrEmpty(annotations), | 
|  | ProguardTypeMatcher.getWildcardsOrEmpty(type), | 
|  | ProguardNameMatcher.getWildcardsOrEmpty(name), | 
|  | arguments != null | 
|  | ? arguments.stream() | 
|  | .map(ProguardTypeMatcher::getWildcards) | 
|  | .flatMap(it -> StreamSupport.stream(it.spliterator(), false)) | 
|  | ::iterator | 
|  | : Collections::emptyIterator); | 
|  | } | 
|  |  | 
|  | ProguardMemberRule materialize(DexItemFactory dexItemFactory) { | 
|  | return new ProguardMemberRule( | 
|  | ProguardTypeMatcher.materializeList(getAnnotations(), dexItemFactory), | 
|  | getAccessFlags(), | 
|  | getNegatedAccessFlags(), | 
|  | getRuleType(), | 
|  | getType() == null ? null : getType().materialize(dexItemFactory), | 
|  | getName() == null ? null : getName().materialize(), | 
|  | getArguments() == null | 
|  | ? null | 
|  | : getArguments().stream() | 
|  | .map(argument -> argument.materialize(dexItemFactory)) | 
|  | .collect(Collectors.toList()), | 
|  | getReturnValue()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | if (!(o instanceof ProguardMemberRule)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ProguardMemberRule that = (ProguardMemberRule) o; | 
|  |  | 
|  | if (!annotations.equals(that.annotations)) { | 
|  | return false; | 
|  | } | 
|  | if (!accessFlags.equals(that.accessFlags)) { | 
|  | return false; | 
|  | } | 
|  | if (!negatedAccessFlags.equals(that.negatedAccessFlags)) { | 
|  | return false; | 
|  | } | 
|  | if (ruleType != that.ruleType) { | 
|  | return false; | 
|  | } | 
|  | if (name != null ? !name.equals(that.name) : that.name != null) { | 
|  | return false; | 
|  | } | 
|  | if (type != null ? !type.equals(that.type) : that.type != null) { | 
|  | return false; | 
|  | } | 
|  | return arguments != null ? arguments.equals(that.arguments) : that.arguments == null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | int result = annotations.hashCode(); | 
|  | result = 31 * result + accessFlags.hashCode(); | 
|  | result = 31 * result + negatedAccessFlags.hashCode(); | 
|  | result = 31 * result + (ruleType != null ? ruleType.hashCode() : 0); | 
|  | result = 31 * result + (type != null ? type.hashCode() : 0); | 
|  | result = 31 * result + (name != null ? name.hashCode() : 0); | 
|  | result = 31 * result + (arguments != null ? arguments.hashCode() : 0); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | StringBuilder result = new StringBuilder(); | 
|  | for (ProguardTypeMatcher annotation : annotations) { | 
|  | ProguardKeepRule.appendNonEmpty(result, "@", annotation, " "); | 
|  | } | 
|  | ProguardKeepRule.appendNonEmpty(result, null, accessFlags, " "); | 
|  | ProguardKeepRule | 
|  | .appendNonEmpty(result, null, negatedAccessFlags.toString().replace(" ", " !"), " "); | 
|  | switch (getRuleType()) { | 
|  | case ALL_FIELDS: | 
|  | result.append("<fields>"); | 
|  | break; | 
|  | case ALL_METHODS: | 
|  | result.append("<methods>"); | 
|  | break; | 
|  | case METHOD: | 
|  | result.append(getType()); | 
|  | result.append(' '); | 
|  | // Fall through for rest of method signature. | 
|  | case CONSTRUCTOR: | 
|  | case CLINIT: | 
|  | case INIT: { | 
|  | result.append(getName()); | 
|  | result.append('('); | 
|  | result.append(StringUtils.join(",", getArguments())); | 
|  | result.append(')'); | 
|  | break; | 
|  | } | 
|  | case FIELD: { | 
|  | result.append(getType()); | 
|  | result.append(' '); | 
|  | result.append(getName()); | 
|  | break; | 
|  | } | 
|  | case ALL: { | 
|  | result.append("*"); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unknown kind of member rule"); | 
|  | } | 
|  | if (hasReturnValue()) { | 
|  | result.append(returnValue.toString()); | 
|  | } | 
|  | return result.toString(); | 
|  | } | 
|  |  | 
|  | public static ProguardMemberRule defaultKeepAllRule() { | 
|  | ProguardMemberRule.Builder ruleBuilder = new ProguardMemberRule.Builder(); | 
|  | ruleBuilder.setRuleType(ProguardMemberType.ALL); | 
|  | return ruleBuilder.build(); | 
|  | } | 
|  | } |