Merge "Generate android_icu4j with a fixed set of public APIs"
diff --git a/tools/srcgen/currysrc/src/main/java/com/google/currysrc/api/process/ast/BodyDeclarationLocators.java b/tools/srcgen/currysrc/src/main/java/com/google/currysrc/api/process/ast/BodyDeclarationLocators.java
index a012482..2d233f2 100644
--- a/tools/srcgen/currysrc/src/main/java/com/google/currysrc/api/process/ast/BodyDeclarationLocators.java
+++ b/tools/srcgen/currysrc/src/main/java/com/google/currysrc/api/process/ast/BodyDeclarationLocators.java
@@ -166,9 +166,35 @@
if (parametersString.isEmpty()) {
return Collections.emptyList();
}
- return Splitter.on(',').splitToList(parametersString);
+ return splitParameters(parametersString);
}
+ /**
+ * Split parameters by ','. Support simple generics syntax.
+ * TODO: Replace the implementation by JDT parser.
+ */
+ private static List<String> splitParameters(String parametersString) {
+ List<String> result = new ArrayList<>();
+ int genericsLevel = 0;
+ int start = 0;
+ for (int p = 0; p < parametersString.length(); p++) {
+ char c = parametersString.charAt(p);
+ if (genericsLevel == 0 && c == ',') {
+ result.add(parametersString.substring(start, p));
+ start = p + 1;
+ } else if (c == '<') {
+ genericsLevel++;
+ } else if (c == '>') {
+ genericsLevel--;
+ }
+ }
+ if (start < parametersString.length()) {
+ result.add(parametersString.substring(start, parametersString.length()));
+ }
+ return result;
+ }
+
+
private static List<String> splitInTwo(String string, String separator) {
List<String> components = Splitter.on(separator).splitToList(string);
if (components.size() != 2) {
diff --git a/tools/srcgen/generate_android_icu4j.sh b/tools/srcgen/generate_android_icu4j.sh
index 416ab76..d50ec94 100755
--- a/tools/srcgen/generate_android_icu4j.sh
+++ b/tools/srcgen/generate_android_icu4j.sh
@@ -43,6 +43,7 @@
mkdir -p ${ANDROID_ICU4J_DIR}
fi
+WHITELIST_API_FILE=${ICU_SRCGEN_DIR}/whitelisted-public-api.txt
CORE_PLATFORM_API_FILE=${ICU_SRCGEN_DIR}/core-platform-api.txt
# Clean out previous generated code / resources.
@@ -55,7 +56,14 @@
mkdir -p ${DEST_RESOURCE_DIR}
# Generate the source code needed by Android.
-${SRCGEN_TOOL_BINARY} Icu4jTransform ${INPUT_DIRS} ${DEST_SRC_DIR} ${CORE_PLATFORM_API_FILE}
+# Branches used for testing new versions of ICU will have have the ${WHITELIST_API_FILE} file
+# that prevents new (stable) APIs being added to the Android public SDK API. The file should
+# not exist on "normal" release branches and master.
+ICU4J_BASE_COMMAND="${SRCGEN_TOOL_BINARY} Icu4jTransform"
+if [ -e "${WHITELIST_API_FILE}" ]; then
+ ICU4J_BASE_COMMAND+=" --hide-non-whitelisted-api ${WHITELIST_API_FILE}"
+fi
+${ICU4J_BASE_COMMAND} ${INPUT_DIRS} ${DEST_SRC_DIR} ${CORE_PLATFORM_API_FILE}
# Copy / transform the resources needed by the android_icu4j code.
for INPUT_DIR in ${INPUT_DIRS}; do
diff --git a/tools/srcgen/generate_whitelisted_public_api.sh b/tools/srcgen/generate_whitelisted_public_api.sh
new file mode 100755
index 0000000..8350749
--- /dev/null
+++ b/tools/srcgen/generate_whitelisted_public_api.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Copyright (C) 2018 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.
+
+source $(dirname $BASH_SOURCE)/common.sh
+
+SRCGEN_TOOL_BINARY=${ANDROID_HOST_OUT}/bin/android_icu4j_srcgen_binary
+WHITELIST_API_FILE=${ICU_SRCGEN_DIR}/whitelisted-public-api.txt
+
+${SRCGEN_TOOL_BINARY} GeneratePublicApiReport ${ANDROID_ICU4J_DIR}/src/main/java ${WHITELIST_API_FILE}
+
+TEMP_CONTENT=$(cat ${WHITELIST_API_FILE})
+
+# Prepend the license and README in the header
+cat > ${WHITELIST_API_FILE} <<'_EOF'
+# Copyright (C) 2018 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.
+
+_EOF
+
+echo "$TEMP_CONTENT" >> ${WHITELIST_API_FILE}
diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/HideNonWhitelistedDeclarations.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/HideNonWhitelistedDeclarations.java
new file mode 100644
index 0000000..ca3c14f
--- /dev/null
+++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/HideNonWhitelistedDeclarations.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 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.icu4j.srcgen;
+
+import com.android.icu4j.srcgen.checker.RecordPublicApiRules;
+import com.google.common.collect.Lists;
+import com.google.currysrc.api.process.Context;
+import com.google.currysrc.api.process.JavadocUtils;
+import com.google.currysrc.api.process.Processor;
+import com.google.currysrc.api.process.ast.BodyDeclarationLocator;
+import com.google.currysrc.api.process.ast.BodyDeclarationLocators;
+import com.google.currysrc.api.process.ast.StartPositionComparator;
+
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
+import org.eclipse.jdt.core.dom.BodyDeclaration;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
+import org.eclipse.jdt.core.dom.EnumDeclaration;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Adds a @hide javadoc tag to {@link BodyDeclaration}s that are not whitelisted.
+ */
+public class HideNonWhitelistedDeclarations implements Processor {
+ private final List<BodyDeclarationLocator> whitelist;
+ private final String tagComment;
+
+ public HideNonWhitelistedDeclarations(List<BodyDeclarationLocator> whitelist, String tagComment) {
+ this.whitelist = whitelist;
+ this.tagComment = tagComment;
+ }
+
+ @Override
+ public void process(Context context, CompilationUnit cu) {
+ // Ignore this process if the whitelist is not provided
+ if (whitelist == null) {
+ return;
+ }
+ List<BodyDeclaration> matchingNodes = Lists.newArrayList();
+ cu.accept(new ASTVisitor() {
+ @Override public boolean visit(EnumConstantDeclaration node) {
+ return handleMemberDeclarationNode(node);
+ }
+
+ @Override public boolean visit(EnumDeclaration node) {
+ return handleTypeDeclarationNode(node);
+ }
+
+ @Override public boolean visit(FieldDeclaration node) {
+ return handleMemberDeclarationNode(node);
+ }
+
+ @Override public boolean visit(MethodDeclaration node) {
+ return handleMemberDeclarationNode(node);
+ }
+
+ @Override public boolean visit(TypeDeclaration node) {
+ return handleTypeDeclarationNode(node);
+ }
+
+ private boolean handleTypeDeclarationNode(AbstractTypeDeclaration node) {
+ matchIfNotWhitelistedAndNotHidden(node);
+ // Continue processing for nested types / methods.
+ return true;
+ }
+
+ private boolean handleMemberDeclarationNode(BodyDeclaration node) {
+ matchIfNotWhitelistedAndNotHidden(node);
+ // Leaf declaration (i.e. a method, fields, enum constant).
+ return false;
+ }
+
+ private void matchIfNotWhitelistedAndNotHidden(final BodyDeclaration node) {
+ if (node == null) {
+ return;
+ }
+
+ if (!RecordPublicApiRules.isPublicApiEligible(node)) {
+ return;
+ }
+
+ if (BodyDeclarationLocators.matchesAny(whitelist, node)) {
+ return;
+ }
+ matchingNodes.add(node);
+ }
+ });
+
+ // Tackle nodes in reverse order to avoid messing up the ASTNode offsets.
+ Collections.sort(matchingNodes, new StartPositionComparator());
+ ASTRewrite rewrite = context.rewrite();
+ for (BodyDeclaration bodyDeclaration : Lists.reverse(matchingNodes)) {
+ JavadocUtils.addJavadocTag(rewrite, bodyDeclaration, tagComment);
+ }
+ }
+
+ @Override public String toString() {
+ return "HideNonWhitelistedDeclarations{" +
+ "whitelist=" + whitelist +
+ "tagComment=" + tagComment +
+ '}';
+ }
+}
diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java
index 44b7053..9b2f805 100644
--- a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java
+++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java
@@ -629,8 +629,24 @@
}
/**
- * Usage:
- * java com.android.icu4j.srcgen.Icu4JTransform {source files/directories} {target dir}
+ * Usage: See {@link Icu4jRules#COMMAND_USAGE}
+ *
+ * The option --hide-non-whitelisted-api can be used to explicitly describe the API surface to be
+ * exposed; anything not in the list will be hidden in additional to other rules. This is useful
+ * when upgrading ICU when we haven't yet added new classes/methods to various hard-coded lists
+ * described below.
+ *
+ * This tool hides the following in the knowledge that classes that are not explicitly
+ * hidden are exposed in the public API set:
+ *
+ * 1) Public classes that are not in the PUBLIC_API_CLASSES list.
+ * 2) Types / fields / methods that were deprecated when the class was first exposed in Android,
+ * listed in INITIAL_DEPRECATED_SET
+ * 3) Types / fields / methods that we explicitly want to hide, listed in DECLARATIONS_TO_HIDE
+ * 4) Types / fields / methods that are flagged with ICU javadoc as draft / provisional or
+ * internal.
+ * 5) If the --hide-non-whitelisted-api option is provided, types / fields / methods that are not
+ * in the whitelisted-api-file.
*/
public static void main(String[] args) throws Exception {
Map<String, String> options = JavaCore.getOptions();
@@ -648,6 +664,9 @@
static class Icu4jRules implements RuleSet {
private static final String SOURCE_CODE_HEADER = "/* GENERATED SOURCE. DO NOT MODIFY. */\n";
+ private static final String COMMAND_USAGE = "Usage: " + Icu4jTransform.class.getCanonicalName()
+ + " [--hide-non-whitelisted-api <whitelisted-api-file>]"
+ + " <source-dir>+ <target-dir> <core-platform-api-file>";
private final InputFileGenerator inputFileGenerator;
private final List<Rule> rules;
@@ -655,16 +674,24 @@
public Icu4jRules(String[] args) throws IOException {
if (args.length < 3) {
- throw new IllegalArgumentException(
- "Usage: " + Icu4jTransform.class.getCanonicalName()
- + " <source-dir>+ <target-dir> <core-platform-api-file>");
+ throw new IllegalArgumentException(COMMAND_USAGE);
+ }
+ Path whitelistedApiPath = null;
+ if ("--hide-non-whitelisted-api".equals(args[0])) {
+ whitelistedApiPath = Paths.get(args[1]);
+ if (args.length < 5) {
+ throw new IllegalArgumentException(COMMAND_USAGE);
+ }
+ String[] newArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, newArgs, 0, args.length - 2);
+ args = newArgs;
}
String[] inputDirNames = new String[args.length - 2];
System.arraycopy(args, 0, inputDirNames, 0, args.length - 2);
inputFileGenerator = Icu4jTransformRules.createInputFileGenerator(inputDirNames);
Path corePlatformApiFile = Paths.get(args[args.length - 1]);
- rules = createTransformRules(corePlatformApiFile);
+ rules = createTransformRules(corePlatformApiFile, whitelistedApiPath);
outputSourceFileGenerator =
Icu4jTransformRules.createOutputFileGenerator(args[args.length - 2]);
}
@@ -701,7 +728,8 @@
};
}
- private static List<Rule> createTransformRules(Path corePlatformApiFile) throws IOException {
+ private static List<Rule> createTransformRules(Path corePlatformApiFile,
+ Path whitelistedApiPath) throws IOException {
// The rules needed to repackage source code that declares or references com.ibm.icu code
// so it references android.icu instead.
Rule[] repackageRules = getRepackagingRules();
@@ -716,20 +744,25 @@
createOptionalRule(
new ReplaceTextCommentScanner(ORIGINAL_ICU_PACKAGE, ANDROID_ICU_PACKAGE)),
- // AST change: Hide all ICU public classes except those in the whitelist.
+ // AST change: Hide all ICU public classes except those in the PUBLIC_API_CLASSES
+ // whitelist.
createHidePublicClassesRule(),
- // AST change: Hide ICU methods that are deprecated and Android does not want to make
- // public.
+ // AST change: Hide ICU methods that are in INITIAL_DEPRECATED_SET and Android does not
+ // want to make public.
createHideOriginalDeprecatedClassesRule(),
- // AST change: Explicitly hide blacklisted methods such as those that get/set static
- // default values that might lead to confusion or strange interactions between Android's
- // ICU4J and java.text / java.util classes.
+ // AST change: Explicitly hide blacklisted methods in DECLARATIONS_TO_HIDE such as those
+ // that get/set static default values that might lead to confusion or strange interactions
+ // between Android's ICU4J and java.text / java.util classes.
createHideBlacklistedDeclarationsRule(),
// AST change: Explicitly hide any elements that are marked as
// @draft / @provisional / @internal
createOptionalRule(new HideDraftProvisionalInternal()),
+ // AST change: Hide new non-whitelisted API in Android temporarily
+ // Usually used for avoiding the new API introduced by upstream to show up in Android.
+ createHideNonWhitelistedRule(whitelistedApiPath),
+
// AST change: Remove JavaDoc tags that Android has no need of:
// @hide has been added in place of @draft, @provisional and @internal
// @stable <ICU version> will not mean much on Android.
@@ -785,4 +818,14 @@
"Only a subset of ICU is exposed in Android"));
}
}
+
+ private static Rule createHideNonWhitelistedRule(Path whitelistedApiPath) {
+ List<BodyDeclarationLocator> bodyDeclarationLocators = null;
+ if (whitelistedApiPath != null) {
+ bodyDeclarationLocators = BodyDeclarationLocators.readBodyDeclarationLocators(
+ whitelistedApiPath);
+ }
+ return createOptionalRule(new HideNonWhitelistedDeclarations(bodyDeclarationLocators,
+ "@hide Hide new API in Android temporarily"));
+ }
}
diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Main.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Main.java
index 67b3d60..5e13381 100644
--- a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Main.java
+++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Main.java
@@ -15,6 +15,8 @@
*/
package com.android.icu4j.srcgen;
+import com.android.icu4j.srcgen.checker.GeneratePublicApiReport;
+
public class Main {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
@@ -33,6 +35,9 @@
case "Icu4jBasicTransform":
Icu4jBasicTransform.main(inputArgs);
break;
+ case "GeneratePublicApiReport":
+ GeneratePublicApiReport.main(inputArgs);
+ break;
default:
throw new IllegalArgumentException("Input class name is not valid: " + className);
}
diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/GeneratePublicApiReport.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/GeneratePublicApiReport.java
new file mode 100644
index 0000000..4cb8d2f
--- /dev/null
+++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/GeneratePublicApiReport.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.icu4j.srcgen.checker;
+
+import com.android.icu4j.srcgen.Icu4jTransformRules;
+import com.google.currysrc.Main;
+import com.google.currysrc.api.input.InputFileGenerator;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.List;
+
+public class GeneratePublicApiReport {
+ private static final boolean DEBUG = false;
+
+ private GeneratePublicApiReport() {
+ }
+
+ /**
+ * Usage:
+ * java com.android.icu4j.srcgen.checker.GeneratePublicApiReport
+ * {android_icu4j src main directories} {report output file path}
+ */
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ throw new IllegalArgumentException("At least 2 argument required.");
+ }
+
+ Main main = new Main(DEBUG);
+
+ // We assume we only need to look at ICU4J code for this for both passes.
+ String[] inputDirs = new String[args.length - 1];
+ System.arraycopy(args, 0, inputDirs, 0, inputDirs.length);
+ InputFileGenerator inputFileGenerator = Icu4jTransformRules.createInputFileGenerator(
+ inputDirs);
+
+ System.out.println("Establishing Android public ICU4J API");
+ RecordPublicApiRules recordPublicApiRulesRules = new RecordPublicApiRules(
+ inputFileGenerator);
+ main.execute(recordPublicApiRulesRules);
+ List<String> publicMemberLocatorStrings = recordPublicApiRulesRules.publicMembers();
+ File outputReportFile = new File(args[args.length - 1]);
+ try (PrintStream report = new PrintStream(outputReportFile)) {
+ for (String publicMemberLocatorString : publicMemberLocatorStrings) {
+ report.println(publicMemberLocatorString);
+ }
+ }
+
+ System.out.println("Report file: " + outputReportFile);
+ }
+}
diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/RecordPublicApiRules.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/RecordPublicApiRules.java
index 42c34f6..6d961bc 100644
--- a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/RecordPublicApiRules.java
+++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/checker/RecordPublicApiRules.java
@@ -52,7 +52,7 @@
* Rules that operate over a set of files and record the public API (according to Android's rules
* for @hide).
*/
-class RecordPublicApiRules implements RuleSet {
+public class RecordPublicApiRules implements RuleSet {
private final InputFileGenerator inputFileGenerator;
@@ -132,79 +132,91 @@
}
private void handleDeclarationNode(BodyDeclaration node) {
- if (isExplicitlyHidden(node)) {
+ if (!isPublicApiEligible(node)) {
return;
}
-
- AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(node);
- if (typeDeclaration == null) {
- // Not unusual: methods / fields defined on anonymous types are like this. The parent
- // is a constructor expression, not a declaration.
- return;
- }
-
- boolean isNonTypeDeclaration = typeDeclaration != node;
- if (isNonTypeDeclaration) {
- if (isExplicitlyHidden(node) || !isMemberPublicApiEligible(typeDeclaration, node)) {
- return;
- }
- }
- while (typeDeclaration != null) {
- if (isExplicitlyHidden(typeDeclaration) || !isTypePublicApiEligible(typeDeclaration)) {
- return;
- }
- typeDeclaration = TypeLocator.findEnclosingTypeDeclaration(typeDeclaration);
- }
// The node is appropriately public and is not hidden.
publicMembers.addAll(BodyDeclarationLocators.toLocatorStringForms(node));
}
- private boolean isTypePublicApiEligible(AbstractTypeDeclaration typeDeclaration) {
- int typeModifiers = typeDeclaration.getModifiers();
- return ((typeModifiers & Modifier.PUBLIC) != 0);
- }
-
- public boolean isMemberPublicApiEligible(AbstractTypeDeclaration type, BodyDeclaration node) {
- if (node.getNodeType() == ASTNode.ENUM_DECLARATION
- || node.getNodeType() == ASTNode.TYPE_DECLARATION) {
- throw new AssertionError("Unsupported node type: " + node);
- }
-
- if (type instanceof TypeDeclaration) {
- TypeDeclaration typeDeclaration = (TypeDeclaration) type;
- // All methods are public on interfaces. Not sure if true on Java 8.
- if (typeDeclaration.isInterface()) {
- return true;
- }
- }
- int memberModifiers = node.getModifiers();
- return ((memberModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0);
- }
-
- private boolean isExplicitlyHidden(BodyDeclaration node) {
- Javadoc javadoc = node.getJavadoc();
- if (javadoc == null) {
- return false;
- }
- final Boolean[] isHidden = new Boolean[] { false };
- javadoc.accept(new ASTVisitor(true /* visitDocNodes */) {
- @Override public boolean visit(TagElement node) {
- String tagName = node.getTagName();
- if (tagName == null) {
- return true;
- }
- if (tagName.equals("@hide")) {
- isHidden[0] = true;
- return false;
- }
- return true;
- }
- });
- return isHidden[0];
- }
-
public List<String> publicMembers() {
return publicMembers;
}
}
+
+ /**
+ * Determine if a declaration can be public API by looking at its and ancestor type's modifier and
+ * {@code @hide} javadoc tag.
+ */
+ public static boolean isPublicApiEligible(BodyDeclaration node) {
+ if (isExplicitlyHidden(node)) {
+ return false;
+ }
+
+ AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(node);
+ if (typeDeclaration == null) {
+ // Not unusual: methods / fields defined on anonymous types are like this. The parent
+ // is a constructor expression, not a declaration.
+ return false;
+ }
+
+ boolean isNonTypeDeclaration = typeDeclaration != node;
+ if (isNonTypeDeclaration) {
+ if (isExplicitlyHidden(node) || !isMemberPublicApiEligible(typeDeclaration, node)) {
+ return false;
+ }
+ }
+ while (typeDeclaration != null) {
+ if (isExplicitlyHidden(typeDeclaration) ||
+ !RecordPublicApiRules.isTypePublicApiEligible(typeDeclaration)) {
+ return false;
+ }
+ typeDeclaration = TypeLocator.findEnclosingTypeDeclaration(typeDeclaration);
+ }
+ return true;
+ }
+
+ private static boolean isExplicitlyHidden(BodyDeclaration node) {
+ // Don't visit AST or use ASTVisitor here as the caller is visiting the node.
+ Javadoc javadoc = node.getJavadoc();
+ if (javadoc == null) {
+ return false;
+ }
+ for (TagElement tagElement : (List<TagElement>) javadoc.tags()) {
+ String tagName = tagElement.getTagName();
+ if (tagName == null) {
+ continue;
+ }
+ if ("@hide".equals(tagName.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isTypePublicApiEligible(AbstractTypeDeclaration typeDeclaration) {
+ int typeModifiers = typeDeclaration.getModifiers();
+ return ((typeModifiers & Modifier.PUBLIC) != 0);
+ }
+
+ private static boolean isMemberPublicApiEligible(AbstractTypeDeclaration type, BodyDeclaration node) {
+ if (node.getNodeType() == ASTNode.ENUM_DECLARATION
+ || node.getNodeType() == ASTNode.TYPE_DECLARATION) {
+ throw new AssertionError("Unsupported node type: " + node);
+ }
+
+ if (type instanceof TypeDeclaration) {
+ TypeDeclaration typeDeclaration = (TypeDeclaration) type;
+ // All methods are public on interfaces. Not sure if true on Java 8.
+ if (typeDeclaration.isInterface()) {
+ return true;
+ }
+ }
+ // Enum constant doesn't have visiblity
+ if (node.getNodeType() == ASTNode.ENUM_CONSTANT_DECLARATION) {
+ return true;
+ }
+ int memberModifiers = node.getModifiers();
+ return ((memberModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0);
+ }
}