content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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
   *
   *     https://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 org.apache.avro.compiler.idl;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * Utility class with {@code ThreadLocal} fields that allow the generated
   * classes {@link Idl} and {@link IdlTokenManager} to exchange documentation
   * comments without forcing explicit parsing of documentation comments.
   *
   * The reason this works is that all calls to this class happen within a call to
   * the method {@link Idl#CompilationUnit()} (either directly or indirectly).
   */
  public class DocCommentHelper {
    /**
     * Pattern to match the common whitespace indents in a multi-line String.
     * Doesn't match a single-line String, fully matches any multi-line String.
     *
     * To use: match on a {@link String#trim() trimmed} String, and then replace all
     * newlines followed by the group "indent" with a newline.
     */
    private static final Pattern WS_INDENT = Pattern.compile("(?U).*\\R(?<indent>\\h*).*(?:\\R\\k<indent>.*)*");
    /**
     * Pattern to match the whitespace indents plus common stars (1 or 2) in a
     * multi-line String. If a String fully matches, replace all occurrences of a
     * newline followed by whitespace and then the group "stars" with a newline.
     *
     * Note: partial matches are invalid.
     */
    private static final Pattern STAR_INDENT = Pattern.compile("(?U)(?<stars>\\*{1,2}).*(?:\\R\\h*\\k<stars>.*)*");
  
    private static final ThreadLocal<DocComment> DOC = new ThreadLocal<>();
    private static final ThreadLocal<List<String>> WARNINGS = ThreadLocal.withInitial(ArrayList::new);
  
    /**
     * Return all warnings that were encountered while parsing, once. Subsequent
     * calls before parsing again will return an empty list.
     */
    static List<String> getAndClearWarnings() {
      List<String> warnings = WARNINGS.get();
      WARNINGS.remove();
      return warnings;
    }
  
    static void setDoc(Token token) {
      DocComment newDocComment = new DocComment(token);
      DocComment oldDocComment = DOC.get();
      if (oldDocComment != null) {
        WARNINGS.get()
            .add(String.format(
                "Found documentation comment at line %d, column %d. Ignoring previous one at line %d, column %d: \"%s\"\n"
                    + "Did you mean to use a multiline comment ( /* ... */ ) instead?",
                newDocComment.line, newDocComment.column, oldDocComment.line, oldDocComment.column, oldDocComment.text));
      }
      DOC.set(newDocComment);
    }
  
    /**
     * Clear any documentation (and generate a warning if there was).
     *
     * This method should NOT be used after an optional component in a grammar
     * (i.e., after a @code{[…]} or @code{…*} construct), because the optional
     * grammar part may have already caused parsing a doc comment special token
     * placed after the code block.
     */
    static void clearDoc() {
      DocComment oldDocComment = DOC.get();
      if (oldDocComment != null) {
        WARNINGS.get()
            .add(String.format(
                "Ignoring out-of-place documentation comment at line %d, column %d: \"%s\"\n"
                    + "Did you mean to use a multiline comment ( /* ... */ ) instead?",
                oldDocComment.line, oldDocComment.column, oldDocComment.text));
      }
      DOC.remove();
    }
  
    static String getDoc() {
      DocComment docComment = DOC.get();
      DOC.remove();
      return docComment == null ? null : docComment.text;
    }
  
    /* Package private to facilitate testing */
    static String stripIndents(String doc) {
      Matcher starMatcher = STAR_INDENT.matcher(doc);
      if (starMatcher.matches()) {
        return doc.replaceAll("(?U)(?:^|(\\R)\\h*)\\Q" + starMatcher.group("stars") + "\\E\\h?", "$1");
      }
  
      Matcher whitespaceMatcher = WS_INDENT.matcher(doc);
      if (whitespaceMatcher.matches()) {
        return doc.replaceAll("(?U)(\\R)" + whitespaceMatcher.group("indent"), "$1");
      }
  
      return doc;
    }
  
    private static class DocComment {
      private final String text;
      private final int line;
      private final int column;
  
      DocComment(Token token) {
        // The token is everything after the initial '/**', including all
        // whitespace and the ending '*/'
        int tokenLength = token.image.length();
        this.text = stripIndents(token.image.substring(0, tokenLength - 2).trim());
        this.line = token.beginLine;
        // The preceding token was "/**", and the current token includes
        // everything since (also all whitespace). Thus, we can safely subtract 3
        // from the token column to get the start of the doc comment.
        this.column = token.beginColumn - 3;
      }
    }
  }
  