minor items
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index c5439e8..a62d6aa 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -87,8 +87,6 @@
 import java.util.function.Consumer;
 
 import static org.codehaus.groovy.antlr.PrimitiveHelper.getDefaultValueForPrimitive;
-import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
-import static org.codehaus.groovy.ast.tools.GenericsUtils.parameterizeType;
 
 /**
  * Handy methods when working with the Groovy AST
@@ -465,13 +463,12 @@
 
     private static void addAllInterfaces(final Set<ClassNode> result, final ClassNode source) {
         for (ClassNode in : source.getInterfaces()) {
-            in = parameterizeType(source, in);
-            if (result.add(in))
-                addAllInterfaces(result, in);
+            in = GenericsUtils.parameterizeType(source, in);
+            if(result.add(in)) addAllInterfaces(result, in);
         }
         ClassNode sc = source.redirect().getUnresolvedSuperClass(false);
-        if (sc != null && !isObjectType(sc)) {
-            addAllInterfaces(result, parameterizeType(source, sc));
+        if (sc != null && !ClassHelper.isObjectType(sc)) {
+            addAllInterfaces(result, GenericsUtils.parameterizeType(source, sc));
         }
     }
 
@@ -484,7 +481,7 @@
 
     public static List<FieldNode> getSuperNonPropertyFields(final ClassNode cNode) {
         List<FieldNode> result;
-        if (isObjectType(cNode)) {
+        if (ClassHelper.isObjectType(cNode)) {
             result = new ArrayList<>();
         } else {
             result = getSuperNonPropertyFields(cNode.getSuperClass());
@@ -499,7 +496,7 @@
 
     public static List<FieldNode> getSuperPropertyFields(final ClassNode cNode) {
         List<FieldNode> result;
-        if (isObjectType(cNode)) {
+        if (ClassHelper.isObjectType(cNode)) {
             result = new ArrayList<>();
         } else {
             result = getSuperPropertyFields(cNode.getSuperClass());
@@ -524,7 +521,7 @@
                                                       final boolean includeFields, final boolean includePseudoGetters, final boolean includePseudoSetters,
                                                       final boolean traverseSuperClasses, final boolean skipReadonly, final boolean reverse, final boolean allNames, final boolean includeStatic) {
         List<PropertyNode> result = new ArrayList<>();
-        if (!(isObjectType(cNode)) && traverseSuperClasses && !reverse) {
+        if (!(ClassHelper.isObjectType(cNode)) && traverseSuperClasses && !reverse) {
             result.addAll(getAllProperties(names, origType, cNode.getSuperClass(), includeProperties, includeFields, includePseudoGetters, includePseudoSetters, true, skipReadonly));
         }
         if (includeProperties) {
@@ -560,7 +557,7 @@
                 result.add(new PropertyNode(fNode, fNode.getModifiers() & 0x1F, null, null));
             }
         }
-        if (!(isObjectType(cNode)) && traverseSuperClasses && reverse) {
+        if (!(ClassHelper.isObjectType(cNode)) && traverseSuperClasses && reverse) {
             result.addAll(getAllProperties(names, origType, cNode.getSuperClass(), includeProperties, includeFields, includePseudoGetters, includePseudoSetters, true, skipReadonly));
         }
         return result;
@@ -881,6 +878,7 @@
     //--------------------------------------------------------------------------
 
     public static Parameter[] cloneParams(final Parameter[] parameters) {
+        if (parameters == null || parameters.length == 0) return parameters;
         return Arrays.stream(parameters).map(p -> param(p.getOriginType(), p.getName())).toArray(Parameter[]::new);
     }
 
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
index 1572c25..7f2dfe5 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -51,6 +51,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.StringJoiner;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -60,10 +61,18 @@
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
 
 /**
- * Utility methods to deal with generic types.
+ * Utility methods to deal with parameterized types.
  */
 public class GenericsUtils {
+
+    /**
+     * @since 2.0.0
+     */
     public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY;
+
+    /**
+     * @since 3.0.0
+     */
     public static final String JAVA_LANG_OBJECT = ClassHelper.OBJECT;
 
     /**
@@ -76,7 +85,9 @@
      * @param parameterizedTypes   the actual type arguments used on this class node
      * @param alignmentTarget      the generic type arguments to which we want to align to
      * @return aligned type arguments
-     * @deprecated You shouldn't call this method because it is inherently unreliable
+     *
+     * @since 2.0.0
+     * @deprecated You shouldn't call this method because it is inherently unreliable.
      */
     @Deprecated(forRemoval = true, since = "2.3.0")
     public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) {
@@ -131,6 +142,8 @@
     /**
      * Generates a wildcard generic type in order to be used for checks against
      * class nodes. See {@link GenericsType#isCompatibleWith(ClassNode)}.
+     *
+     * @since 2.0.0
      */
     public static GenericsType buildWildcardType(final ClassNode... upperBounds) {
         GenericsType gt = new GenericsType(ClassHelper.makeWithoutCaching("?"), upperBounds, null);
@@ -141,7 +154,7 @@
     /**
      * Returns the type parameter/argument relationships of the specified type.
      *
-     * @param type the class node to check
+     * @since 2.0.0
      */
     public static Map<GenericsType.GenericsTypeName, GenericsType> extractPlaceholders(final ClassNode type) {
         Map<GenericsType.GenericsTypeName, GenericsType> placeholders = new HashMap<>();
@@ -153,8 +166,7 @@
      * Populates the supplied map with the type parameter/argument relationships
      * of the specified type.
      *
-     * @param type the class node to check
-     * @param placeholders the generics type information collector
+     * @since 2.0.0
      */
     public static void extractPlaceholders(final ClassNode type, final Map<GenericsType.GenericsTypeName, GenericsType> placeholders) {
         if (type == null) return;
@@ -216,17 +228,16 @@
         }
     }
 
-    public static String toGenericTypesString(GenericsType[] genericsTypes) {
+    /**
+     * @since 3.0.0
+     */
+    public static String toGenericTypesString(final GenericsType[] genericsTypes) {
         if (genericsTypes == null) return "";
-        StringBuilder sb = new StringBuilder("<");
-        for (int i = 0, n = genericsTypes.length; i < n; i++) {
-            sb.append(genericsTypes[i].toString());
-            if (i < n - 1) {
-                sb.append(",");
-            }
+        StringJoiner sj = new StringJoiner(",","<","> ");
+        for (GenericsType genericsType : genericsTypes) {
+            sj.add(genericsType.toString());
         }
-        sb.append("> ");
-        return sb.toString();
+        return sj.toString();
     }
 
     /**
@@ -238,6 +249,8 @@
      * @param hint   the class node where generics types are parameterized
      * @param target the interface we want to parameterize generics types
      * @return a parameterized interface class node
+     *
+     * @since 2.0.0
      * @deprecated Use #parameterizeType instead
      */
     @Deprecated(forRemoval = true, since = "2.5.0")
@@ -254,6 +267,8 @@
      * @param hint   the ClassNode where generics types are parameterized
      * @param target the interface we want to parameterize generics types
      * @return a parameterized interface ClassNode
+     *
+     * @since 2.1.0
      */
     public static ClassNode parameterizeType(ClassNode hint, final ClassNode target) {
         if (hint.isArray()) {
@@ -294,6 +309,9 @@
         return correctToGenericsSpecRecurse(gt, cn);
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode nonGeneric(final ClassNode type) {
         int dims = 0;
         ClassNode temp = type;
@@ -314,20 +332,32 @@
         return type;
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode newClass(ClassNode type) {
         return type.getPlainNodeReference();
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode makeClassSafe(Class klass) {
         return makeClassSafeWithGenerics(ClassHelper.make(klass));
     }
 
+    /**
+     * @since 2.4.0
+     */
     public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) {
         GenericsType[] genericsTypes = new GenericsType[1];
         genericsTypes[0] = new GenericsType(genericsType);
         return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes);
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) {
         ClassNode plainNodeReference = newClass(type);
         if (genericTypes != null && genericTypes.length > 0) {
@@ -337,6 +367,9 @@
         return plainNodeReference;
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) {
         if (type.isArray()) {
             return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray();
@@ -352,6 +385,9 @@
         return makeClassSafe0(type, gTypes);
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static MethodNode correctToGenericsSpec(Map<String, ClassNode> genericsSpec, MethodNode mn) {
         if (genericsSpec == null) return mn;
         if (mn.getGenericsTypes() != null) genericsSpec = addMethodGenerics(mn, genericsSpec);
@@ -367,6 +403,9 @@
         return newMethod;
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode correctToGenericsSpecRecurse(Map<String, ClassNode> genericsSpec, ClassNode type) {
         return correctToGenericsSpecRecurse(genericsSpec, type, Collections.emptyList());
     }
@@ -386,6 +425,9 @@
         return newTypes;
     }
 
+    /**
+     * @since 2.4.1
+     */
     public static ClassNode correctToGenericsSpecRecurse(Map<String, ClassNode> genericsSpec, ClassNode type, List<String> exclusions) {
         if (type.isArray()) {
             return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray();
@@ -442,6 +484,9 @@
         return makeClassSafeWithGenerics(type, newgTypes);
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode correctToGenericsSpec(final Map<String, ClassNode> genericsSpec, final GenericsType type) {
         ClassNode cn = null;
         if (type.isPlaceholder()) {
@@ -459,6 +504,9 @@
         return cn;
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static ClassNode correctToGenericsSpec(final Map<String, ClassNode> genericsSpec, ClassNode type) {
         if (type.isArray()) {
             return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray();
@@ -474,10 +522,16 @@
         return type != null ? type : ClassHelper.OBJECT_TYPE.getPlainNodeReference();
     }
 
+    /**
+     * @since 2.4.0
+     */
     public static Map<String, ClassNode> createGenericsSpec(final ClassNode type) {
         return createGenericsSpec(type, Collections.emptyMap());
     }
 
+    /**
+     * @since 2.3.0
+     */
     public static Map<String, ClassNode> createGenericsSpec(final ClassNode type, final Map<String, ClassNode> oldSpec) {
         // Example:
         // abstract class A<X,Y,Z> { ... }
@@ -495,6 +549,9 @@
         return newSpec;
     }
 
+    /**
+     * @since 2.4.1
+     */
     public static Map<String, ClassNode> addMethodGenerics(final MethodNode node, final Map<String, ClassNode> oldSpec) {
         Map<String, ClassNode> newSpec = new HashMap<>(oldSpec);
         GenericsType[] tps = node.getGenericsTypes();
@@ -523,8 +580,11 @@
         return newSpec;
     }
 
-    // TODO: this is very similar to StaticTypesCheckingSupport#extractGenericsConnections, using ClassNode instead of GenericsType
+    /**
+     * @since 2.3.1
+     */
     public static void extractSuperClassGenerics(final ClassNode type, final ClassNode target, final Map<String, ClassNode> spec) {
+    // TODO: this is very similar to StaticTypesCheckingSupport#extractGenericsConnections, using ClassNode instead of GenericsType
         if (target == null || target == type) return;
         if (target.isGenericsPlaceHolder()) {
             spec.put(target.getUnresolvedName(), type);
@@ -565,6 +625,9 @@
         }
     }
 
+    /**
+     * @since 3.0.0
+     */
     public static ClassNode getSuperClass(final ClassNode type, final ClassNode target) {
         return ClassHelper.getNextSuperClass(ClassHelper.isPrimitiveType(type) ? ClassHelper.getWrapper(type) : type, target);
     }
@@ -614,7 +677,7 @@
         }
     }
 
-    private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map<String, ClassNode> spec) {
+    private static void extractSuperClassGenerics(final ClassNode[] usage, final ClassNode[] declaration, final Map<String, ClassNode> spec) {
         if (usage == null || declaration == null || declaration.length == 0) return;
         // both have generics
         for (int i = 0; i < usage.length; i++) {
@@ -628,6 +691,9 @@
         }
     }
 
+    /**
+     * @since 2.4.0
+     */
     public static ClassNode[] parseClassNodesFromString(final String option, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage) {
         try {
             ModuleNode moduleNode = ParserPlugin.buildAST("Dummy<" + option + "> dummy;", compilationUnit.getConfiguration(), compilationUnit.getClassLoader(), null);
@@ -685,6 +751,8 @@
      * @param genericsSpec    the generics context information spec
      * @param oldPlaceHolders the old placeholders
      * @return the new generics types
+     *
+     * @since 2.5.0
      */
     public static GenericsType[] applyGenericsContextToPlaceHolders(Map<String, ClassNode> genericsSpec, GenericsType[] oldPlaceHolders) {
         if (oldPlaceHolders == null || oldPlaceHolders.length == 0) return oldPlaceHolders;
@@ -729,15 +797,19 @@
     /**
      * Clears the parameterized type cache.
      * <p>
-     * It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675
+     * It is useful to IDE as the type being compiled are continuously being edited/altered; see GROOVY-8675
+     *
+     * @since 3.0.0
      */
     public static void clearParameterizedTypeCache() {
         PARAMETERIZED_TYPE_CACHE.clearAll();
     }
 
     /**
-     * Convenience method for {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)}
-     * when the {@code tryToFindExactType} boolean is {@code false}.
+     * Convenience method for {@link #findParameterizedTypeFromCache(ClassNode,ClassNode,boolean)}
+     * with {@code tryToFindExactType} set to {@code false}.
+     *
+     * @since 3.0.0
      */
     public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) {
         return findParameterizedTypeFromCache(genericsClass, actualType, false);
@@ -746,7 +818,9 @@
     /**
      * Try to get the parameterized type from the cache.
      * <p>
-     * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode,ClassNode,boolean)}
+     * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode,ClassNode,boolean)}.
+     *
+     * @since 3.0.0
      */
     public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, boolean tryToFindExactType) {
         if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
@@ -761,7 +835,9 @@
     }
 
     /**
-     * Convenience method for {@link #findParameterizedType(ClassNode,ClassNode,boolean)} when {@code tryToFindExactType} is {@code false}.
+     * Convenience method for {@link #findParameterizedType(ClassNode,ClassNode,boolean)} with {@code tryToFindExactType} set to {@code false}.
+     *
+     * @since 3.0.0
      */
     public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType) {
         return findParameterizedType(genericsClass, actualType, false);
@@ -771,6 +847,8 @@
      * Gets the parameterized type by searching the whole class hierarchy according to generics class and actual receiver.
      * <p>
      * {@link #findParameterizedTypeFromCache(ClassNode,ClassNode,boolean)} is strongly recommended for better performance.
+     *
+     * @since 3.0.0
      */
     public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) {
         final GenericsType[] genericsTypes = genericsClass.getGenericsTypes();
@@ -828,6 +906,8 @@
      *
      * The resolved types can not help us to choose methods correctly if the argument is a string:  T: Object, S: Serializable
      * so we need actual types:  T: String, S: Long
+     *
+     * @since 3.0.0
      */
     public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) {
         return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, false);
@@ -917,6 +997,8 @@
 
     /**
      * Checks for any placeholder (aka unresolved) generics.
+     *
+     * @since 4.0.0
      */
     public static boolean hasUnresolvedGenerics(final ClassNode type) {
         if (type.isGenericsPlaceHolder()) return true;
@@ -982,7 +1064,9 @@
      * Gets the actual type according to the placeholder name.
      *
      * @param placeholderName the placeholder name (i.e. "T", "E", etc.)
-     * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)}
+     * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap}
+     *
+     * @since 3.0.0
      */
     public static ClassNode findActualTypeByGenericsPlaceholderName(final String placeholderName, final Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) {
         Function<GenericsType, ClassNode> resolver = gt -> {