All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eolang.jeo.representation.bytecode.BytecodeAnnotationProperty Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2024 Objectionary.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.eolang.jeo.representation.bytecode;

import com.jcabi.log.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.eolang.jeo.representation.directives.DirectivesAnnotationProperty;
import org.objectweb.asm.AnnotationVisitor;
import org.xembly.Directive;

/**
 * Bytecode annotation property.
 * @since 0.3
 * @todo #537:60min Refactor {@link BytecodeAnnotationProperty}.
 *  The class uses public static methods to create instances of itself.
 *  We should use constructors instead of static methods.
 *  Don't forget to add/update the tests.
 */
@ToString
@EqualsAndHashCode
public final class BytecodeAnnotationProperty implements BytecodeAnnotationValue {

    /**
     * Type of the property.
     */
    private final Type type;

    /**
     * Property parameters.
     */
    private final List params;

    /**
     * Constructor.
     * @param type Type of the property.
     * @param params Property parameters.
     */
    private BytecodeAnnotationProperty(final Type type, final List params) {
        this.type = type;
        this.params = params;
    }

    /**
     * Factory method for enum property.
     * @param name Property name.
     * @param desc Property descriptor.
     * @param value Property value.
     * @return Property.
     */
    @SuppressWarnings("PMD.ProhibitPublicStaticMethods")
    public static BytecodeAnnotationProperty enump(
        final String name, final String desc, final String value
    ) {
        return new BytecodeAnnotationProperty(Type.ENUM, Arrays.asList(name, desc, value));
    }

    /**
     * Factory method for plain property.
     * @param name Property name.
     * @param value Property value.
     * @return Property.
     */
    @SuppressWarnings("PMD.ProhibitPublicStaticMethods")
    public static BytecodeAnnotationProperty plain(final String name, final Object value) {
        return new BytecodeAnnotationProperty(Type.PLAIN, Arrays.asList(name, value));
    }

    /**
     * Factory method for array property.
     * @param name Property name.
     * @param values Property values.
     * @return Property.
     */
    @SuppressWarnings("PMD.ProhibitPublicStaticMethods")
    public static BytecodeAnnotationProperty array(final String name, final List values) {
        return new BytecodeAnnotationProperty(
            Type.ARRAY,
            Stream.concat(Stream.of(name), values.stream()).collect(Collectors.toList())
        );
    }

    /**
     * Factory method for annotation property.
     * @param name Property name.
     * @param desc Property descriptor.
     * @param values Property values.
     * @return Property.
     */
    @SuppressWarnings("PMD.ProhibitPublicStaticMethods")
    public static BytecodeAnnotationProperty annotation(
        final String name, final String desc, final List values
    ) {
        return new BytecodeAnnotationProperty(
            Type.ANNOTATION,
            Stream.concat(Stream.of(name, desc), values.stream())
                .collect(Collectors.toList())
        );
    }

    /**
     * Factory method for property by type.
     * @param type Type.
     * @param params Parameters.
     * @return Property.
     */
    @SuppressWarnings("PMD.ProhibitPublicStaticMethods")
    public static BytecodeAnnotationProperty byType(final String type, final List params) {
        final BytecodeAnnotationProperty result;
        switch (Type.valueOf(type)) {
            case PLAIN:
                result = BytecodeAnnotationProperty.plain((String) params.get(0), params.get(1));
                break;
            case ENUM:
                result = BytecodeAnnotationProperty.enump(
                    (String) params.get(0), (String) params.get(1), (String) params.get(2)
                );
                break;
            case ARRAY:
                result = array((String) params.get(0), params.subList(1, params.size()));
                break;
            case ANNOTATION:
                Logger.debug(
                    BytecodeAnnotationProperty.class, "Annotation property params: %s", params
                );
                result = BytecodeAnnotationProperty.annotation(
                    (String) params.get(0), (String) params.get(1),
                    params.subList(2, params.size())
                );
                break;
            default:
                throw new IllegalArgumentException(
                    String.format(
                        "Unknown annotation property type %s",
                        type
                    )
                );
        }
        return result;
    }

    @Override
    public void writeTo(final AnnotationVisitor avisitor) {
        switch (this.type) {
            case PLAIN:
                avisitor.visit((String) this.params.get(0), this.params.get(1));
                break;
            case ENUM:
                avisitor.visitEnum(
                    (String) this.params.get(0),
                    (String) this.params.get(1),
                    (String) this.params.get(2)
                );
                break;
            case ARRAY:
                if (this.params.isEmpty()) {
                    avisitor.visitArray(null).visitEnd();
                } else {
                    final AnnotationVisitor array = avisitor.visitArray(
                        Optional.ofNullable(this.params.get(0))
                            .map(String.class::cast)
                            .orElse(null)
                    );
                    for (final Object param : this.params.subList(1, this.params.size())) {
                        ((BytecodeAnnotationValue) param).writeTo(array);
                    }
                    array.visitEnd();
                }
                break;
            case ANNOTATION:
                final AnnotationVisitor annotation = avisitor.visitAnnotation(
                    (String) this.params.get(0),
                    (String) this.params.get(1)
                );
                for (final Object param : this.params.subList(2, this.params.size())) {
                    ((BytecodeAnnotationValue) param).writeTo(annotation);
                }
                annotation.visitEnd();
                break;
            default:
                throw new IllegalStateException(String.format("Unexpected value: %s", this.type));
        }
    }

    @Override
    public Iterable directives() {
        final Iterable result;
        switch (this.type) {
            case ENUM:
                result = DirectivesAnnotationProperty.enump(
                    (String) this.params.get(0),
                    (String) this.params.get(1),
                    (String) this.params.get(2)
                );
                break;
            case PLAIN:
                result = DirectivesAnnotationProperty.plain(
                    (String) this.params.get(0),
                    this.params.get(1)
                );
                break;
            case ANNOTATION:
                result = DirectivesAnnotationProperty.annotation(
                    (String) this.params.get(0),
                    (String) this.params.get(1),
                    this.params.subList(2, this.params.size()).stream()
                        .map(BytecodeAnnotationProperty.class::cast)
                        .map(BytecodeAnnotationProperty::directives)
                        .collect(Collectors.toList())
                );
                break;
            case ARRAY:
                result = DirectivesAnnotationProperty.array(
                    (String) this.params.get(0),
                    this.params.subList(1, this.params.size()).stream()
                        .map(BytecodeAnnotationValue.class::cast)
                        .map(BytecodeAnnotationValue::directives)
                        .collect(Collectors.toList())
                );
                break;
            default:
                throw new IllegalStateException(String.format("Unexpected value: %s", this.type));
        }
        return result;
    }

    /**
     * Property types.
     */
    private enum Type {
        /**
         * Plain property.
         */
        PLAIN,

        /**
         * Enum property.
         */
        ENUM,

        /**
         * Array property.
         */
        ARRAY,

        /**
         * Annotation property.
         */
        ANNOTATION
    }
}