/*
 * Copyright 2021, TeamDev. All rights reserved.
 *
 * 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
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.tools.javadoc;

import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;
import com.sun.tools.javadoc.Main;
import io.spine.annotation.Internal;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * Extension of {@linkplain Standard} doclet, which excludes
 * {@linkplain Internal}-annotated components.
 *
 * <p>Use it to generate documentation for audience, that should not know about
 * {@linkplain Internal}-annotated components.
 *
 * <p>Works by pre-processing a {@linkplain RootDoc}.
 * The doclet creates new {@linkplain RootDoc},
 * that does not contain {@linkplain Internal}-annotated components and further generates documents.
 *
 * <p>You can use the non-standard doclet by specifying the following Javadoc options:
 * <ul>
 *     <li>doclet io.spine.tools.javadoc.ExcludeInternalDoclet;</li>
 *     <li>docletpath classpathlist (The path to the doclet starting class file).</li>
 * </ul>
 *
 * <p>Call it with Javadoc tool like this:
 * <pre> {@code javadoc -doclet io.spine.tools.javadoc.ExcludeInternalDoclet
 * -docletpath "classpathlist" ...}</pre>
 *
 * <p>If everything done right, you will get the standard documentation generated by Javadoc tool,
 * except {@linkplain Internal}-annotated components.
 */
@SuppressWarnings("ExtendsUtilityClass")
public class ExcludeInternalDoclet extends Standard {

    private final ExcludePrinciple excludePrinciple;

    ExcludeInternalDoclet(ExcludePrinciple excludePrinciple) {
        super();
        this.excludePrinciple = excludePrinciple;
    }

    /**
     * Entry point for the Javadoc tool.
     *
     * @param args the command-line parameters
     */
    public static void main(String[] args) {
        String name = ExcludeInternalDoclet.class.getName();
        Main.execute(name, name, args);
    }

    /**
     * The "start" method as required by Javadoc.
     *
     * @param root the root of the documentation tree
     * @return {@code true} if the doclet ran without encountering any errors,
     * {@code false} otherwise
     */
    @SuppressWarnings({"unused", "RedundantSuppression"}) // called by com.sun.tools.javadoc.Main
    public static boolean start(RootDoc root) {
        ExcludePrinciple excludePrinciple = new ExcludeInternal(root);
        ExcludeInternalDoclet doclet = new ExcludeInternalDoclet(excludePrinciple);
        return Standard.start((RootDoc) doclet.process(root, RootDoc.class));
    }

    /**
     * Creates proxy of "com.sun..." interfaces and excludes
     * {@linkplain ProgramElementDoc}s using {@linkplain #excludePrinciple}.
     *
     * @param returnValue the value to process
     * @param returnValueType the expected type of value
     * @return the processed value
     */
    @Nullable
    Object process(@Nullable Object returnValue, Class<?> returnValueType) {
        if (returnValue == null) {
            return null;
        }
        boolean belongsToComSun =
                returnValue.getClass()
                           .getName()
                           .startsWith("com.sun.");
        if (belongsToComSun) {
            return createProxy(returnValue);
        } else if (returnValue instanceof Object[] && returnValueType.getComponentType() != null) {
            return createArray((Object[]) returnValue, returnValueType);
        } else {
            return returnValue;
        }
    }

    @NonNull
    private Object[] createArray(Object[] returnValue, Class<?> returnValueType) {
        Class<?> componentType = returnValueType.getComponentType();
        Object[] array = returnValue;
        List<Object> list = new ArrayList<>();
        for (Object entry : array) {
            if (!(entry instanceof ProgramElementDoc
                    && excludePrinciple.shouldExclude((ProgramElementDoc) entry))) {
                list.add(process(entry, componentType));
            }
        }
        Object[] arr = list.toArray((Object[]) Array.newInstance(componentType, list.size()));
        return arr;
    }

    @NonNull
    private Object createProxy(@NonNull Object returnValue) {
        Class<?> cls = returnValue.getClass();
        InvocationHandler handler = new ExcludeHandler(this, returnValue);
        Object proxy = Proxy.newProxyInstance(
                cls.getClassLoader(), cls.getInterfaces(), handler
        );
        return proxy;
    }
}

@@ -5276,21 +5276,12 @@
 ple.
-shouldExclude
+test
 ((Pr

Fix method call