From e9ececeff0a05b3d99aae5a7945c938dbd8d8432 Mon Sep 17 00:00:00 2001
From: Kristina <57489792+KeyrisXdSnow@users.noreply.github.com>
Date: Wed, 10 Jul 2024 14:22:20 +0200
Subject: [PATCH] OD-17115 Fix of DslDoc generation task
---
.../ish/docs/DslGroovyRootDocBuilder.groovy | 47 +-
.../antlr/override/ArrayGroovyMethods.java | 73 +++
.../groovy/antlr/override/FormatHelper.java | 562 ++++++++++++++++++
.../antlr/override/GroovydocVisitor.java | 394 ++++++++++++
.../apache/groovy/antlr/override/Opcodes.java | 18 +
.../apache/groovy/antlr/override/README.md | 21 +
.../override/RecordTypeASTTransformation.java | 55 ++
.../antlr/override/SimpleGroovyDoc.java | 17 +
8 files changed, 1177 insertions(+), 10 deletions(-)
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java
create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java
diff --git a/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy b/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy
index a5f3a601755..7d37f21efe0 100644
--- a/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy
+++ b/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy
@@ -18,6 +18,7 @@ package au.com.ish.docs
import groovyjarjarantlr.RecognitionException
import groovyjarjarantlr.TokenStreamException
import groovyjarjarantlr.collections.AST
+import org.apache.groovy.antlr.override.GroovydocVisitor
import org.codehaus.groovy.antlr.AntlrASTProcessor
import org.codehaus.groovy.antlr.SourceBuffer
import org.codehaus.groovy.antlr.UnicodeEscapingReader
@@ -28,13 +29,13 @@ import org.codehaus.groovy.antlr.java.JavaRecognizer
import org.codehaus.groovy.antlr.parser.GroovyLexer
import org.codehaus.groovy.antlr.parser.GroovyRecognizer
import org.codehaus.groovy.antlr.treewalker.PreOrderTraversal
-import org.codehaus.groovy.antlr.treewalker.SourceCodeTraversal
import org.codehaus.groovy.antlr.treewalker.Visitor
+import org.codehaus.groovy.ast.ModuleNode
+import org.codehaus.groovy.control.*
import org.codehaus.groovy.groovydoc.GroovyClassDoc
import org.codehaus.groovy.groovydoc.GroovyRootDoc
import org.codehaus.groovy.runtime.ResourceGroovyMethods
import org.codehaus.groovy.tools.groovydoc.LinkArgument
-import org.codehaus.groovy.tools.groovydoc.SimpleGroovyClassDocAssembler
import org.codehaus.groovy.tools.groovydoc.SimpleGroovyExecutableMemberDoc
import org.codehaus.groovy.tools.groovydoc.SimpleGroovyPackageDoc
import org.codehaus.groovy.tools.groovydoc.SimpleGroovyRootDoc
@@ -74,11 +75,11 @@ class DslGroovyRootDocBuilder {
unicodeReader.setLexer(lexer)
parser = GroovyRecognizer.make(lexer)
}
- parser.setSourceBuffer(sourceBuffer)
- parser.compilationUnit()
- AST ast = parser.getAST()
-
if (isJava) {
+ parser.setSourceBuffer(sourceBuffer)
+ parser.compilationUnit()
+ AST ast = parser.getAST()
+
// modify the Java AST into a Groovy AST (just token types)
Visitor java2groovyConverter = new Java2GroovyConverter(parser.getTokenNames())
AntlrASTProcessor java2groovyTraverser = new PreOrderTraversal(java2groovyConverter)
@@ -89,10 +90,36 @@ class DslGroovyRootDocBuilder {
AntlrASTProcessor groovifierTraverser = new PreOrderTraversal(groovifier)
groovifierTraverser.process(ast)
}
- Visitor visitor = new SimpleGroovyClassDocAssembler(packagePath, file, sourceBuffer, links, properties, !isJava)
- AntlrASTProcessor traverser = new SourceCodeTraversal(visitor)
- traverser.process(ast)
- return ((SimpleGroovyClassDocAssembler)visitor).getGroovyClassDocs()
+
+ CompilerConfiguration config = new CompilerConfiguration()
+ config.getOptimizationOptions().put(CompilerConfiguration.GROOVYDOC, true)
+ CompilationUnit compUnit = new CompilationUnit(config)
+ SourceUnit unit = new SourceUnit(file, src, config, null, new ErrorCollector(config));
+ compUnit.addSource(unit);
+ int phase = Phases.CONVERSION;
+ if (properties.containsKey("phaseOverride")) {
+ String raw = properties.getProperty("phaseOverride")
+ try {
+ phase = Integer.parseInt(raw)
+ } catch(NumberFormatException ignore) {
+ raw = raw.toUpperCase();
+ switch(raw) {
+ // some dup here but kept simple since we may swap Phases to an enum
+ case "CONVERSION": phase = 3; break;
+ case "SEMANTIC_ANALYSIS": phase = 4; break;
+ case "CANONICALIZATION": phase = 5; break;
+ case "INSTRUCTION_SELECTION": phase = 6; break;
+ case "CLASS_GENERATION": phase = 7; break;
+ default:
+ System.err.println("Ignoring unrecognised or unsuitable phase and keeping default");
+ }
+ }
+ }
+ compUnit.compile(phase);
+ ModuleNode root = unit.getAST()
+ GroovydocVisitor visitor = new GroovydocVisitor(unit, packagePath, links, properties)
+ root.getClasses().forEach(clazz -> visitor.visitClass(clazz))
+ return visitor.getGroovyClassDocs()
}
protected void setOverview() {
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java
new file mode 100644
index 00000000000..3d5a52aa086
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+/*
+ * 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
+ *
+ * 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 org.apache.groovy.antlr.override;
+
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport;
+import org.codehaus.groovy.util.ArrayIterator;
+
+/**
+ * Defines new groovy methods which appear on arrays inside the Groovy environment.
+ * Static methods are used with the first parameter being the destination class,
+ * i.e. public static int[] each(int[] self, Closure closure)
+ * provides an each({i -> })
method for int[]
.
+ *
+ * NOTE: While this class contains many 'public' static methods, it is
+ * primarily regarded as an internal class (its internal package name
+ * suggests this also). We value backwards compatibility of these
+ * methods when used within Groovy but value less backwards compatibility
+ * at the Java method call level. I.e. future versions of Groovy may
+ * remove or move a method call in this file but would normally
+ * aim to keep the method available from within Groovy.
+ */
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+
+public class ArrayGroovyMethods extends DefaultGroovyMethodsSupport {
+
+ private ArrayGroovyMethods() {
+ }
+
+ /**
+ * Concatenates the string representation of each item in this array,
+ * with the given String as a separator between each item.
+ *
+ *
+ * Serializable[] array = [1,2L,-3G]
+ * assert array.join("+") == "1+2+-3"
+ *
+ *
+ * @param self an array of Object
+ * @param separator a String separator
+ * @return the joined String
+ * @since 1.0
+ */
+ public static String join(Object[] self, String separator) {
+ return DefaultGroovyMethods.join(new ArrayIterator<>(self), separator);
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java
new file mode 100644
index 00000000000..8e9d443b69b
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+/*
+ * 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
+ *
+ * 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 org.apache.groovy.antlr.override;
+
+import groovy.lang.GroovyRuntimeException;
+import groovy.lang.GroovySystem;
+import groovy.lang.MetaClassRegistry;
+import groovy.lang.Range;
+import groovy.lang.Writable;
+import groovy.transform.NamedParam;
+import groovy.transform.NamedParams;
+import org.apache.groovy.io.StringBuilderWriter;
+import org.codehaus.groovy.control.ResolveVisitor;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.runtime.NullObject;
+import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+import org.w3c.dom.Element;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static java.lang.Math.max;
+import static java.util.Arrays.stream;
+
+/**
+ * Formatting methods
+ */
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+
+public class FormatHelper {
+
+ private FormatHelper() {}
+
+ private static final String SQ = "'";
+ private static final String DQ = "\"";
+
+ private static final Object[] EMPTY_ARGS = {};
+
+ // heuristic size to pre-allocate stringbuffers for collections of items
+ private static final int ITEM_ALLOCATE_SIZE = 5;
+
+ public static final MetaClassRegistry metaRegistry = GroovySystem.getMetaClassRegistry();
+ private static final String XMLUTIL_CLASS_FULL_NAME = "groovy.xml.XmlUtil";
+ private static final String SERIALIZE_METHOD_NAME = "serialize";
+
+ static final Set DEFAULT_IMPORT_PKGS = new HashSet<>();
+ static final Set DEFAULT_IMPORT_CLASSES = new HashSet<>();
+
+ static {
+ for (String pkgName : ResolveVisitor.DEFAULT_IMPORTS) {
+ FormatHelper.DEFAULT_IMPORT_PKGS.add(pkgName.substring(0, pkgName.length() - 1));
+ }
+ FormatHelper.DEFAULT_IMPORT_CLASSES.add("java.math.BigDecimal");
+ FormatHelper.DEFAULT_IMPORT_CLASSES.add("java.math.BigInteger");
+ }
+
+ public static String toString(Object arguments) {
+ return format(arguments, false, -1, false);
+ }
+
+ public static String inspect(Object self) {
+ return format(self, true);
+ }
+
+ public static String format(Object arguments, boolean verbose) {
+ return format(arguments, verbose, -1);
+ }
+
+ public static String format(Object arguments, boolean inspect, boolean escapeBackslashes) {
+ return format(arguments, inspect, -1);
+ }
+
+ public static String format(Object arguments, boolean verbose, int maxSize) {
+ return format(arguments, verbose, maxSize, false);
+ }
+
+ public static String format(Object arguments, boolean inspect, boolean escapeBackslashes, int maxSize) {
+ return format(arguments, inspect, escapeBackslashes, maxSize, false);
+ }
+
+ /**
+ * Output the {@code toString} for the argument(s) with various options to configure.
+ * Configuration options:
+ *
+ *
+ * - safe
- provides protection if the {@code toString} throws an exception, in which case the exception is swallowed and a dumber default {@code toString} is used
+ * - maxSize
- will attempt to truncate the output to fit approx the maxSize number of characters, -1 means don't truncate
+ * - inspect
- if false, render a value by its {@code toString}, otherwise use its {@code inspect} value
+ * - escapeBackSlashes
- whether characters like tab, newline, etc. are converted to their escaped rendering ('\t', '\n', etc.)
+ * - verbose
- shorthand to turn on both {@code inspect} and {@code escapeBackslashes}
+ *
+ *
+ *
+ * @param options a map of configuration options
+ * @param arguments the argument(s) to calculate the {@code toString} for
+ * @return the string rendering of the argument(s)
+ * @see DefaultGroovyMethods#inspect(Object)
+ */
+ public static String toString(@NamedParams({
+ @NamedParam(value = "safe", type = Boolean.class),
+ @NamedParam(value = "maxSize", type = Integer.class),
+ @NamedParam(value = "verbose", type = Boolean.class),
+ @NamedParam(value = "escapeBackslashes", type = Boolean.class),
+ @NamedParam(value = "inspect", type = Boolean.class)
+ }) Map options, Object arguments) {
+ Object safe = options.get("safe");
+ if (!(safe instanceof Boolean)) safe = false;
+ Object maxSize = options.get("maxSize");
+ if (!(maxSize instanceof Integer)) maxSize = -1;
+ Object verbose = options.get("verbose");
+ Object inspect = options.get("inspect");
+ Object escapeBackslashes = options.get("escapeBackslashes");
+ if (!(inspect instanceof Boolean)) inspect = false;
+ if (!(escapeBackslashes instanceof Boolean)) escapeBackslashes = false;
+ if (!(verbose instanceof Boolean)) verbose = false;
+ if (Boolean.TRUE.equals(verbose)) {
+ inspect = true;
+ escapeBackslashes = true;
+ }
+ return format(arguments, (boolean) inspect, (boolean) escapeBackslashes, (int) maxSize, (boolean) safe);
+ }
+
+ public static String format(Object arguments, boolean verbose, int maxSize, boolean safe) {
+ return format(arguments, verbose, verbose, maxSize, safe);
+ }
+
+ public static String format(Object arguments, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) {
+ if (arguments == null) {
+ final NullObject nullObject = NullObject.getNullObject();
+ return (String) nullObject.getMetaClass().invokeMethod(nullObject, "toString", EMPTY_ARGS);
+ }
+ if (arguments.getClass().isArray()) {
+ if (arguments instanceof Object[]) {
+ return toArrayString((Object[]) arguments, inspect, escapeBackslashes, maxSize, safe);
+ }
+ if (arguments instanceof char[]) {
+ return new String((char[]) arguments);
+ }
+ // other primitives
+ return formatCollection(DefaultTypeTransformation.arrayAsCollection(arguments), inspect, escapeBackslashes, maxSize, safe);
+ }
+ if (arguments instanceof Range) {
+ Range range = (Range) arguments;
+ try {
+ if (inspect) {
+ return range.inspect();
+ } else {
+ return range.toString();
+ }
+ } catch (RuntimeException ex) {
+ if (!safe) throw ex;
+ return handleFormattingException(arguments, ex);
+ } catch (Exception ex) {
+ if (!safe) throw new GroovyRuntimeException(ex);
+ return handleFormattingException(arguments, ex);
+ }
+ }
+ if (arguments instanceof Collection) {
+ return formatCollection((Collection) arguments, inspect, escapeBackslashes, maxSize, safe);
+ }
+ if (arguments instanceof Map) {
+ return formatMap((Map) arguments, inspect, escapeBackslashes, maxSize, safe);
+ }
+ if (arguments instanceof Element) {
+ try {
+ Method serialize = Class.forName(XMLUTIL_CLASS_FULL_NAME).getMethod(SERIALIZE_METHOD_NAME, Element.class);
+ return (String) serialize.invoke(null, arguments);
+ } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (arguments instanceof CharSequence) {
+ String arg = escapeBackslashes ? escapeBackslashes(arguments.toString()) : arguments.toString();
+ if (arguments instanceof String) {
+ if (!inspect) return arg;
+ return !escapeBackslashes && multiline(arg) ? "'''" + arg + "'''" : SQ + arg.replace(SQ, "\\'") + SQ;
+ }
+ if (!inspect) return arg;
+ return !escapeBackslashes && multiline(arg) ? "\"\"\"" + arg + "\"\"\"" : DQ + arg.replace(DQ, "\\\"") + DQ;
+ }
+ try {
+ // TODO: For GROOVY-2599 do we need something like below but it breaks other things
+// return (String) invokeMethod(arguments, "toString", EMPTY_ARGS);
+ return arguments.toString();
+ } catch (RuntimeException ex) {
+ if (!safe) throw ex;
+ return handleFormattingException(arguments, ex);
+ } catch (Exception ex) {
+ if (!safe) throw new GroovyRuntimeException(ex);
+ return handleFormattingException(arguments, ex);
+ }
+ }
+
+ private static boolean multiline(String s) {
+ return s.contains("\n") || s.contains("\r");
+ }
+
+ public static String escapeBackslashes(String orig) {
+ // must replace backslashes first, as the other replacements add backslashes not to be escaped
+ return orig
+ .replace("\\", "\\\\") // backslash
+ .replace("\n", "\\n") // line feed
+ .replace("\r", "\\r") // carriage return
+ .replace("\t", "\\t") // tab
+ .replace("\f", "\\f"); // form feed
+ }
+
+ private static String handleFormattingException(Object item, Exception ex) {
+
+ String hash;
+ try {
+ hash = Integer.toHexString(item.hashCode());
+ } catch (Exception ignored) {
+ hash = "????";
+ }
+ return "<" + typeName(item) + "@" + hash + ">";
+ }
+
+ private static String formatMap(Map map, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) {
+ if (map.isEmpty()) {
+ return "[:]";
+ }
+ StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * map.size() * 2);
+ buffer.append('[');
+ boolean first = true;
+ for (Object o : map.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(", ");
+ }
+ if (maxSize != -1 && buffer.length() > maxSize) {
+ buffer.append("...");
+ break;
+ }
+ Map.Entry entry = (Map.Entry) o;
+ if (entry.getKey() == map) {
+ buffer.append("(this Map)");
+ } else {
+ buffer.append(format(entry.getKey(), inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe));
+ }
+ buffer.append(":");
+ if (entry.getValue() == map) {
+ buffer.append("(this Map)");
+ } else {
+ buffer.append(format(entry.getValue(), inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe));
+ }
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+
+ private static int sizeLeft(int maxSize, StringBuilder buffer) {
+ return maxSize == -1 ? maxSize : max(0, maxSize - buffer.length());
+ }
+
+ private static String formatCollection(Collection collection, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) {
+ StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * collection.size());
+ buffer.append('[');
+ boolean first = true;
+ for (Object item : collection) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(", ");
+ }
+ if (maxSize != -1 && buffer.length() > maxSize) {
+ buffer.append("...");
+ break;
+ }
+ if (item == collection) {
+ buffer.append("(this Collection)");
+ } else {
+ buffer.append(format(item, inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe));
+ }
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+
+ /**
+ * A helper method to format the arguments types as a comma-separated list.
+ *
+ * @param arguments the type to process
+ * @return the string representation of the type
+ */
+ public static String toTypeString(Object[] arguments) {
+ return toTypeString(arguments, -1);
+ }
+
+ /**
+ * A helper method to format the arguments types as a comma-separated list.
+ *
+ * @param arguments the type to process
+ * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop
+ * @return the string representation of the type
+ */
+ public static String toTypeString(Object[] arguments, int maxSize) {
+ if (arguments == null) {
+ return "null";
+ }
+ if (arguments.length == 0) {
+ return "";
+ }
+ if (maxSize < 0) {
+ return stream(arguments)
+ .map(arg -> arg != null ? typeName(arg) : "null")
+ .collect(java.util.stream.Collectors.joining(", "));
+ }
+
+ var plainForm = new StringBuilder();
+ var shortForm = new StringBuilder();
+ for (int i = 0; i < arguments.length; i += 1) {
+ String type = arguments[i] != null ? typeName(arguments[i]) : "null";
+
+ if (plainForm.length() < maxSize) {
+ if (i > 0) plainForm.append(", ");
+ plainForm.append(type);
+ } else if (plainForm.charAt(plainForm.length() - 1) != '.') {
+ plainForm.append("...");
+ }
+
+ if (shortForm.length() < maxSize) {
+ if (i > 0) shortForm.append(", ");
+ String[] tokens = type.split("\\.");
+ for (int j = 0; j < tokens.length - 1; j += 1) {
+ // GROOVY-11270: reduce "foo.bar.Baz" to "f.b.Baz"
+ shortForm.appendCodePoint(tokens[j].codePointAt(0)).append('.');
+ }
+ shortForm.append(tokens[tokens.length - 1]);
+ } else {
+ shortForm.append("...");
+ break;
+ }
+ }
+
+ return (plainForm.length() <= maxSize ? plainForm : shortForm).toString();
+ }
+
+ /**
+ * Gets the type name
+ *
+ * @param argument the object to find the type for
+ * @return the type name (slightly pretty format taking into account default imports)
+ */
+ static String typeName(Object argument) {
+ Class> aClass = argument.getClass();
+ String pkgName = aClass.getPackage() == null ? "" : aClass.getPackage().getName();
+ boolean useShort = DEFAULT_IMPORT_PKGS.contains(pkgName) || DEFAULT_IMPORT_CLASSES.contains(aClass.getName());
+ return useShort ? aClass.getSimpleName() : aClass.getName();
+ }
+
+ /**
+ * A helper method to return the string representation of a map with bracket boundaries "[" and "]".
+ *
+ * @param arg the map to process
+ * @return the string representation of the map
+ */
+ public static String toMapString(Map arg) {
+ return toMapString(arg, -1);
+ }
+
+ /**
+ * A helper method to return the string representation of a map with bracket boundaries "[" and "]".
+ *
+ * @param arg the map to process
+ * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop
+ * @return the string representation of the map
+ */
+ public static String toMapString(Map arg, int maxSize) {
+ return formatMap(arg, false, false, maxSize, false);
+ }
+
+ /**
+ * A helper method to return the string representation of a list with bracket boundaries "[" and "]".
+ *
+ * @param arg the collection to process
+ * @return the string representation of the collection
+ */
+ public static String toListString(Collection arg) {
+ return toListString(arg, -1);
+ }
+
+ /**
+ * A helper method to return the string representation of a list with bracket boundaries "[" and "]".
+ *
+ * @param arg the collection to process
+ * @param maxSize stop after approximately this many characters and append '...'
+ * @return the string representation of the collection
+ */
+ public static String toListString(Collection arg, int maxSize) {
+ return toListString(arg, maxSize, false);
+ }
+
+ /**
+ * A helper method to return the string representation of a list with bracket boundaries "[" and "]".
+ *
+ * @param arg the collection to process
+ * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop
+ * @param safe whether to use a default object representation for any item in the collection if an exception occurs when generating its toString
+ * @return the string representation of the collection
+ */
+ public static String toListString(Collection arg, int maxSize, boolean safe) {
+ return formatCollection(arg, false, false, maxSize, safe);
+ }
+
+ /**
+ * A helper method to return the string representation of an array of objects
+ * with brace boundaries "[" and "]".
+ *
+ * @param arguments the array to process
+ * @return the string representation of the array
+ */
+ public static String toArrayString(Object[] arguments) {
+ return toArrayString(arguments, false, false, -1, false);
+ }
+
+ private static String toArrayString(Object[] array, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) {
+ if (array == null) {
+ return "null";
+ }
+ boolean first = true;
+ StringBuilder argBuf = new StringBuilder(array.length);
+ argBuf.append('[');
+
+ for (Object item : array) {
+ if (first) {
+ first = false;
+ } else {
+ argBuf.append(", ");
+ }
+ if (maxSize != -1 && argBuf.length() > maxSize) {
+ argBuf.append("...");
+ break;
+ }
+ if (item == array) {
+ argBuf.append("(this array)");
+ } else {
+ argBuf.append(format(item, inspect, escapeBackslashes, sizeLeft(maxSize, argBuf), safe));
+ }
+ }
+ argBuf.append(']');
+ return argBuf.toString();
+ }
+
+ /**
+ * A helper method to return the string representation of an array of objects
+ * with brace boundaries "[" and "]".
+ *
+ * @param arguments the array to process
+ * @param maxSize stop after approximately this many characters and append '...'
+ * @param safe whether to use a default object representation for any item in the array if an exception occurs when generating its toString
+ * @return the string representation of the array
+ */
+ public static String toArrayString(Object[] arguments, int maxSize, boolean safe) {
+ return toArrayString(arguments, false, false, maxSize, safe);
+ }
+
+ /**
+ * Writes an object to a Writer using Groovy's default representation for the object.
+ */
+ public static void write(Writer out, Object object) throws IOException {
+ if (object instanceof String) {
+ out.write((String) object);
+ } else if (object instanceof Object[]) {
+ out.write(toArrayString((Object[]) object));
+ } else if (object instanceof Map) {
+ out.write(toMapString((Map) object));
+ } else if (object instanceof Collection) {
+ out.write(toListString((Collection) object));
+ } else if (object instanceof Writable) {
+ Writable writable = (Writable) object;
+ writable.writeTo(out);
+ } else if (object instanceof InputStream || object instanceof Reader) {
+ // Copy stream to stream
+ Reader reader;
+ if (object instanceof InputStream) {
+ reader = new InputStreamReader((InputStream) object);
+ } else {
+ reader = (Reader) object;
+ }
+
+ try (Reader r = reader) {
+ char[] chars = new char[8192];
+ for (int i; (i = r.read(chars)) != -1; ) {
+ out.write(chars, 0, i);
+ }
+ }
+ } else {
+ out.write(toString(object));
+ }
+ }
+
+ /**
+ * Appends an object to an Appendable using Groovy's default representation for the object.
+ */
+ public static void append(Appendable out, Object object) throws IOException {
+ if (object instanceof String) {
+ out.append((String) object);
+ } else if (object instanceof Object[]) {
+ out.append(toArrayString((Object[]) object));
+ } else if (object instanceof Map) {
+ out.append(toMapString((Map) object));
+ } else if (object instanceof Collection) {
+ out.append(toListString((Collection) object));
+ } else if (object instanceof Writable) {
+ Writable writable = (Writable) object;
+ Writer stringWriter = new StringBuilderWriter();
+ writable.writeTo(stringWriter);
+ out.append(stringWriter.toString());
+ } else if (object instanceof InputStream || object instanceof Reader) {
+ // Copy stream to stream
+ try (Reader reader =
+ object instanceof InputStream
+ ? new InputStreamReader((InputStream) object)
+ : (Reader) object) {
+ char[] chars = new char[8192];
+ for (int i; (i = reader.read(chars)) != -1; ) {
+ for (int j = 0; j < i; j++) {
+ out.append(chars[j]);
+ }
+ }
+ }
+ } else {
+ out.append(toString(object));
+ }
+ }
+}
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java
new file mode 100644
index 00000000000..f8d5a0dcaeb
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+/*
+ * 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
+ *
+ * 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.
+ */
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+package org.apache.groovy.antlr.override;
+
+import groovy.lang.groovydoc.Groovydoc;
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.control.ResolveVisitor;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.groovydoc.GroovyClassDoc;
+import org.codehaus.groovy.groovydoc.GroovyFieldDoc;
+import org.codehaus.groovy.groovydoc.GroovyMethodDoc;
+import org.codehaus.groovy.tools.groovydoc.*;
+
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.groovy.antlr.override.RecordTypeASTTransformation.recordNative;
+import static org.codehaus.groovy.transform.trait.Traits.isTrait;
+
+/**
+ * A visitor which collects Groovydoc information.
+ */
+public class GroovydocVisitor extends ClassCodeVisitorSupport {
+ private final SourceUnit unit;
+ private final List links;
+ private String packagePath;
+ private SimpleGroovyClassDoc currentClassDoc = null;
+ private Map classDocs = new HashMap<>();
+ private final Properties properties;
+ private static final String FS = "/";
+
+ public GroovydocVisitor(final SourceUnit unit, String packagePath, List links) {
+ this(unit, packagePath, links, new Properties());
+ }
+
+ public GroovydocVisitor(final SourceUnit unit, String packagePath, List links, Properties properties) {
+ this.unit = unit;
+ this.packagePath = packagePath;
+ this.links = links;
+ this.properties = properties;
+ }
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return unit;
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ final Map aliases = new HashMap<>();
+ final List imports = new ArrayList<>();
+ for (ImportNode iNode : node.getModule().getImports()) {
+ String name = iNode.getClassName();
+ imports.add(name.replace('.', '/'));
+ if (iNode.getAlias() != null && !iNode.getAlias().isEmpty()) {
+ aliases.put(iNode.getAlias(), name.replace('.', '/'));
+ }
+ }
+ for (ImportNode iNode : node.getModule().getStarImports()) {
+ String name = iNode.getPackageName()+"*";
+ imports.add(name.replace('.', '/'));
+ }
+ String name = node.getNameWithoutPackage();
+
+ if (node instanceof InnerClassNode) {
+ name = name.replace('$', '.');
+ }
+ currentClassDoc = new SimpleGroovyClassDoc(withDefaultImports(imports), aliases, name, links);
+ if (node.isEnum()) {
+ currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.ENUM_DEF);
+ } else if (recordNative(node)) { // node is record
+ currentClassDoc.setTokenType(org.apache.groovy.antlr.override.SimpleGroovyDoc.RECORD_DEF);
+ } else if (node.isAnnotationDefinition()) {
+ currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.ANNOTATION_DEF);
+ } else if (isTrait(node)) {
+ currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.TRAIT_DEF);
+ } else if (node.isInterface()) {
+ currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.INTERFACE_DEF);
+ }
+ if (node.isScript()) {
+ if ("false".equals(properties.getProperty("processScripts", "true"))) return;
+ currentClassDoc.setScript(true);
+ }
+ for (ClassNode iface : node.getInterfaces()) {
+ currentClassDoc.addInterfaceName(makeType(iface));
+ }
+ currentClassDoc.setRawCommentText(getDocContent(node.getGroovydoc()));
+ currentClassDoc.setNameWithTypeArgs(name + genericTypesAsString(node.getGenericsTypes()));
+ if (!node.isInterface() && !node.isEnum() && node.getSuperClass() != null) {
+ String superName = makeType(node.getSuperClass());
+ currentClassDoc.setSuperClassName(superName);
+ String superSimpleName = node.getSuperClass().getNameWithoutPackage();
+ if (!classDocs.containsKey(superSimpleName)) {
+ SimpleGroovyClassDoc superDoc = new SimpleGroovyClassDoc(imports, superName);
+ superDoc.setFullPathName(superName);
+ }
+ }
+ processModifiers(currentClassDoc, node, node.getModifiers());
+ processAnnotations(currentClassDoc, node);
+ if (Modifier.isAbstract(node.getModifiers())) {
+ currentClassDoc.setAbstract(true);
+ }
+ currentClassDoc.setFullPathName(packagePath + FS + name);
+ currentClassDoc.setGroovy(true);
+ classDocs.put(currentClassDoc.getFullPathName(), currentClassDoc);
+ super.visitClass(node);
+ SimpleGroovyClassDoc parent = currentClassDoc;
+ if (currentClassDoc.isClass() && currentClassDoc.constructors().length == 0) {
+ // add default no-arg constructor, but not for interfaces, traits, enums, or annotation definitions
+ SimpleGroovyConstructorDoc cons = new SimpleGroovyConstructorDoc(name, currentClassDoc);
+ cons.setPublic(true);
+ currentClassDoc.add(cons);
+ }
+ Iterator innerClasses = node.getInnerClasses();
+ while (innerClasses.hasNext()) {
+ visitClass(innerClasses.next());
+ parent.addNested(currentClassDoc);
+ currentClassDoc = parent;
+ }
+ }
+
+ private List withDefaultImports(List imports) {
+ imports = imports != null ? imports : new ArrayList<>();
+ imports.add(packagePath + "/*"); // everything in this package
+ for (String pkg : ResolveVisitor.DEFAULT_IMPORTS) {
+ imports.add(pkg.replace('.', '/') + "*");
+ }
+ return imports;
+ }
+
+ private static final Pattern JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/");
+
+ private String getDocContent(Groovydoc groovydoc) {
+ if (groovydoc == null) return "";
+ String result = groovydoc.getContent();
+ if (result == null) result = "";
+ Matcher m = JAVADOC_COMMENT_PATTERN.matcher(result);
+ if (m.find()) {
+ result = m.group(1).trim();
+ }
+ return result;
+ }
+
+ private void processAnnotations(SimpleGroovyProgramElementDoc element, AnnotatedNode node) {
+ for (AnnotationNode an : node.getAnnotations()) {
+ String name = an.getClassNode().getName();
+ element.addAnnotationRef(new SimpleGroovyAnnotationRef(name, an.getText()));
+ }
+ }
+
+ private void processAnnotations(SimpleGroovyParameter param, AnnotatedNode node) {
+ for (AnnotationNode an : node.getAnnotations()) {
+ String name = an.getClassNode().getName();
+ param.addAnnotationRef(new SimpleGroovyAnnotationRef(name, an.getText()));
+ }
+ }
+
+ @Override
+ public void visitConstructor(ConstructorNode node) {
+ if (node.isSynthetic()) return;
+ SimpleGroovyConstructorDoc cons = new SimpleGroovyConstructorDoc(currentClassDoc.simpleTypeName(), currentClassDoc);
+ setConstructorOrMethodCommon(node, cons);
+ currentClassDoc.add(cons);
+ super.visitConstructor(node);
+ }
+
+ @Override
+ public void visitMethod(MethodNode node) {
+ if (currentClassDoc.isEnum() && "$INIT".equals(node.getName()))
+ return;
+ if (node.isSynthetic()) return;
+ if ("false".equals(properties.getProperty("includeMainForScripts", "true"))
+ && currentClassDoc.isScript() && "main".equals(node.getName()) && node.isStatic() && node.getParameters().length == 1)
+ return;
+
+ SimpleGroovyMethodDoc meth = new SimpleGroovyMethodDoc(node.getName(), currentClassDoc);
+ meth.setReturnType(new SimpleGroovyType(makeType(node.getReturnType())));
+ setConstructorOrMethodCommon(node, meth);
+ currentClassDoc.add(meth);
+ processPropertiesFromGetterSetter(meth);
+ super.visitMethod(node);
+ meth.setTypeParameters(genericTypesAsString(node.getGenericsTypes()));
+ }
+
+ private String genericTypesAsString(GenericsType[] genericsTypes) {
+ if (genericsTypes == null || genericsTypes.length == 0)
+ return "";
+ return "<" + ArrayGroovyMethods.join(genericsTypes, ", ") + ">";
+ }
+
+ private void processPropertiesFromGetterSetter(SimpleGroovyMethodDoc currentMethodDoc) {
+ String methodName = currentMethodDoc.name();
+ int len = methodName.length();
+ String prefix;
+ String propName;
+ if (len > 3 && methodName.startsWith("get")) {
+ prefix = "get";
+ propName = methodName.substring(3);
+ } else if (len > 3 && methodName.startsWith("set")) {
+ prefix = "set";
+ propName = methodName.substring(3);
+ } else if (len > 2 && methodName.startsWith("is")) {
+ prefix = "is";
+ propName = methodName.substring(2);
+ } else {
+ // Not a (get/set/is) method that contains a property name
+ return;
+ }
+
+ for (GroovyFieldDoc field : currentClassDoc.properties()) {
+ if (propName.equals(field.name())) return;
+ }
+ SimpleGroovyClassDoc classDoc = currentClassDoc;
+ // TODO: not sure why but groovy.ui.view.BasicContentPane#buildOutputArea classDoc is null
+ if (classDoc == null) {
+ return;
+ }
+ GroovyMethodDoc[] methods = classDoc.methods();
+
+ //find expected method name
+ String expectedMethodName;
+ if ("set".equals(prefix) && (currentMethodDoc.parameters().length >= 1 && !currentMethodDoc.parameters()[0].typeName().equals("boolean"))) {
+ expectedMethodName = "get" + propName;
+ } else if ("get".equals(prefix) && !currentMethodDoc.returnType().typeName().equals("boolean")) {
+ expectedMethodName = "set" + propName;
+ } else if ("is".equals(prefix)) {
+ expectedMethodName = "set" + propName;
+ } else {
+ expectedMethodName = "is" + propName;
+ }
+
+ for (GroovyMethodDoc methodDoc : methods) {
+ if (methodDoc.name().equals(expectedMethodName)) {
+
+ //extract the field name
+ String fieldName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
+ SimpleGroovyFieldDoc currentFieldDoc = new SimpleGroovyFieldDoc(fieldName, classDoc);
+
+ //find the type of the field; if it's a setter, need to get the type of the params
+ if (expectedMethodName.startsWith("set") && methodDoc.parameters().length >= 1) {
+ String typeName = methodDoc.parameters()[0].typeName();
+ currentFieldDoc.setType(new SimpleGroovyType(typeName));
+ } else {
+ //if it's not setter, get the type info of the return type of the get* method
+ currentFieldDoc.setType(methodDoc.returnType());
+ }
+
+ if (methodDoc.isPublic() && currentMethodDoc.isPublic()) {
+ classDoc.addProperty(currentFieldDoc);
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void visitProperty(PropertyNode node) {
+ String name = node.getName();
+ SimpleGroovyFieldDoc fieldDoc = new SimpleGroovyFieldDoc(name, currentClassDoc);
+ fieldDoc.setType(new SimpleGroovyType(makeType(node.getType())));
+ int mods = node.getModifiers();
+ if (!hasAnno(node.getField(), "PackageScope")) {
+ processModifiers(fieldDoc, node.getField(), mods);
+ Groovydoc groovydoc = node.getGroovydoc();
+ fieldDoc.setRawCommentText(groovydoc == null ? "" : getDocContent(groovydoc));
+ currentClassDoc.addProperty(fieldDoc);
+ }
+ processAnnotations(fieldDoc, node.getField());
+ super.visitProperty(node);
+ }
+
+ private String makeType(ClassNode node) {
+ final ClassNode cn = node.isArray() ? node.getComponentType() : node;
+ return cn.getName().replace('.', '/').replace('$', '.')
+ + genericTypesAsString(cn.getGenericsTypes())
+ + (node.isArray() ? "[]" : "");
+ }
+
+ @Override
+ public void visitDeclarationExpression(DeclarationExpression expression) {
+ if (currentClassDoc.isScript()) {
+ if (hasAnno(expression, "Field")) {
+ VariableExpression varx = expression.getVariableExpression();
+ SimpleGroovyFieldDoc field = new SimpleGroovyFieldDoc(varx.getName(), currentClassDoc);
+ field.setType(new SimpleGroovyType(makeType(varx.getType())));
+ int mods = varx.getModifiers();
+ processModifiers(field, varx, mods);
+ boolean isProp = (mods & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0;
+ if (isProp) {
+ currentClassDoc.addProperty(field);
+ } else {
+ currentClassDoc.add(field);
+ }
+ }
+ }
+ super.visitDeclarationExpression(expression);
+ }
+
+ private void processModifiers(SimpleGroovyProgramElementDoc element, AnnotatedNode node, int mods) {
+ if (Modifier.isStatic(mods)) {
+ element.setStatic(true);
+ }
+ if (hasAnno(node, "PackageScope")) {
+ element.setPackagePrivate(true);
+ } else {
+ if (Modifier.isPublic(mods)) {
+ element.setPublic(true);
+ } else if (Modifier.isProtected(mods)) {
+ element.setProtected(true);
+ } else if (Modifier.isPrivate(mods)) {
+ element.setPrivate(true);
+ } else {
+ element.setPackagePrivate(true);
+ }
+ }
+ if (Modifier.isFinal(mods)) {
+ element.setFinal(true);
+ }
+ }
+
+ @Override
+ public void visitField(FieldNode node) {
+ if (node.isSynthetic()) return;
+ String name = node.getName();
+ SimpleGroovyFieldDoc fieldDoc = new SimpleGroovyFieldDoc(name, currentClassDoc);
+ fieldDoc.setType(new SimpleGroovyType(makeType(node.getType())));
+ processModifiers(fieldDoc, node, node.getModifiers());
+ processAnnotations(fieldDoc, node);
+ fieldDoc.setRawCommentText(getDocContent(node.getGroovydoc()));
+ if (node.isEnum()) {
+ currentClassDoc.addEnumConstant(fieldDoc);
+ } else {
+ currentClassDoc.add(fieldDoc);
+ }
+ super.visitField(node);
+ }
+
+ private void setConstructorOrMethodCommon(MethodNode node, SimpleGroovyExecutableMemberDoc methOrCons) {
+ methOrCons.setRawCommentText(getDocContent(node.getGroovydoc()));
+ processModifiers(methOrCons, node, node.getModifiers());
+ processAnnotations(methOrCons, node);
+ if (node.isAbstract()) {
+ methOrCons.setAbstract(true);
+ }
+ for (Parameter param : node.getParameters()) {
+ SimpleGroovyParameter p = new SimpleGroovyParameter(param.getName());
+ p.setType(new SimpleGroovyType(makeType(param.getType())));
+ processAnnotations(p, param);
+ methOrCons.add(p);
+ }
+ }
+
+ private boolean hasAnno(AnnotatedNode node, String annoSuffix) {
+ for (AnnotationNode annotationNode : node.getAnnotations()) {
+ // check name to cover non/resolved cases
+ if (annotationNode.getClassNode().getName().endsWith(annoSuffix)) return true;
+ }
+ return false;
+ }
+
+ public Map getGroovyClassDocs() {
+ return classDocs;
+ }
+}
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java
new file mode 100644
index 00000000000..6528e723451
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+package org.apache.groovy.antlr.override;
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+
+public class Opcodes {
+
+ public static final int ACC_PUBLIC = 0x0001; // class, field, method;
+ public static final int ACC_PRIVATE = 0x0002; // class, field, method
+ public static final int ACC_PROTECTED = 0x0004; // class, field, method
+}
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md
new file mode 100644
index 00000000000..ba16b94509c
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md
@@ -0,0 +1,21 @@
+GroovyDocVisitor is a special class for Parsing Groovy documents that was introduced in version > 4.
+
+Since this class is not yet in the version used, it was copied into the project and also included manually.
+
+After updating Groovy > 4, all contents of the package
+
+`buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override`
+
+can be removed and standard Document Parsing mechanisms can be used.
+
+// TODO after Groovy update
+
+Change class [DslGroovyRootDocBuilder.groovy](..%2F..%2F..%2F..%2F..%2F..%2Fgroovy%2Fau%2Fcom%2Fish%2Fdocs%2FDslGroovyRootDocBuilder.groovy)
+
+````
+private Map parseGroovy(String src, String packagePath, String file) {
+ ...
+}
+````
+
+Acourding to https://issues.apache.org/jira/browse/GROOVY-11269
\ No newline at end of file
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java
new file mode 100644
index 00000000000..f973dca8aa7
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+/*
+ * 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
+ *
+ * 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 org.apache.groovy.antlr.override;
+
+import org.apache.groovy.lang.annotation.Incubating;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+/**
+ * Handles generation of code for the @RecordType annotation.
+ */
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+
+@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
+public class RecordTypeASTTransformation {
+
+ private static final String RECORD_CLASS_NAME = "java.lang.Record";
+
+ /**
+ * Indicates that the given classnode is a native JVM record class.
+ * For classes being compiled, this will only be valid after the
+ * {@code RecordTypeASTTransformation} transform has been invoked.
+ */
+ @Incubating
+ public static boolean recordNative(final ClassNode node) {
+ return node.getUnresolvedSuperClass() != null && RECORD_CLASS_NAME.equals(node.getUnresolvedSuperClass().getName());
+ }
+
+}
diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java
new file mode 100644
index 00000000000..b792999d1e6
--- /dev/null
+++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright ish group pty ltd 2024.
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ */
+
+package org.apache.groovy.antlr.override;
+
+// TODO: Remove after update Groovy v4, this part of code is taken from this version
+
+public class SimpleGroovyDoc {
+
+ public static final int RECORD_DEF = 16;
+
+}