/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.implementation.bind.annotation;

import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.utility.CompoundList;

import java.lang.annotation.*;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * Parameters that are annotated with this annotation will be assigned a collection (or an array) containing
 * all arguments of the source method. Currently, this annotation supports the following collection types:
 * <ul>
 * <li>Array</li>
 * </ul>
 * </p>
 * <p>
 * By default, this annotation applies a
 * {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#STRICT}
 * assignment of the source method's parameters to the array. This implies that parameters that are not assignable to
 * the annotated array's component type make the method with this parameter unbindable. To avoid this, you can
 * use a {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#SLACK} assignment
 * which simply skips non-assignable values instead.
 * </p>
 * <p>
 * <b>Important</b>: Don't confuse this annotation with {@link net.bytebuddy.asm.Advice.AllArguments} annotation. This annotation
 * should be used only in combination with method delegation
 * ({@link net.bytebuddy.implementation.MethodDelegation MethodDelegation.to(...)}).
 * For {@link net.bytebuddy.asm.Advice} ASM visitor use alternative annotation from
 * <code>net.bytebuddy.asm.Advice</code> package.
 * </p>
 *
 * @see net.bytebuddy.implementation.MethodDelegation
 * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
 * @see net.bytebuddy.implementation.bind.annotation.RuntimeType
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AllArguments {

    /**
     * Defines the type of {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment}
     * type that is applied for filling the annotated array with values.
     *
     * @return The assignment handling to be applied for the annotated parameter.
     */
    Assignment value() default Assignment.STRICT;

    /**
     * Determines if the array should contain the instance that defines the intercepted value when intercepting
     * a non-static method.
     *
     * @return {@code true} if the instance on which the intercepted method should be invoked should be
     * included in the array containing the arguments.
     */
    boolean includeSelf() default false;

    /**
     * Determines if a {@code null} value should be assigned if the instrumented method does not declare any parameters.
     *
     * @return {@code true} if a {@code null} value should be assigned if the instrumented method does not declare any parameters.
     */
    boolean nullIfEmpty() default false;

    /**
     * A directive for how an {@link net.bytebuddy.implementation.bind.annotation.AllArguments}
     * annotation on an array is to be interpreted.
     */
    enum Assignment {

        /**
         * A strict assignment attempts to include <b>all</b> parameter values of the source method. If only one of these
         * parameters is not assignable to the component type of the annotated array, the method is considered as
         * non-bindable.
         */
        STRICT(true),

        /**
         * Other than a {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#STRICT}
         * assignment, a slack assignment simply ignores non-bindable parameters and does not include them in the target
         * array. In the most extreme case where no source method parameter is assignable to the component type
         * of the annotated array, the array that is assigned to the target parameter is empty.
         */
        SLACK(false);

        /**
         * Determines if this assignment is strict.
         */
        private final boolean strict;

        /**
         * Creates a new assignment type.
         *
         * @param strict {@code true} if this assignment is strict.
         */
        Assignment(boolean strict) {
            this.strict = strict;
        }

        /**
         * Returns {@code true} if this assignment is strict.
         *
         * @return {@code true} if this assignment is strict.
         */
        protected boolean isStrict() {
            return strict;
        }
    }

    /**
     * A binder for handling the
     * {@link net.bytebuddy.implementation.bind.annotation.AllArguments}
     * annotation.
     *
     * @see TargetMethodAnnotationDrivenBinder
     */
    enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<AllArguments> {

        /**
         * The singleton instance.
         */
        INSTANCE;

        /**
         * {@inheritDoc}
         */
        public Class<AllArguments> getHandledType() {
            return AllArguments.class;
        }

        /**
         * {@inheritDoc}
         */
        public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<AllArguments> annotation,
                                                               MethodDescription source,
                                                               ParameterDescription target,
                                                               Implementation.Target implementationTarget,
                                                               Assigner assigner,
                                                               Assigner.Typing typing) {
            TypeDescription.Generic componentType;
            if (target.getType().represents(Object.class)) {
                componentType = TypeDescription.Generic.OBJECT;
            } else if (target.getType().isArray()) {
                componentType = target.getType().getComponentType();
            } else {
                throw new IllegalStateException("Expected an array type for all argument annotation on " + source);
            }
            boolean includeThis = !source.isStatic() && annotation.load().includeSelf();
            if (!includeThis && source.getParameters().isEmpty() && annotation.load().nullIfEmpty()) {
                return new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE);
            }
            List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(source.getParameters().size() + (includeThis ? 1 : 0));
            int offset = source.isStatic() || includeThis ? 0 : 1;
            for (TypeDescription.Generic sourceParameter : includeThis
                    ? CompoundList.of(implementationTarget.getInstrumentedType().asGenericType(), source.getParameters().asTypeList())
                    : source.getParameters().asTypeList()) {
                StackManipulation stackManipulation = new StackManipulation.Compound(
                        MethodVariableAccess.of(sourceParameter).loadFrom(offset),
                        assigner.assign(sourceParameter, componentType, typing)
                );
                if (stackManipulation.isValid()) {
                    stackManipulations.add(stackManipulation);
                } else if (annotation.load().value().isStrict()) {
                    return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
                }
                offset += sourceParameter.getStackSize().getSize();
            }
            return new MethodDelegationBinder.ParameterBinding.Anonymous(ArrayFactory.forType(componentType).withValues(stackManipulations));
        }
    }
}

@@ -1573,186 +1573,63 @@
 ed a
- collection (or an array) containing%0A * all arguments of the source method. Currently, this annotation supports the following collection types:%0A * %3Cul%3E%0A * %3Cli%3EArray%3C/li%3E%0A * %3C/ul%3E
+n array of all arguments of the instrumented%0A * method.
 %0A * 

Fix javadoc.