package de.renew.formalism.java;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Vector;

import de.renew.expression.CallExpression;
import de.renew.expression.EqualsExpression;
import de.renew.expression.Expression;
import de.renew.expression.Function;
import de.renew.expression.InvertibleExpression;
import de.renew.expression.ListExpression;
import de.renew.expression.LocalVariable;
import de.renew.expression.NoArgExpression;
import de.renew.expression.TupleExpression;
import de.renew.expression.TypeCheckingExpression;
import de.renew.expression.VariableExpression;
import de.renew.formalism.function.ArrayFunction;
import de.renew.formalism.function.ArrayWriteFunction;
import de.renew.formalism.function.CastFunction;
import de.renew.formalism.function.ConstructorFunction;
import de.renew.formalism.function.DynamicConstructorFunction;
import de.renew.formalism.function.DynamicFieldFunction;
import de.renew.formalism.function.DynamicFieldWriteFunction;
import de.renew.formalism.function.DynamicMethodFunction;
import de.renew.formalism.function.DynamicStaticMethodFunction;
import de.renew.formalism.function.Executor;
import de.renew.formalism.function.FieldFunction;
import de.renew.formalism.function.FieldWriteFunction;
import de.renew.formalism.function.Identity;
import de.renew.formalism.function.MethodFunction;
import de.renew.formalism.function.StaticFieldFunction;
import de.renew.formalism.function.StaticFieldWriteFunction;
import de.renew.formalism.function.StaticMethodFunction;
import de.renew.util.Types;

/**
 * A helper class with helper methods.
 */
public final class JavaHelper {

    /**
     * Message shown when no matching method is found, with suggestions for alternatives.
     */
    public static final String KNOWN_METHODS = "\nKnown methods:";

    /**
     * Message shown when no matching field is found, with alternative field suggestions.
     */
    public static final String KNOWN_FIELDS = "\nKnown fields:";

    private static org.apache.log4j.Logger _logger =
        org.apache.log4j.Logger.getLogger(JavaHelper.class);

    private static final String ERROR_LINE_PREFIX = "Error in line ";
    private static final String COLUMN_PREFIX = ", column ";
    private static final String MESSAGE_SUFFIX = ":\n";
    private static final String TYPE_MISMATCH_ERROR = "Operator types do not match.";
    private static final String VOID_TYPE_ERROR = "Expression of type void not allowed here.";
    private static final String TO = " to ";
    private static final String PERIOD = ".";
    private static final String INVALID_LEFT_HAND_SIDE = "Invalid left hand side of assignment.";
    private static final String NO_SUCH_VARIABLE = "No such variable: ";
    private static final String ENCOUNTERED = "Encountered ";
    private static final String DECLARED_IN = " declared in ";
    private static final String MALFORMED_PRIMARY_VECTOR = "Malformed primary vector.";
    private static final String INIT = "<init>";
    private static final String DUE_TO = " due to ";
    private static final String COLON = ": ";
    private static final String MULTIPLE_METHODS_MATCH = "Multiple methods match: ";
    private static final String CANNOT_NOT_BIND_METHOD = "Cannot not bind method ";
    private static final String LOADING_CLASS = "Could not load class ";
    private static final String NO_SUCH_CLASS_OR_VARIABLE = "No such class or variable.";
    private static final String NOT_AN_ARRAY = "Not an array.";
    private static final String NO_SUCH_FIELD = "No such field.";
    private static final String NO_SUCH_STATIC_FIELD = "No such static field: ";

    /**
     * Private constructor to prevent instantiation of this utility class.
     * Throws an {@link UnsupportedOperationException} to clearly signal that
     * instantiation is not supported.
     */
    private JavaHelper() {
        throw new UnsupportedOperationException("Utility class");
    }

    /**
     * Returns the static logger for this class.
     *
     * @return the {@link org.apache.log4j.Logger} instance for this class
     */
    public static org.apache.log4j.Logger getLogger() {
        return _logger;
    }

    /**
     * Sets a new logger instance.
     *
     * @param newLogger The new Logger instance to be used for logging.
     */
    public static void setLogger(org.apache.log4j.Logger newLogger) {
        _logger = newLogger;
    }

    /**
     * Builds a formatted error message string that includes the line and column information from a {@link Token},
     * if available, along with the provided error message.
     * If the {@code token} is non-null, the error message will be prefixed with the line number and column
     * number associated with the token, in the format:
     * "Error in line {lineNumber}, column {columnNumber}: {message}"
     * If the {@code token} is null, only the provided error message will be returned.
     *
     * @param msg the error message to include in the final error string
     * @param token the {@link Token} containing line and column information; may be {@code null}
     * @return a formatted error message string, optionally including line and column information
     */
    public static String buildErrorMessage(String msg, Token token) {
        if (token != null) {
            Token posToken = token.next == null ? token : token.next;
            return ERROR_LINE_PREFIX + posToken.beginLine + COLUMN_PREFIX + posToken.beginColumn
                + MESSAGE_SUFFIX + msg;
        } else {
            return msg;
        }
    }

    /**
     * Creates a {@link ParseException} with a detailed error message, including
     * information about the line and column where the error occurred if a {@link Token} is provided.
     * If the specified {@code token} is non-null, the exception message will include the
     * line and column numbers associated with the token (or its successor if available).
     * Otherwise, it will use only the specified message.
     *
     * @param msg the error message to include in the exception
     * @param token the {@link Token} at or near the point of the error; may be null
     * if line and column details are not available
     * @return a {@link ParseException} with the specified message and, if available,
     * line and column information from the {@code token}
     */
    public static ParseException makeParseException(String msg, Token token) {
        if (token != null) {
            ParseException ex = new ParseException(buildErrorMessage(msg, token));
            ex.currentToken = token;
            return ex;
        } else {
            return new ParseException(msg);
        }
    }

    /**
     * Creates an {@link ExtendedParseException} with a detailed error message, including
     * the line and column information from the specified {@link Token}, if available.
     * Additionally, an extra object can be associated with the exception for further context.
     * If the {@code token} is non-null, the exception message will include the line and
     * column numbers associated with the token (or its successor if available). Otherwise,
     * the message will contain only the specified error description.
     *
     * @param msg the error message to include in the exception
     * @param o an additional context object to include in the exception, providing more
     * details about the error (can be any relevant object)
     * @param token the {@link Token} at or near the error's location; may be null if line and column
     * details are unavailable
     * @return an {@link ExtendedParseException} with the specified message and additional
     * context, including line and column information if the {@code token} is available
     */
    public static ExtendedParseException makeExtendedParseException(
        String msg, Object o, Token token)
    {
        if (token != null) {
            ExtendedParseException ex =
                new ExtendedParseException(buildErrorMessage(msg, token), o);
            ex.currentToken = token;
            return ex;
        } else {
            return new ExtendedParseException(msg, o);
        }
    }

    /**
     * Creates a {@link TypedExpression} representing a binary operation between two
     * {@link TypedExpression} operands, using the specified function and type.
     * This method constructs a binary operation by combining the expressions from
     * the left and right operands into a {@link TupleExpression}, then applies the
     * specified function to these expressions to form a {@link CallExpression}. The
     * resulting {@code CallExpression} is wrapped within a {@code TypedExpression}
     * with the specified type.
     *
     * @param left the left operand as a {@link TypedExpression}
     * @param right the right operand as a {@link TypedExpression}
     * @param fun the {@link Function} representing the operation to apply to the operands
     * @param type the resulting type of the binary operation, specified as a {@link Class}
     * @return a {@link TypedExpression} representing the binary operation with the specified type
     */
    public static TypedExpression makeTypedBinary(
        TypedExpression left, TypedExpression right, Function fun, Class<?> type)
    {
        return new TypedExpression(
            type, new CallExpression(
                type, new TupleExpression(left.getExpression(), right.getExpression()), fun));
    }

    /**
     * Creates a {@link TypedExpression} representing a binary operation between two
     * {@link TypedExpression} operands, where the result is of boolean type.
     * This method uses {@code makeTypedBinary} to construct the expression, specifying
     * the result type as {@code Boolean.TYPE} (the primitive {@code boolean} type). The operation
     * between the two operands is defined by the provided function.
     *
     * @param left the left operand as a {@link TypedExpression}
     * @param right the right operand as a {@link TypedExpression}
     * @param fun the {@link Function} representing the operation to apply to the operands
     * @return a {@link TypedExpression} representing a boolean binary operation with {@code boolean} type
     */
    public static TypedExpression makeBooleanBinary(
        TypedExpression left, TypedExpression right, Function fun)
    {
        return makeTypedBinary(left, right, fun, Boolean.TYPE);
    }

    /**
     * Creates a {@link TypedExpression} representing a binary operation between two
     * {@link TypedExpression} operands, using the specified function and determining the
     * resulting type based on the operand types.
     * If the operand types are compatible, the method determines the appropriate type
     * based on the types of the left and right operands. If they are incompatible
     * (e.g., attempting a binary operation between a {@code boolean} and a numeric type),
     * a {@link ParseException} will be thrown to signal the mismatch.
     *
     * @param left the left operand as a {@link TypedExpression}
     * @param right the right operand as a {@link TypedExpression}
     * @param fun the {@link Function} representing the operation to apply to the operands
     * @param errToken the {@link Token} used to provide context for error messages in case of a type mismatch
     * @return a {@link TypedExpression} representing the binary operation with the appropriate type
     * @throws ParseException if the operand types are incompatible or cannot be resolved
     */
    public static TypedExpression makeBinary(
        TypedExpression left, TypedExpression right, Function fun, Token errToken)
        throws ParseException
    {
        Class<?> type = determineBinaryType(left, right, errToken);
        return makeTypedBinary(left, right, fun, type);
    }

    /**
     * Determines the appropriate common type for two {@link TypedExpression} operands
     * based on their types. This method evaluates the types of the operands and returns
     * a compatible type for binary operations.
     * The method handles three main cases:
     * If either operand is untyped, the result is {@code Types.UNTYPED}.
     * If both operands are of type {@code Boolean.TYPE}, the result is {@code Boolean.TYPE}.
     * If there is a mismatch in boolean types, a {@link ParseException} is thrown.
     * If either operand is a numeric type, the result is the more general numeric type
     * among {@code Double.TYPE}, {@code Float.TYPE}, {@code Long.TYPE}, or {@code Integer.TYPE}.
     *
     * @param left the left operand as a {@link TypedExpression}
     * @param right the right operand as a {@link TypedExpression}
     * @param errToken the {@link Token} used to provide context for error messages
     * @return the common {@link Class} type for the operation, such as {@code Double.TYPE},
     * {@code Boolean.TYPE}, or {@code Integer.TYPE}.
     * @throws ParseException if operand types are incompatible (e.g., mismatched boolean types)
     */
    private static Class<?> determineBinaryType(
        TypedExpression left, TypedExpression right, Token errToken) throws ParseException
    {
        if (!left.isTyped() || !right.isTyped()) {
            return Types.UNTYPED;
        }

        Class<?> leftType = left.getType();
        Class<?> rightType = right.getType();

        if (isBooleanType(leftType, rightType, errToken)) {
            return Boolean.TYPE;
        }

        return determineNumericType(leftType, rightType);
    }

    /**
     * Checks if both operand types are {@code Boolean.TYPE} and ensures compatibility.
     *
     * @param leftType the type of the left operand
     * @param rightType the type of the right operand
     * @param errToken the {@link Token} used to provide context for error messages
     * @return {@code true} if both types are {@code Boolean.TYPE}, {@code false} otherwise
     * @throws ParseException if one operand is {@code Boolean.TYPE} and the other is not
     */
    private static boolean isBooleanType(Class<?> leftType, Class<?> rightType, Token errToken)
        throws ParseException
    {
        if (leftType == Boolean.TYPE || rightType == Boolean.TYPE) {
            if (leftType != Boolean.TYPE || rightType != Boolean.TYPE) {
                throw makeParseException(TYPE_MISMATCH_ERROR, errToken);
            }
            return true;
        }
        return false;
    }

    /**
     * Determines the common numeric type for two operand types based on type precedence.
     *
     * @param leftType the type of the left operand
     * @param rightType the type of the right operand
     * @return the most general numeric type among {@code Double.TYPE}, {@code Float.TYPE},
     * {@code Long.TYPE}, or {@code Integer.TYPE}.
     */
    private static Class<?> determineNumericType(Class<?> leftType, Class<?> rightType) {
        if (leftType == Double.TYPE || rightType == Double.TYPE) {
            return Double.TYPE;
        }
        if (leftType == Float.TYPE || rightType == Float.TYPE) {
            return Float.TYPE;
        }
        if (leftType == Long.TYPE || rightType == Long.TYPE) {
            return Long.TYPE;
        }
        return Integer.TYPE;
    }

    /**
     * Performs unary numeric promotion on the given type.
     * This method accepts an input type and applies unary numeric promotion rules as follows:
     * If the type is untyped ({@code Types.UNTYPED}), it returns the same type.
     * If the type is a reference type or boolean, a {@link ParseException} is thrown to indicate
     * that a primitive numeric type is expected.
     * If the type is a smaller integer type (byte, short, or char), it promotes the type to
     * {@code int}, following Java's numeric promotion rules.
     * For other types, it returns the original type unchanged.
     *
     * @param old the original {@link Class} type to promote
     * @param errToken the {@link Token} used to provide context for the error message in case of a type mismatch
     * @return the promoted {@link Class} type
     * @throws ParseException if the input type is not a primitive numeric type
     */
    public static Class<?> unaryNumericPromotion(Class<?> old, Token errToken)
        throws ParseException
    {
        // Accept untyped expressions.
        if (old == Types.UNTYPED) {
            return old;
        }

        // Exclude reference types and boolean.
        if (old == null || !old.isPrimitive()) {
            throw makeParseException("Primitive type expected.", errToken);
        }
        if (old == Boolean.TYPE) {
            throw makeParseException("Numeric type expected.", errToken);
        }

        // Convert small types to int.
        if (old == Byte.TYPE || old == Short.TYPE || old == Character.TYPE) {
            return Integer.TYPE;
        } else {
            return old;
        }
    }

    /**
     * Performs unary integral promotion on the given type.
     * This method checks if the given type is compatible with integral (integer-based)
     * operations. If the type is {@code float} or {@code double}, a {@link ParseException}
     * is thrown to indicate that an integral (non-floating-point) type is expected.
     * Otherwise, the method applies unary numeric promotion to the given type by
     * calling {@link #unaryNumericPromotion(Class, Token)}.
     *
     * @param old the original {@link Class} type to promote
     * @param errToken the {@link Token} used to provide context for the error message if an integral type is expected
     * @return the promoted {@link Class} type, after integral promotion
     * @throws ParseException if the input type is {@code float} or {@code double}, as these are not integral types
     */
    public static Class<?> unaryIntegralPromotion(Class<?> old, Token errToken)
        throws ParseException
    {
        if (old == Float.TYPE || old == Double.TYPE) {
            throw makeParseException("Integral type expected.", errToken);
        }
        return unaryNumericPromotion(old, errToken);
    }

    /**
     * Ensures that the given expression is not of type {@code void}.
     * This method checks the type of the provided {@link TypedExpression}. If the expression
     * is of type {@code void}, a {@link ParseException} is thrown to signal that a non-void
     * expression is expected in this context.
     *
     * @param expr the {@link TypedExpression} to check
     * @param errToken the {@link Token} used to provide context for the error message if the expression is of type {@code void}
     * @throws ParseException if the type of the expression is {@code void}
     */
    public static void ensureNonVoid(TypedExpression expr, Token errToken) throws ParseException {
        if (expr.getType() == Void.TYPE) {
            throw makeParseException(VOID_TYPE_ERROR, errToken);
        }
    }

    /**
     * Ensures that the given {@link TypedExpression} can be converted to the specified target type.
     * This method checks if the type of the provided expression can be converted (via widening conversion)
     * to the specified target type. If the expression is untyped, the method always succeeds.
     * If the expression is typed and the conversion is not allowed, a {@link ParseException} is thrown.
     *
     * @param clazz the target {@link Class} to which the expression should be convertible
     * @param expr the {@link TypedExpression} to check for convertibility
     * @param errToken the {@link Token} used to provide context for the error message in case of an invalid conversion
     * @throws ParseException if the expression cannot be converted to the target type
     */
    public static void ensureConvertability(Class<?> clazz, TypedExpression expr, Token errToken)
        throws ParseException
    {
        if (expr.isTyped() && !Types.allowsWideningConversion(expr.getType(), clazz)) {
            throw makeParseException(
                "Cannot convert " + makeTypeErrorString(expr.getType()) + TO
                    + makeTypeErrorString(clazz) + PERIOD,
                errToken);
        }
    }

    /**
     * Ensures that the given {@link TypedExpression} represents an enumerable type.
     * This method checks whether the type of the provided expression is a valid enumerable type.
     * An enumerable type is expected to be a primitive integer type (e.g., {@code byte}, {@code short},
     * {@code int}, or {@code long}), but not a floating-point type (e.g., {@code float}, {@code double}) or non-primitive type.
     * If the expression's type is invalid for enumeration, a {@link ParseException} is thrown.
     *
     * @param expr the {@link TypedExpression} to check for enumerability
     * @param errToken the {@link Token} used to provide context for the error message in case the expression's type is not enumerable
     * @throws ParseException if the type of the expression is not an enumerable type
     */
    public static void ensureEnumerateability(TypedExpression expr, Token errToken)
        throws ParseException
    {
        if (expr.isTyped()) {
            if (expr.getType() == null || !expr.getType().isPrimitive()
                || expr.getType() == Double.TYPE || expr.getType() == Float.TYPE) {
                throw makeParseException("Enumerable type expected.", errToken);
            }
        }
    }

    /**
     * Ensures that the types of the given {@link TypedExpression} operands match for a binary operation.
     * This method checks whether both operands have compatible types for a binary operation. It ensures that both
     * operands are either primitive types or both are reference types, and that neither operand is a boolean type
     * when performing a binary operation. If there is a type mismatch, a {@link ParseException} is thrown.
     *
     * @param left the left operand {@link TypedExpression} to check
     * @param right the right operand {@link TypedExpression} to check
     * @param errToken the {@link Token} used to provide context for the error message in case of a type mismatch
     * @throws ParseException if the operand types do not match or if one of the operands is a boolean type
     */
    public static void ensureBinaryMatch(
        TypedExpression left, TypedExpression right, Token errToken) throws ParseException
    {
        if (left.isTyped() && right.isTyped()) {
            Class<?> leftType = left.getType();
            Class<?> rightType = right.getType();
            boolean leftRef = (leftType == null || !leftType.isPrimitive());
            boolean rightRef = (rightType == null || !rightType.isPrimitive());
            if (leftRef ^ rightRef) {
                throw makeParseException(TYPE_MISMATCH_ERROR, errToken);
            }
            if (leftType == Boolean.TYPE ^ rightType == Boolean.TYPE) {
                throw makeParseException(TYPE_MISMATCH_ERROR, errToken);
            }
        }
    }

    /**
     * Creates a {@link TypedExpression} representing an explicit type cast of the given expression to the specified target type.
     * This method handles various casting scenarios:
     * Identity cast: No action is taken when the expression's type matches the target type.
     * Cast without loss of information: Types that can be safely cast without losing data, such as casting {@code byte} to {@code int} or {@code String} to {@code Object}.
     * Cast with potential loss of information: Types where casting might result in information loss, such as casting {@code int} to {@code byte} or {@code long} to {@code double}.
     * Impossible cast: Types that cannot be cast, such as casting {@code int} to {@code Object} or {@code String} to {@code Class}.
     * The method distinguishes between these cases and returns an appropriate {@link TypedExpression} or throws a {@link ParseException} for unsupported casts.
     *
     * @param clazz the target type {@link Class} to which the expression should be cast
     * @param expr the {@link TypedExpression} to be cast
     * @param errToken the {@link Token} used to provide context for the error message in case the cast is invalid
     * @return a new {@link TypedExpression} representing the cast expression
     * @throws ParseException if the cast is impossible or unsupported
     */
    public static TypedExpression makeExplicitCastExpression(
        Class<?> clazz, TypedExpression expr, Token errToken) throws ParseException
    {
        // We must distiguish the following cases:
        // - An identity cast. No action.
        // - The cast does not lose information.
        // (Typical: byte -> int, String -> Object, Object -> String)
        // - The cast might lose information.
        // (Typical: int -> byte, long -> double)
        // - The cast is impossible.
        // (Typical: int -> Object, String -> Class)
        if (expr.getType() == clazz) {
            return expr;
        } else if (!expr.isTyped()) {
            if (clazz.isPrimitive()) {
                // We do not know which class the untyped expression
                // will take at runtime, so we must restrict ourselves to
                // directed information transfer.
                return new TypedExpression(
                    clazz,
                    new CallExpression(clazz, expr.getExpression(), new CastFunction(clazz)));
            } else {
                // It is still safe to allow bidirectional communication.
                // With references types, the value is only checked
                // and handed through.
                return new TypedExpression(
                    clazz, new TypeCheckingExpression(clazz, expr.getExpression()));
            }
        } else if (Types.allowsCast(expr.getType(), clazz)) {
            if (clazz.isPrimitive()) {
                if (expr.getType() == null) {
                    // Nothing to do. Casting a constant null is trivial.
                    return new TypedExpression(clazz, expr.getExpression());
                } else if (Types.allowsLosslessWidening(expr.getType(), clazz)) {
                    return new TypedExpression(
                        clazz,
                        new InvertibleExpression(
                            clazz, expr.getExpression(), new CastFunction(clazz),
                            new CastFunction(expr.getType())));
                } else {
                    return new TypedExpression(
                        clazz,
                        new CallExpression(clazz, expr.getExpression(), new CastFunction(clazz)));
                }
            } else {
                // We must guard against two cases.
                // The cast may fail and a value might be illegally
                // fed backwards into the equation.
                return new TypedExpression(
                    clazz, new TypeCheckingExpression(clazz, makeGuardedExpression(expr)));
            }
        } else {
            throw makeParseException(
                "Cannot cast " + Types.typeToString(expr.getType()) + TO + Types.typeToString(clazz)
                    + PERIOD,
                errToken);
        }
    }

    /**
     * Converts a {@link Vector} of {@link TypedExpression} objects into an array of {@link Expression} objects.
     * This method iterates over the elements of the provided {@link Vector} and extracts the {@link Expression}
     * from each {@link TypedExpression}, placing them into a newly created {@link Expression} array.
     *
     * @param vector the {@link Vector} of {@link TypedExpression} objects to convert
     * @return an array of {@link Expression} objects corresponding to the {@link TypedExpression} elements in the provided {@link Vector}
     */
    public static Expression[] makeExpressionArray(Vector<TypedExpression> vector) {
        Expression[] result = new Expression[vector.size()];
        for (int i = 0; i < result.length; i++) {
            TypedExpression expr = vector.elementAt(i);
            result[i] = expr.getExpression();
        }
        return result;
    }

    /**
     * Converts a {@link Vector} of {@link TypedExpression} objects into a {@link TupleExpression}.
     * This method first converts the {@link Vector} of {@link TypedExpression} objects into an array of {@link Expression}
     * objects using {@link #makeExpressionArray}. It then uses this array to create and return a new {@link TupleExpression}.
     *
     * @param vector the {@link Vector} of {@link TypedExpression} objects to be converted into a {@link TupleExpression}
     * @return a new {@link TupleExpression} containing the {@link Expression} objects derived from the provided {@link TypedExpression}s
     */
    public static TupleExpression makeTupleExpression(Vector<TypedExpression> vector) {
        return new TupleExpression(makeExpressionArray(vector));
    }

    /**
     * Converts a {@link Vector} of {@link TypedExpression} objects into a {@link ListExpression}.
     * This method first converts the {@link Vector} of {@link TypedExpression} objects into an array of {@link Expression}
     * objects using {@link #makeExpressionArray}. It then uses this array along with the provided {@code tailed} flag
     * to create and return a new {@link ListExpression}.
     *
     * @param vector the {@link Vector} of {@link TypedExpression} objects to be converted into a {@link ListExpression}
     * @param tailed a {@code boolean} indicating whether the list expression should have a "tail" (i.e., an additional element)
     * @return a new {@link ListExpression} containing the {@link Expression} objects derived from the provided {@link TypedExpression}s
     */
    public static ListExpression makeListExpression(
        Vector<TypedExpression> vector, boolean tailed)
    {
        return new ListExpression(makeExpressionArray(vector), tailed);
    }

    /**
     * Wraps a {@link TypedExpression} in a {@link TypeCheckingExpression} to ensure type safety.
     * If the given {@link TypedExpression} is typed and its type is not {@code null}, it is wrapped in a {@link TypeCheckingExpression}
     * to validate that no illegally typed value is propagated backwards into the expression. If the expression is untyped, it
     * is returned as-is.
     *
     * @param expr the {@link TypedExpression} to be wrapped and guarded
     * @return a {@link Expression} that ensures the type of the given {@link TypedExpression} is checked, or the original expression if the type is not specified
     */
    public static Expression makeGuardedExpression(TypedExpression expr) {
        // The typed expression is wrapped in a type checking expression
        // to ensure that no illegally typed value is propagated
        // backwards into the expression.
        if (expr.isTyped() && expr.getType() != null) {
            return new TypeCheckingExpression(expr.getType(), expr.getExpression());
        } else {
            return expr.getExpression();
        }
    }

    /**
     * Converts a {@link Vector} of {@link TypedExpression} objects into a {@link TupleExpression},
     * ensuring that each expression is type-checked for safety.
     * This method takes a {@link Vector} of {@link TypedExpression} objects, converts each of them into a guarded expression
     * using {@link #makeGuardedExpression}, and then assembles the resulting expressions into a new {@link TupleExpression}.
     * The type-checking ensures that no illegally typed values are propagated into the tuple.
     *
     * @param vector the {@link Vector} of {@link TypedExpression} objects to be converted into a {@link TupleExpression}
     * @return a new {@link TupleExpression} containing the type-checked expressions derived from the provided {@link TypedExpression}s
     */
    public static TupleExpression makeGuardedTupleExpression(Vector<TypedExpression> vector) {
        // We receive a vector of typed expressions and convert it
        // into a tuple expression that is sufficiently guarded.
        Expression[] result = new Expression[vector.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = makeGuardedExpression(vector.elementAt(i));
        }
        return new TupleExpression(result);
    }

    /**
     * Converts a {@link Vector} of {@link TypedExpression} objects into an array of {@link Class} objects representing their types.
     * This method iterates over the elements of the provided {@link Vector} of {@link TypedExpression}s. If all expressions
     * are typed, it returns an array of {@link Class} objects corresponding to their types. If any expression is untyped,
     * the method returns {@code null} to indicate that the entire vector is untyped.
     *
     * @param vector the {@link Vector} of {@link TypedExpression} objects to be converted into an array of types
     * @return an array of {@link Class} objects representing the types of the expressions, or {@code null} if any expression is untyped
     */
    public static Class<?>[] makeTypeArray(Vector<TypedExpression> vector) {
        Class<?>[] types = new Class<?>[vector.size()];
        for (int i = 0; i < types.length; i++) {
            TypedExpression expr = vector.elementAt(i);
            if (!expr.isTyped()) {
                // If one component is untyped, then the whole vector is
                // untyped.
                return null;
            }
            types[i] = expr.getType();
        }
        return types;
    }

    /**
     * Increases the array dimension level of the given class type by one.
     * This method creates a new array of the specified class type with zero length
     * to determine its array class type (i.e., it "increases" the array dimension).
     * If the class type cannot be used to create an array, a {@link ParseException}
     * is thrown with an appropriate error message.
     *
     * @param clazz the class type whose array level should be increased
     * @param errToken the {@link Token} used to provide context for the error in case of failure
     * @return a {@link Class} object representing the array type of the specified class
     * @throws ParseException if the class cannot be used to create an array
     */
    public static Class<?> increaseArrayLevel(Class<?> clazz, Token errToken)
        throws ParseException
    {
        try {
            return java.lang.reflect.Array.newInstance(clazz, 0).getClass();
        } catch (Exception e) {
            throw makeParseException("Could not create class.", errToken);
        }
    }

    /**
     * Generates a descriptive error message for the given type.
     * This method creates a string representation of a type error based on the provided class type.
     * It returns a specific message for a {@code null} type, an untyped expression, or a typed expression
     * with a detailed description of the type.
     *
     * @param type the {@link Class} object representing the type to be described
     * @return a string describing the given type, such as "a null expression", "an untyped expression",
     * or "an expression of type {@code type}"
     */
    public static String makeTypeErrorString(Class<?> type) {
        if (type == null) {
            return "a null expression";
        } else if (type == Types.UNTYPED) {
            return "an untyped expression";
        } else {
            return "an expression of type " + Types.typeToString(type);
        }
    }

    /**
     * Creates a typed equality expression between two {@link TypedExpression} objects,
     * ensuring that the right-hand side expression is properly converted (if needed)
     * to match the type of the left-hand side expression.
     * This method assumes that the input expressions are already validated
     * to ensure that they are typed, of different types, and that the right-hand side expression
     * can be widened to the left-hand side type.
     * Depending on the type of the right-hand side expression, it is either cast to match
     * the left-hand side type, or a guarding mechanism is applied to prevent invalid values.
     *
     * @param left the left-hand side {@link TypedExpression}
     * @param right the right-hand side {@link TypedExpression}
     * @return a new {@link TypedExpression} representing the equality between the two expressions
     */
    // NOTICEsignature
    // NOTICEthrows
    private static TypedExpression makeTypedEquality(TypedExpression left, TypedExpression right) {
        // It is assumed that this routine is called form
        // makeEqualityAssertion where the proper checks have been
        // undertaken. Both expressions are typed, different,
        // and the right expression allows a lossless widening conversion
        // to the type of the left expression.
        Class<?> leftType = left.getType();
        Class<?> rightType = right.getType();

        Expression castedRight;
        if (rightType == null) {
            // A constant null is assigned. No casts are required.
            castedRight = right.getExpression();
        } else if (rightType.isPrimitive()) {
            // The equality will only hold if the two sides result in the
            // same value. The conversion that is required is done in such
            // a way that it is invertible. The two casts will automatically
            // detect cases where one side is out of range for the other side.
            castedRight = new InvertibleExpression(
                leftType, right.getExpression(), new CastFunction(leftType),
                new CastFunction(rightType));
        } else {
            // There is no conversion involved, but we must make sure
            // that no illegal value is fed back into the right expression
            // that has a narrower type.
            castedRight = makeGuardedExpression(right);
        }

        return new TypedExpression(
            left.getType(),
            new EqualsExpression(left.getType(), left.getExpression(), castedRight));
    }

    /**
     * Creates a typed equality assertion between two {@link TypedExpression} objects,
     * ensuring that the types of the left and right expressions are compatible
     * for equality comparison. The method handles various cases of type compatibility
     * and performs necessary type conversions or error handling.
     * The method checks whether the expressions are typed and whether their types
     * are compatible for equality comparison. If both expressions are typed,
     * it ensures that the types can either be compared directly or be widened
     * without loss of information. If necessary, type conversions or guard checks
     * are applied to ensure type safety.
     *
     * @param left the left-hand side {@link TypedExpression} to compare
     * @param right the right-hand side {@link TypedExpression} to compare
     * @param errToken the {@link Token} used to provide context for error handling
     * @return a new {@link TypedExpression} representing the equality comparison
     * @throws ParseException if the types of the left and right expressions are incompatible
     * for equality comparison or if an illegal type is used (e.g., {@code void})
     */
    public static TypedExpression makeEqualityAssertion(
        TypedExpression left, TypedExpression right, Token errToken) throws ParseException
    {
        Class<?> leftType = left.getType();
        Class<?> rightType = right.getType();
        if (leftType == Void.TYPE || rightType == Void.TYPE) {
            throw makeParseException(VOID_TYPE_ERROR, errToken);
        }
        if (!left.isTyped() || !right.isTyped()
            || Types.allowsIdentityConversion(leftType, rightType)) {
            return new TypedExpression(
                leftType, new EqualsExpression(
                    leftType, makeGuardedExpression(left), makeGuardedExpression(right)));
        }

        // Both expressions are typed.
        if (Types.allowsLosslessWidening(rightType, leftType)) {
            // NOTICEsignature
            return makeTypedEquality(left, right);
        }
        if (Types.allowsLosslessWidening(leftType, rightType)) {
            // NOTICEsignature
            return makeTypedEquality(right, left);
        }
        throw makeParseException("Type mismatch in assignment.", errToken);
    }

    /**
     * Creates a single-part expression based on the provided primary part, right-hand side expression,
     * and declaration node. This method serves as a simplified version of the more general
     * {@link #makeSinglePartExpression(PrimaryPart, TypedExpression, ParsedDeclarationNode, boolean)}
     * method with the default value for the last parameter set to {@code false}.
     * This method constructs a {@link TypedExpression} for a single part expression by combining
     * the given `PrimaryPart` with the `TypedExpression` on the right-hand side. The `declNode`
     * provides additional context during the creation process.
     *
     * @param firstPart the first part of the expression, represented as a {@link PrimaryPart}
     * @param right the right-hand side {@link TypedExpression} to combine with the first part
     * @param declNode the {@link ParsedDeclarationNode} providing additional context for expression construction
     * @return a new {@link TypedExpression} representing the combined single-part expression
     * @throws ParseException if there is an error during the construction of the expression
     */
    public static TypedExpression makeSinglePartExpression(
        PrimaryPart firstPart, TypedExpression right, ParsedDeclarationNode declNode)
        throws ParseException
    {
        return makeSinglePartExpression(firstPart, right, declNode, false);
    }

    /**
     * Constructs a single-part expression based on the provided primary part, right-hand side expression,
     * declaration node, and an optional refactoring flag.
     * This method handles different types of primary parts (expression, variable name) and performs
     * appropriate checks for assignments, type conversions, and variable lookups. If refactoring is enabled,
     * token information for variables is added. The method also handles the creation of expressions
     * based on local variables or classes, ensuring type safety and proper handling of assignments.
     *
     * @param firstPart the primary part of the expression (either an expression or a variable name)
     * @param right the right-hand side {@link TypedExpression} to combine with the primary part
     * @param declNode the {@link ParsedDeclarationNode} providing context for interpreting variable names and types
     * @param refactoring a flag indicating whether to add token information during refactoring
     * @return a {@link TypedExpression} representing the constructed single-part expression
     * @throws ParseException if there is a type mismatch or invalid assignment
     */
    public static TypedExpression makeSinglePartExpression(
        PrimaryPart firstPart, TypedExpression right, ParsedDeclarationNode declNode,
        boolean refactoring) throws ParseException
    {

        return switch (firstPart.type) {
            case PrimaryPart.EXPR -> handleExpressionCase(firstPart, right);
            case PrimaryPart.NAME -> handleNameCase(firstPart, right, declNode, refactoring);
            default -> throw new RuntimeException(MALFORMED_PRIMARY_VECTOR);
        };
    }

    /**
     * Handles the case where the primary part is an expression.
     * If the right-hand side expression is null, it simply returns the primary part's expression.
     * Otherwise, it throws a {@link ParseException} indicating an invalid left-hand side of an assignment.
     *
     * @param firstPart the primary part, which contains the expression
     * @param right the right-hand side {@link TypedExpression}
     * @return the primary part's {@link TypedExpression} if right is null
     * @throws ParseException if the right-hand side expression is not null
     */
    private static TypedExpression handleExpressionCase(
        PrimaryPart firstPart, TypedExpression right) throws ParseException
    {
        if (right == null) {
            return (TypedExpression) firstPart.obj;
        } else {
            throw makeParseException(INVALID_LEFT_HAND_SIDE, firstPart.token);
        }
    }

    /**
     * Handles the case where the primary part is a variable name.
     * Looks up the variable name in the provided declaration node and processes it accordingly.
     * Depending on the type of the variable (local variable or class), it either processes the assignment
     * or throws an appropriate exception for an unknown variable.
     *
     * @param firstPart the primary part, which contains the variable name
     * @param right the right-hand side {@link TypedExpression}
     * @param declNode the {@link ParsedDeclarationNode} used for variable lookup
     * @param refactoring a flag indicating whether token information should be added during refactoring
     * @return a {@link TypedExpression} representing the assignment or lookup of the variable
     * @throws ParseException if the variable does not exist, or an assignment is invalid
     */
    private static TypedExpression handleNameCase(
        PrimaryPart firstPart, TypedExpression right, ParsedDeclarationNode declNode,
        boolean refactoring) throws ParseException
    {
        Object meaning = getMeaningOfVariable(firstPart, declNode);

        if (meaning instanceof LocalVariable) {
            return handleLocalVariableCase(
                (LocalVariable) meaning, right, declNode, firstPart, refactoring);
        } else if (meaning instanceof Class<?>) {
            throw makeParseException(
                NO_SUCH_VARIABLE + firstPart.obj + " (denotes a class).", firstPart.token);

        } else {
            return handleUnknownVariableCase(firstPart, right, declNode);
        }
    }

    /**
     * Looks up the meaning of a variable (either a local variable or class) from the given declaration node.
     * If the variable name cannot be resolved, a {@link ParseException} is thrown.
     *
     * @param firstPart the primary part, which contains the variable name
     * @param declNode the {@link ParsedDeclarationNode} used for variable lookup
     * @return the resolved meaning of the variable, either a {@link LocalVariable} or a {@link Class}
     * @throws ParseException if there is a linkage error while resolving the variable name
     */
    private static Object getMeaningOfVariable(
        PrimaryPart firstPart, ParsedDeclarationNode declNode) throws ParseException
    {
        try {
            return declNode.interpreteName((String) firstPart.obj);
        } catch (LinkageError e) {
            String errorMessage = NO_SUCH_VARIABLE + firstPart.obj
                + "\n(denotes class with linkage problem:\n" + e + ").";
            _logger.warn(errorMessage);
            _logger.debug(ENCOUNTERED + e, e);
            throw makeParseException(errorMessage, firstPart.token);
        }
    }

    /**
     * Handles the case where the variable is a local variable. Depending on whether the right-hand side expression
     * is provided, it either creates a {@link TypedExpression} representing the variable or processes an assignment.
     *
     * @param variable the {@link LocalVariable} to be assigned
     * @param right the right-hand side {@link TypedExpression}
     * @param declNode the {@link ParsedDeclarationNode} used for variable lookup
     * @param firstPart the primary part, which contains the variable name
     * @param refactoring a flag indicating whether token information should be added during refactoring
     * @return a {@link TypedExpression} representing the assignment or variable
     * @throws ParseException if there is a type mismatch during assignment
     */
    private static TypedExpression handleLocalVariableCase(
        LocalVariable variable, TypedExpression right, ParsedDeclarationNode declNode,
        PrimaryPart firstPart, boolean refactoring) throws ParseException
    {
        Class<?> type = declNode.findType(variable);

        if (refactoring) {
            addTokenInformation(variable, firstPart.token.next);
        }

        if (right == null) {
            return new TypedExpression(type, new VariableExpression(type, variable));
        } else {
            return handleAssignment(type, variable, right, firstPart);
        }
    }

    /**
     * Handles the assignment logic for a local variable. Depending on the type of the variable and the type of the
     * right-hand side expression, it either directly assigns the value or applies necessary type conversions.
     *
     * @param type the type of the variable being assigned to
     * @param variable the {@link LocalVariable} being assigned to
     * @param right the right-hand side {@link TypedExpression}
     * @param firstPart the primary part, which contains the variable name
     * @return a {@link TypedExpression} representing the assignment
     * @throws ParseException if there is a type mismatch during assignment
     */
    private static TypedExpression handleAssignment(
        Class<?> type, LocalVariable variable, TypedExpression right, PrimaryPart firstPart)
        throws ParseException
    {
        // This is called in an action inscription
        // where we do not want to propagate information
        // backwards due to consistency reasons.
        //
        // Furthermore, an inscription like "action x=y"
        // is accepted as compile time type safe, if
        // x is assignable from y. But backward information
        // transfer would ruin this condition.
        //
        // Note that the inscription "x=y" is interpreted
        // and implemented differently.
        if (type == Types.UNTYPED) {
            return new TypedExpression(
                type,
                new EqualsExpression(
                    type, new VariableExpression(type, variable),
                    new CallExpression(right.getType(), right.getExpression(), Identity.FUN)));
        } else if (right.isTyped() && Types.allowsWideningConversion(right.getType(), type)) {
            return new TypedExpression(
                type,
                new EqualsExpression(
                    type, new VariableExpression(type, variable), new CallExpression(
                        right.getType(), right.getExpression(), new CastFunction(type))));
        } else {
            throw makeParseException(
                "Cannot assign " + makeTypeErrorString(right.getType()) + TO
                    + makeTypeErrorString(type) + PERIOD,
                firstPart.token);
        }
    }

    /**
     * Handles the case where the variable cannot be found or resolved, and suggests possible variable names.
     * A {@link ParseException} is thrown with extended information about possible variables that match the name.
     *
     * @param firstPart the primary part, which contains the variable name
     * @param right the right-hand side {@link TypedExpression}
     * @param declNode the {@link ParsedDeclarationNode} used for variable lookup
     * @return a {@link ParseException} with extended information about variable suggestions
     * @throws ParseException if no variable is found and suggestions are provided
     */
    private static TypedExpression handleUnknownVariableCase(
        PrimaryPart firstPart, TypedExpression right, ParsedDeclarationNode declNode)
        throws ParseException
    {
        _logger.debug("No variable " + firstPart.obj + DECLARED_IN + declNode);

        Collection<VariableSuggestion> variableSuggestions =
            VariableSuggestion.suggest(firstPart, right, declNode);

        throw makeExtendedParseException(
            NO_SUCH_VARIABLE + firstPart.obj, variableSuggestions, firstPart.token);
    }

    /**
     * Constructs a {@link TypedExpression} for invoking a constructor on the given class.
     * This method attempts to find the appropriate constructor based on the argument types,
     * either at compile-time (if types are provided) or at runtime (if there are untyped arguments).
     * It also handles exceptions related to constructor resolution and provides meaningful error messages.
     *
     * @param clazz the class for which the constructor should be invoked
     * @param vector a {@link Vector} of {@link TypedExpression}s representing the arguments to the constructor
     * @param errToken the {@link Token} to be used in the error messages
     * @return a {@link TypedExpression} representing the constructor call
     * @throws ParseException if the constructor cannot be found or there is an error during the process
     */
    public static TypedExpression makeConstructorExpression(
        Class<?> clazz, Vector<TypedExpression> vector, Token errToken) throws ParseException
    {
        Class<?>[] types = makeTypeArray(vector);
        if (types != null) {
            // All arguments were typed.
            // Determine the constructor now.
            Constructor<?> constructor;
            try {
                constructor = Executor.findBestConstructor(clazz, types, true);
                if (constructor == null) {
                    throw makeParseException(
                        "Multiple constructors match: "
                            + Executor.renderMethodSignature(clazz, INIT, types),
                        errToken);
                }
            } catch (NoSuchMethodException e) {
                Collection<ConstructorSuggestion> suggestions =
                    ConstructorSuggestion.suggest(clazz);
                throw makeExtendedParseException(
                    "No such constructor: " + Executor.renderMethodSignature(clazz, INIT, types),
                    suggestions, errToken);
            } catch (LinkageError e) {
                _logger.warn(ENCOUNTERED + e, e);
                throw makeParseException(
                    "Cannot not bind constructor "
                        + Executor.renderMethodSignature(clazz, INIT, types) + DUE_TO + e + COLON,
                    errToken);
            }
            return new TypedExpression(
                clazz, new CallExpression(
                    clazz, makeTupleExpression(vector), new ConstructorFunction(constructor)));
        } else {
            // At least one argument was untyped.
            // Determine the constructor at runtime.
            return new TypedExpression(
                clazz, new CallExpression(
                    clazz, makeTupleExpression(vector), new DynamicConstructorFunction(clazz)));
        }
    }

    /**
     * Constructs a {@link TypedExpression} for invoking a method on a given expression.
     * This method handles both the case of invoking an instance method (or a dynamic method call)
     * and the case of invoking a static method. It also checks for valid arguments,
     * handles errors related to non-existent or overloaded methods, and supports dynamic method
     * invocations if the argument types are untyped.
     * If {@code superClass} is non-null, this method attempts to invoke a method on the specified
     * superclass. However, due to limitations in the reflection API, invoking a method on a superclass
     * explicitly is not supported.
     *
     * @param expr the {@link TypedExpression} representing the object on which the method is invoked
     * @param superClass the class representing the superclass (if invoking a method on a superclass), or null
     * @param name the name of the method to be invoked
     * @param args a {@link Vector} of {@link TypedExpression}s representing the arguments to the method
     * @param errorToken the {@link Token} used for error reporting
     * @return a {@link TypedExpression} representing the result of the method invocation
     * @throws ParseException if the method cannot be found, if there is a type mismatch,
     * or if any error occurs during method resolution
     */
    // superClass: If non-null, designates the class for which
    // a method should be sought. Typically used for
    // super.method(...) handling.
    // name: method name
    // types: must match the types of the argument vector
    // args: argument vector of typed expressions
    public static TypedExpression makeMethodCall(
        TypedExpression expr, Class<?> superClass, String name, Vector<TypedExpression> args,
        Token errorToken) throws ParseException
    {
        if (superClass != null) {
            // Unfortunately, there seems to be no way to invoke an
            // overridden method by means of the reflection API.
            // Therefore, it is impossible to specify the invoked
            // base class explicitly.
            throw JavaHelper
                .makeParseException("Calls to super object are not supported.", errorToken);
        }

        Class<?> targetType = expr.getType();
        if (targetType == null) {
            throw makeParseException("Cannot invoke method on null object.", errorToken);
        }

        Class<?>[] types = makeTypeArray(args);
        if (types == null || !expr.isTyped()) {
            // This case does not need to be caught because it gets already
            // caught above
            /*
             * if (superClass != null) { throw makeParseException(
             * "Cannot invoke super method with untyped arguments.",
             * errorToken); }
             */
            return new TypedExpression(
                Types.UNTYPED,
                new CallExpression(
                    Types.UNTYPED,
                    new TupleExpression(expr.getExpression(), makeTupleExpression(args)),
                    new DynamicMethodFunction(name)));
        } else {
            // This does not work. See explanation above.
            /*
             * if (superClass != null) { targetType = superClass; }
             */
            Method method;
            try {
                method = Executor.findBestMethod(targetType, name, types, true);
                if (method == null) {
                    throw makeParseException(
                        MULTIPLE_METHODS_MATCH
                            + Executor.renderMethodSignature(targetType, name, types),
                        errorToken);
                }
            } catch (NoSuchMethodException e) {
                List<MethodSuggestion> methodSuggestions =
                    MethodSuggestion.suggest(targetType, name, types, Integer.MAX_VALUE);

                throw makeExtendedParseException(
                    "No such method: " + Executor.renderMethodSignature(targetType, name, types)
                        + checkForGivenPrefix(name) + JavaHelper.KNOWN_METHODS,
                    methodSuggestions, errorToken);
            } catch (LinkageError e) {
                _logger.warn(ENCOUNTERED + e, e);
                throw makeParseException(
                    CANNOT_NOT_BIND_METHOD + Executor.renderMethodSignature(targetType, name, types)
                        + DUE_TO + e + COLON,
                    errorToken);
            }

            // Even if the method is a static method,
            // it is invoked like an ordinary instance method.
            // This ensures the greatest compatibility to Java.
            return new TypedExpression(
                method.getReturnType(),
                new CallExpression(
                    method.getReturnType(),
                    new TupleExpression(expr.getExpression(), makeTupleExpression(args)),
                    new MethodFunction(method)));
        }
    }

    /**
     * Constructs an expression based on the provided primary parts, right-hand side expression,
     * declaration node, and error token. This method acts as a convenience method that calls
     * another version of makeExpression with the refactoring flag set to {@code false}.
     * It handles the creation of expressions where the right-hand side expression and additional
     * declaration context are used to construct a valid expression.
     *
     * @param vector the vector containing the primary parts that make up the left-hand side of the expression
     * @param right the right-hand side {@link TypedExpression} to combine with the primary parts
     * @param declNode the {@link ParsedDeclarationNode} providing context for interpreting variable names and types
     * @param rightErrToken the {@link Token} representing the error context for the right-hand side expression
     * @return a {@link TypedExpression} representing the constructed expression
     * @throws ParseException if an error occurs during the construction of the expression
     */
    public static TypedExpression makeExpression(
        Vector<PrimaryPart> vector, TypedExpression right, ParsedDeclarationNode declNode,
        Token rightErrToken) throws ParseException
    {
        return makeExpression(vector, right, declNode, rightErrToken, false);
    }

    /**
     * Constructs a typed expression based on a sequence of primary parts, an optional right-hand side expression,
     * and a declaration node. This method can handle various types of expressions such as class names, method calls,
     * field accesses, and array indexing. It also supports the creation of assignment expressions if a right-hand
     * side expression is provided.
     * The method processes the given primary parts step by step, analyzing each part in sequence and building a
     * corresponding expression. The primary parts can include expressions, names (representing variables or classes),
     * arrays, and method calls. The result is a `TypedExpression` representing the parsed and constructed expression.
     * If refactoring is enabled, the method also handles local variables, ensuring the correct type is applied during
     * method and field accesses. If a right-hand side expression is present, it ensures proper assignment and conversion.
     *
     * @param vector A vector of `PrimaryPart` objects representing the parts of the expression to be parsed.
     * @param right The optional right-hand side expression for assignments. Can be `null` if no assignment is needed.
     * @param declNode The declaration node used to interpret variable names and resolve references to classes or variables.
     * @param rightErrToken The token associated with the right-hand side expression, used for error reporting.
     * @param refactoring A flag indicating whether refactoring is enabled, affecting local variable handling.
     * @return A `TypedExpression` representing the parsed expression.
     * @throws ParseException If an error occurs during parsing, such as an invalid or malformed expression or unresolved name.
     * @throws RuntimeException If an unexpected error occurs during the process, such as a malformed primary vector.
     */
    public static TypedExpression makeExpression(
        Vector<PrimaryPart> vector, TypedExpression right, ParsedDeclarationNode declNode,
        Token rightErrToken, boolean refactoring) throws ParseException
    {
        if (right != null) {
            ensureNonVoid(right, rightErrToken);
        }

        PrimaryPart firstPart = vector.elementAt(0);

        if (vector.size() == 1) {
            return makeSinglePartExpression(firstPart, right, declNode, refactoring);
        }

        // Ok, so it is a long vector, we must analyse it part by part.
        int m = vector.size();
        if (right != null) {
            // We must create an assignment, so we leave a single
            // item in the queue, if we haven't read it so far.
            m--;
        }

        // How many parts did we analyse?
        int i = 1;

        // During the composition the following variable is either
        // null or a class or a local variable or a typed expression.
        // Ultimately, it will hold an expression.
        Object composed = null;

        // The longest name tried is stored in the case we need to report
        // an error.
        String name;

        switch (firstPart.type) {
            case PrimaryPart.EXPR:
                composed = firstPart.obj;
                name = firstPart.toString();
                break;
            case PrimaryPart.NAME:
                name = (String) firstPart.obj;
                if (declNode == null) {
                    break;
                }
                try {
                    composed = declNode.interpreteName(name);
                } catch (LinkageError e) {
                    _logger.warn(ENCOUNTERED + e, e);
                    throw makeParseException(LOADING_CLASS + name + DUE_TO + e, firstPart.token);
                }

                // Where did we find the longest match?
                int lasti = i;

                while (i < m) {
                    PrimaryPart part = vector.elementAt(i);
                    i++;
                    if (part.type != PrimaryPart.NAME) {
                        // Leave the while loop.
                        // I hate to do this, but here it is really the
                        // best solution.
                        break;
                    }
                    name = name + PERIOD + part.obj;
                    Object newComposed;
                    try {
                        newComposed = declNode.interpreteName(name);
                    } catch (LinkageError e) {
                        _logger.warn(ENCOUNTERED + e, e);
                        throw makeParseException(
                            LOADING_CLASS + name + " due to linkage problem:\n" + e,
                            firstPart.token);
                    }
                    if (newComposed != null) {
                        composed = newComposed;
                        lasti = i;
                    }
                }

                // Reset the list pointer to the last value
                // where we parsed a name.
                i = lasti;

                // At this time composed might be null if no appropriate name
                // was found.
                break;
            default:
                throw new RuntimeException(MALFORMED_PRIMARY_VECTOR);
        }

        if (composed == null) {
            _logger.debug("No class or variable " + name + DECLARED_IN + declNode);
            throw makeParseException(NO_SUCH_CLASS_OR_VARIABLE + name, firstPart.token);
        }

        // Let's get rid of local variables.
        if (composed instanceof LocalVariable) {
            if (refactoring) {
                addTokenInformation((LocalVariable) composed, firstPart.token.next);
            }

            Class<?> type = declNode.findType((LocalVariable) composed);
            composed =
                new TypedExpression(type, new VariableExpression(type, (LocalVariable) composed));
        }

        // From now on, the composed object can only be a class or
        // a typed expression.
        while (i < m) {
            PrimaryPart part = vector.elementAt(i);
            i++;
            switch (part.type) {
                case PrimaryPart.NAME:
                    // Does an argument list follow?
                    Vector<TypedExpression> args = null;
                    if (i < vector.size()) {
                        PrimaryPart additionalPart = vector.elementAt(i);
                        if (additionalPart.type == PrimaryPart.CALL) {
                            i++;
                            // The warning regarding the next cast can be suppressed
                            // because the type has
                            // been checked by
                            // "additionalPart.type == PrimaryPart.CALL"
                            @SuppressWarnings("unchecked")
                            Vector<TypedExpression> vec =
                                (Vector<TypedExpression>) additionalPart.obj;
                            args = vec;
                        }
                    }

                    if (args != null) {
                        // Method call.
                        if (composed instanceof Class<?>) {
                            Class<?>[] types = makeTypeArray(args);
                            if (types == null) {
                                composed = new TypedExpression(
                                    Types.UNTYPED,
                                    new CallExpression(
                                        Types.UNTYPED, makeTupleExpression(args),
                                        new DynamicStaticMethodFunction(
                                            (String) part.obj, (Class<?>) composed)));
                            } else {
                                Method method;
                                try {
                                    method = Executor.findBestMethod(
                                        (Class<?>) composed, (String) part.obj, types, true);
                                    if (method == null) {
                                        throw makeParseException(
                                            MULTIPLE_METHODS_MATCH + Executor.renderMethodSignature(
                                                (Class<?>) composed, (String) part.obj, types),
                                            part.token);
                                    }
                                } catch (NoSuchMethodException e) {
                                    List<MethodSuggestion> methodSuggestions =
                                        MethodSuggestion.suggest(
                                            (Class<?>) composed, (String) part.obj, types,
                                            Modifier.STATIC);

                                    throw makeExtendedParseException(
                                        "No such static method: "
                                            + Executor.renderMethodSignature(
                                                (Class<?>) composed, (String) part.obj, types)
                                            + checkForGivenPrefix((String) part.obj)
                                            + JavaHelper.KNOWN_METHODS,
                                        methodSuggestions, part.token);
                                } catch (LinkageError e) {
                                    _logger.warn(ENCOUNTERED + e, e);
                                    throw makeParseException(
                                        CANNOT_NOT_BIND_METHOD
                                            + Executor.renderMethodSignature(
                                                (Class<?>) composed, (String) part.obj, types)
                                            + DUE_TO + e + COLON,
                                        part.token);
                                }
                                if ((method.getModifiers() & Modifier.STATIC) == 0) {
                                    throw makeParseException(
                                        "Cannot make static call to " + "instance method.",
                                        part.token);
                                }
                                composed = new TypedExpression(
                                    method.getReturnType(),
                                    new CallExpression(
                                        method.getReturnType(), makeTupleExpression(args),
                                        new StaticMethodFunction(method)));
                            }
                        } else {
                            // composed instanceof TypedExpression
                            composed = makeMethodCall(
                                (TypedExpression) composed, null, (String) part.obj, args,
                                part.token);
                        }
                    } else {
                        // Field access.
                        if (composed instanceof Class<?> clazz) {
                            Field field;
                            try {
                                field = clazz.getField((String) part.obj);
                            } catch (Exception e) {
                                List<FieldSuggestion> fieldSuggestions = FieldSuggestion.suggest(
                                    (Class<?>) composed, (String) part.obj, Modifier.STATIC);
                                throw makeExtendedParseException(
                                    NO_SUCH_STATIC_FIELD + part.obj + JavaHelper.KNOWN_FIELDS,
                                    fieldSuggestions, part.token);
                            }
                            composed = new TypedExpression(
                                field.getType(), new NoArgExpression(
                                    field.getType(), new StaticFieldFunction(field)));
                        } else {
                            // composed instanceof TypedExpression
                            if (((TypedExpression) composed).isTyped()) {
                                Class<?> type = ((TypedExpression) composed).getType();
                                if (type == null) {
                                    throw makeParseException(
                                        "Cannot access field of null object.", part.token);
                                }
                                if ((type.isArray()) && ("length".equals(part.obj))) {
                                    composed = new TypedExpression(
                                        Integer.TYPE,
                                        new CallExpression(
                                            Integer.TYPE,
                                            ((TypedExpression) composed).getExpression(),
                                            new DynamicFieldFunction((String) part.obj)));
                                } else {
                                    Field field;
                                    try {
                                        field = type.getField((String) part.obj);
                                    } catch (Exception e) {
                                        List<FieldSuggestion> fieldSuggestions = FieldSuggestion
                                            .suggest(type, (String) part.obj, Integer.MAX_VALUE);
                                        throw makeExtendedParseException(
                                            NO_SUCH_STATIC_FIELD + part.obj
                                                + JavaHelper.KNOWN_FIELDS,
                                            fieldSuggestions, part.token);
                                    }
                                    composed = new TypedExpression(
                                        field.getType(),
                                        new CallExpression(
                                            field.getType(),
                                            ((TypedExpression) composed).getExpression(),
                                            new FieldFunction(field)));
                                }
                            } else {
                                composed = new TypedExpression(
                                    Types.UNTYPED,
                                    new CallExpression(
                                        Types.UNTYPED, ((TypedExpression) composed).getExpression(),
                                        new DynamicFieldFunction((String) part.obj)));
                            }
                        }
                    }
                    break;
                case PrimaryPart.ARRAY:
                    if (!(composed instanceof TypedExpression)) {
                        throw makeParseException(NO_SUCH_CLASS_OR_VARIABLE, part.token);
                    }
                    Class<?> type = Types.UNTYPED;
                    if (((TypedExpression) composed).isTyped()) {
                        type = ((TypedExpression) composed).getType();
                        if (!type.isArray()) {
                            throw makeParseException(NOT_AN_ARRAY, part.token);
                        }
                        type = type.getComponentType();
                    }
                    ensureConvertability(Integer.TYPE, (TypedExpression) part.obj, part.token);
                    composed = new TypedExpression(
                        type,
                        new CallExpression(
                            type,
                            new TupleExpression(
                                ((TypedExpression) composed).getExpression(),
                                ((TypedExpression) part.obj).getExpression()),
                            ArrayFunction.FUN));
                    break;
                case PrimaryPart.CALL:
                    throw makeParseException("Bad method call or no such method.", part.token);
                default:
                    throw new RuntimeException(MALFORMED_PRIMARY_VECTOR);
            }
        }

        if (right != null) {
            if (i == vector.size()) {
                throw makeParseException(INVALID_LEFT_HAND_SIDE, rightErrToken);
            }

            // Process the very last item in the queue.
            PrimaryPart part = vector.elementAt(i);
            switch (part.type) {
                case PrimaryPart.NAME:
                    if (composed instanceof TypedExpression) {
                        if (((TypedExpression) composed).isTyped()) {
                            Class<?> type = ((TypedExpression) composed).getType();
                            Field field;
                            try {
                                field = type.getField((String) part.obj);
                            } catch (Exception e) {
                                throw makeParseException(NO_SUCH_FIELD, part.token);
                            }
                            ensureConvertability(field.getType(), right, rightErrToken);
                            composed = new TypedExpression(
                                field.getType(),
                                new CallExpression(
                                    field.getType(),
                                    new TupleExpression(
                                        ((TypedExpression) composed).getExpression(),
                                        right.getExpression()),
                                    new FieldWriteFunction(field)));
                        } else {
                            composed = new TypedExpression(
                                Types.UNTYPED,
                                new CallExpression(
                                    Types.UNTYPED,
                                    new TupleExpression(
                                        ((TypedExpression) composed).getExpression(),
                                        right.getExpression()),
                                    new DynamicFieldWriteFunction((String) part.obj)));
                        }
                    } else {
                        // composed instanceof Class
                        Class<?> clazz = (Class<?>) composed;
                        Field field;
                        try {
                            field = clazz.getField((String) part.obj);
                        } catch (Exception e) {
                            throw makeParseException(NO_SUCH_FIELD, part.token);
                        }
                        ensureConvertability(field.getType(), right, rightErrToken);
                        composed = new TypedExpression(
                            field.getType(),
                            new CallExpression(
                                field.getType(), right.getExpression(),
                                new StaticFieldWriteFunction(field)));
                    }
                    break;
                case PrimaryPart.ARRAY:
                    if (!(composed instanceof TypedExpression)) {
                        throw makeParseException(NO_SUCH_CLASS_OR_VARIABLE, part.token);
                    }
                    Class<?> type = Types.UNTYPED;
                    if (((TypedExpression) composed).isTyped()) {
                        type = ((TypedExpression) composed).getType();
                        if (!type.isArray()) {
                            throw makeParseException(NOT_AN_ARRAY, part.token);
                        }
                        type = type.getComponentType();
                        ensureConvertability(type, right, rightErrToken);
                    }
                    ensureConvertability(Integer.TYPE, (TypedExpression) part.obj, part.token);
                    composed = new TypedExpression(
                        type,
                        new CallExpression(
                            type,
                            new TupleExpression(
                                ((TypedExpression) composed).getExpression(),
                                ((TypedExpression) part.obj).getExpression(),
                                right.getExpression()),
                            ArrayWriteFunction.FUN));
                    break;
                default:
                    throw new RuntimeException(MALFORMED_PRIMARY_VECTOR);
            }
        }

        if (!(composed instanceof TypedExpression)) {
            throw makeParseException(NO_SUCH_CLASS_OR_VARIABLE, firstPart.token);
        }

        return (TypedExpression) composed;
    }

    /**
     * Checks if the user has filtered his search request. Decides if the header
     * needs one more row to show the letters the user has filtered for.
     *
     * @param obj String of the users input after the object-identifier.
     * @return empty String or String that is showing the letters the user has
     * filtered for to add to the header.
     */
    private static String checkForGivenPrefix(String obj) {
        String name = obj;

        if (name.endsWith("_")) {
            name = name.substring(0, name.length() - 1);
            if (!name.isEmpty()) {
                name = " \n (prefixing \"" + name + "\")";
            }
        }

        return name;
    }

    private static void addTokenInformation(LocalVariable var, Token t) {
        var.setVariableBeginLine(t.beginLine);
        var.setVariableBeginColumn(t.beginColumn);
        var.setVariableEndLine(t.endLine);
        var.setVariableEndColumn(t.endColumn);

    }
}