CsmArrayBrackets.java

/*
 * Copyright (C) 2013-2026 The JavaParser Team.
 *
 * This file is part of JavaParser.
 *
 * JavaParser can be used either under the terms of
 * a) the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * b) the terms of the Apache License
 *
 * You should have received a copy of both licenses in LICENCE.LGPL and
 * LICENCE.APACHE. Please refer to those files for details.
 *
 * JavaParser 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 Lesser General Public License for more details.
 */
package com.github.javaparser.printer.concretesyntaxmodel;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.nodeTypes.NodeWithVariables;
import com.github.javaparser.ast.observer.ObservableProperty;
import com.github.javaparser.ast.type.ArrayType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.printer.SourcePrinter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * The brackets of an array type, written where the source wrote them.
 * <p>
 * Java allows the brackets of an array on both sides of a declared name: {@code int[] a} and {@code int a[]} declare
 * the same type. The AST keeps this information in {@link ArrayType#getOrigin()}: a level with
 * {@link ArrayType.Origin#TYPE} is written after the element type, a level with {@link ArrayType.Origin#NAME} after
 * the declared name.
 * <p>
 * The lexical preserving printer compares the syntax model of a node with its original tokens, so the model must put
 * the brackets on the same side as the source. This element is therefore used in two places:
 * <ul>
 *     <li>on {@link ArrayType} ({@link #typeSide(CsmElement)}), where it prints the brackets of the level only when
 *     they are written after the element type;</li>
 *     <li>after the name of a declaration ({@link #nameSide()}), where it prints the brackets that the declared type
 *     does not print itself.</li>
 * </ul>
 * Only the brackets are printed after the name: the annotations of a name-side level ({@code int a @A []}) are not.
 */
public class CsmArrayBrackets implements CsmElement {

    /**
     * The syntax of the brackets of an {@link ArrayType}, or {@code null} for the name-side element.
     */
    private final CsmElement typeSideBrackets;

    private CsmArrayBrackets(CsmElement typeSideBrackets) {
        this.typeSideBrackets = typeSideBrackets;
    }

    /**
     * Creates the element used in the syntax of {@link ArrayType}.
     *
     * @param brackets the syntax of the annotations and brackets of the level
     * @return an element printing {@code brackets} only when the level is written after the element type
     */
    public static CsmArrayBrackets typeSide(CsmElement brackets) {
        return new CsmArrayBrackets(brackets);
    }

    /**
     * Creates the element used after the name of a declaration having a {@code TYPE} property
     * ({@link com.github.javaparser.ast.body.Parameter}, {@link VariableDeclarator}).
     *
     * @return an element printing the bracket pairs written after the name, derived from the declared type
     */
    public static CsmArrayBrackets nameSide() {
        return new CsmArrayBrackets(null);
    }

    /**
     * @return {@code true} for the element written after the name of a declaration
     */
    public boolean isNameSide() {
        return typeSideBrackets == null;
    }

    /**
     * @return the syntax of the brackets of an {@link ArrayType}, {@code null} for the name-side element
     */
    public CsmElement getTypeSideBrackets() {
        return typeSideBrackets;
    }

    /**
     * Computes the number of bracket pairs written after the name of a declaration.
     * <p>
     * For a parameter, these are the levels of its type with {@link ArrayType.Origin#NAME}.
     * <p>
     * For a variable declarator in a field or a variable declaration, the type written before the names is the maximum
     * common type of the declaration ({@code int[]} in {@code int[] a, b[]}). That type prints its type-side levels,
     * so the declarator prints all the levels of its own type beyond them, whatever their origin. This also covers a
     * declarator whose type was set to an array type built in code, with the default origin.
     *
     * @param declaration the parameter or variable declarator
     * @param type the type of the declaration; during a change of type, the new type
     * @return the number of {@code []} to write after the name
     */
    public static int nameSideLevels(Node declaration, Object type) {
        if (declaration instanceof VariableDeclarator
                && declaration.getParentNode().isPresent()
                && declaration.getParentNode().get() instanceof NodeWithVariables) {
            NodeWithVariables<?> parent =
                    (NodeWithVariables<?>) declaration.getParentNode().get();
            // The maximum common type the declaration will have once the type of this declarator is changed
            List<Type> types = new ArrayList<>();
            for (VariableDeclarator variable : parent.getVariables()) {
                types.add(variable == declaration ? (Type) type : variable.getType());
            }
            Optional<Type> maximumCommonType = NodeWithVariables.calculateMaximumCommonType(types);
            if (maximumCommonType.isPresent()) {
                return ((Type) type).getArrayLevel() - countLevels(maximumCommonType.get(), ArrayType.Origin.TYPE);
            }
        }
        return countLevels(type, ArrayType.Origin.NAME);
    }

    /**
     * Counts the levels of the given type having the given origin.
     *
     * @param type a type, possibly an {@link ArrayType}
     * @param origin the origin of the levels to count
     * @return the number of {@link ArrayType} levels of {@code type} with {@code origin}
     */
    private static int countLevels(Object type, ArrayType.Origin origin) {
        int levels = 0;
        while (type instanceof ArrayType) {
            ArrayType arrayType = (ArrayType) type;
            if (arrayType.getOrigin() == origin) {
                levels++;
            }
            type = arrayType.getComponentType();
        }
        return levels;
    }

    /**
     * @param origin the origin of an {@link ArrayType} level
     * @return {@code true} when the brackets of the level are written after the element type
     */
    public static boolean isTypeSide(Object origin) {
        return origin != ArrayType.Origin.NAME;
    }

    @Override
    public void prettyPrint(Node node, SourcePrinter printer) {
        if (isNameSide()) {
            int levels = nameSideLevels(node, ObservableProperty.TYPE.getRawValue(node));
            for (int i = 0; i < levels; i++) {
                printer.print("[]");
            }
        } else if (isTypeSide(ObservableProperty.ORIGIN.getRawValue(node))) {
            typeSideBrackets.prettyPrint(node, printer);
        }
    }

    @Override
    public String toString() {
        return isNameSide() ? "CsmArrayBrackets(name side)" : "CsmArrayBrackets(type side)";
    }
}