blob: b120978c94911152c449c5994327cb9b85d6b8dc [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.class2greylist;
import com.google.common.base.Strings;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class ApiResolver {
private final List<ApiComponents> mPotentialPublicAlternatives;
private final Set<PackageAndClassName> mPublicApiClasses;
private static final Pattern LINK_TAG_PATTERN = Pattern.compile("\\{@link ([^\\}]+)\\}");
private static final Pattern CODE_TAG_PATTERN = Pattern.compile("\\{@code ([^\\}]+)\\}");
private static final Integer MIN_SDK_REQUIRING_PUBLIC_ALTERNATIVES = 29;
public ApiResolver() {
mPotentialPublicAlternatives = null;
mPublicApiClasses = null;
}
public ApiResolver(Set<String> publicApis) {
mPotentialPublicAlternatives = publicApis.stream()
.map(api -> {
try {
return ApiComponents.fromDexSignature(api);
} catch (SignatureSyntaxError e) {
throw new RuntimeException("Could not parse public API signature:", e);
}
})
.collect(Collectors.toList());
mPublicApiClasses = mPotentialPublicAlternatives.stream()
.map(api -> api.getPackageAndClassName())
.collect(Collectors.toCollection(HashSet::new));
}
/**
* Verify that all public alternatives are valid.
*
* @param publicAlternativesString String containing public alternative explanations.
* @param signature Signature of the member that has the annotation.
*/
public void resolvePublicAlternatives(String publicAlternativesString, String signature,
Integer maxSdkVersion)
throws JavadocLinkSyntaxError, AlternativeNotFoundError,
RequiredAlternativeNotSpecifiedError {
if (Strings.isNullOrEmpty(publicAlternativesString) && maxSdkVersion != null
&& maxSdkVersion >= MIN_SDK_REQUIRING_PUBLIC_ALTERNATIVES) {
throw new RequiredAlternativeNotSpecifiedError();
}
if (publicAlternativesString != null && mPotentialPublicAlternatives != null) {
// Grab all instances of type {@link foo}
Matcher matcher = LINK_TAG_PATTERN.matcher(publicAlternativesString);
boolean hasLinkAlternative = false;
// Validate all link tags
while (matcher.find()) {
hasLinkAlternative = true;
String alternativeString = matcher.group(1);
ApiComponents alternative = ApiComponents.fromLinkTag(alternativeString,
signature);
if (alternative.getMemberName().isEmpty()) {
// Provided class as alternative
if (!mPublicApiClasses.contains(alternative.getPackageAndClassName())) {
throw new ClassAlternativeNotFoundError(alternative);
}
} else if (!mPotentialPublicAlternatives.contains(alternative)) {
// If the link is not a public alternative, it must because the link does not
// contain the method parameter types, e.g. {@link foo.bar.Baz#foo} instead of
// {@link foo.bar.Baz#foo(int)}. If the method name is unique within the class,
// we can handle it.
if (!Strings.isNullOrEmpty(alternative.getMethodParameterTypes())) {
throw new MemberAlternativeNotFoundError(alternative);
}
List<ApiComponents> almostMatches = mPotentialPublicAlternatives.stream()
.filter(api -> api.equalsIgnoringParam(alternative))
.collect(Collectors.toList());
if (almostMatches.size() == 0) {
throw new MemberAlternativeNotFoundError(alternative);
} else if (almostMatches.size() > 1) {
throw new MultipleAlternativesFoundError(alternative, almostMatches);
}
}
}
// No {@link ...} alternatives exist; try looking for {@code ...}
if (!hasLinkAlternative) {
if (!CODE_TAG_PATTERN.matcher(publicAlternativesString).find()) {
throw new NoAlternativesSpecifiedError();
}
}
}
}
}