content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.customchecks;
  
  import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
  
  import com.google.auto.service.AutoService;
  import com.google.errorprone.BugPattern;
  import com.google.errorprone.VisitorState;
  import com.google.errorprone.bugpatterns.BugChecker;
  import com.google.errorprone.matchers.Description;
  import com.sun.source.doctree.DocCommentTree;
  import com.sun.source.tree.ClassTree;
  import com.sun.source.tree.PackageTree;
  import com.sun.tools.javac.api.JavacTrees;
  import java.util.regex.Pattern;
  import javax.annotation.Nullable;
  import javax.lang.model.element.Modifier;
  
  @AutoService(BugChecker.class)
  @BugPattern(
      summary =
          "This public internal class doesn't end with any of the applicable javadoc disclaimers: \""
              + OtelInternalJavadoc.EXPECTED_INTERNAL_COMMENT_V1
              + "\", or \""
              + OtelInternalJavadoc.EXPECTED_INTERNAL_COMMENT_V2
              + "\"",
      severity = WARNING)
  public class OtelInternalJavadoc extends BugChecker implements BugChecker.ClassTreeMatcher {
  
    private static final long serialVersionUID = 1L;
  
    private static final Pattern INTERNAL_PACKAGE_PATTERN = Pattern.compile("\\binternal\\b");
  
    private static final Pattern EXCLUDE_PACKAGE_PATTERN =
        Pattern.compile("^io\\.opentelemetry\\.javaagent\\.instrumentation\\.internal\\.");
  
    static final String EXPECTED_INTERNAL_COMMENT_V1 =
        "This class is internal and is hence not for public use."
            + " Its APIs are unstable and can change at any time.";
  
    static final String EXPECTED_INTERNAL_COMMENT_V2 =
        "This class is internal and experimental. Its APIs are unstable and can change at any time."
            + " Its APIs (or a version of them) may be promoted to the public stable API in the"
            + " future, but no guarantees are made.";
  
    @Override
    public Description matchClass(ClassTree tree, VisitorState state) {
      if (!isPublic(tree) || !isInternal(state) || tree.getSimpleName().toString().endsWith("Test")) {
        return Description.NO_MATCH;
      }
      String javadoc = getJavadoc(state);
      if (javadoc != null
          && (javadoc.contains(EXPECTED_INTERNAL_COMMENT_V1)
              || javadoc.contains(EXPECTED_INTERNAL_COMMENT_V2))) {
        return Description.NO_MATCH;
      }
      return describeMatch(tree);
    }
  
    private static boolean isPublic(ClassTree tree) {
      return tree.getModifiers().getFlags().contains(Modifier.PUBLIC);
    }
  
    private static boolean isInternal(VisitorState state) {
      PackageTree packageTree = state.getPath().getCompilationUnit().getPackage();
      if (packageTree == null) {
        return false;
      }
      String packageName = state.getSourceForNode(packageTree.getPackageName());
      return packageName != null
          && INTERNAL_PACKAGE_PATTERN.matcher(packageName).find()
          && !EXCLUDE_PACKAGE_PATTERN.matcher(packageName).find();
    }
  
    @Nullable
    private static String getJavadoc(VisitorState state) {
      DocCommentTree docCommentTree =
          JavacTrees.instance(state.context).getDocCommentTree(state.getPath());
      if (docCommentTree == null) {
        return null;
      }
      return docCommentTree.toString().replace("\n", "");
    }
  }
  