package com.sun.jna;

import com.google.common.collect.Lists;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.io.File;
import java.lang.reflect.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Utility class for detecting missing {@link com.sun.jna.Structure#getFieldOrder()} methods.
 *
 * This class could be moved to the unit test tree, but them reusing it in the 'platform' project would require
 * publishing this test tree.
 *
 * @author Dan Rollo
 * Date: 1/17/13
 * Time: 4:08 PM
 */
public final class StructureFieldOrderInspector {

    private StructureFieldOrderInspector(){}

    /**
     * Search for Structure sub types in the source tree of the given class, and validate the getFieldOrder() method.
     *
     * @param classDeclaredInSourceTreeToSearch a class who's source tree will be searched for Structure sub types.
     */
    public static void checkStructureGetFieldOrder(final Class classDeclaredInSourceTreeToSearch) {
        final Set<Class<? extends Structure>> classes = StructureFieldOrderInspector.findSubTypesOfStructure(classDeclaredInSourceTreeToSearch);

        for (final Class<? extends Structure> structureSubType : classes) {
            StructureFieldOrderInspector.checkMethodGetFieldOrder(structureSubType);
        }
    }

    /**
     * Find all classes that extend {@link Structure}.
     */
    public static Set<Class<? extends Structure >> findSubTypesOfStructure(final Class classDeclaredInSourceTreeToSearch) {

        // use: http://code.google.com/p/reflections/

        final Reflections reflections = new Reflections(new ConfigurationBuilder()
                .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClass(classDeclaredInSourceTreeToSearch))
        );

        return reflections.getSubTypesOf(Structure.class);
    }


    public static void checkMethodGetFieldOrder(final Class<? extends Structure> structureSubType) {

        if (Structure.ByValue.class.isAssignableFrom(structureSubType)
                || Structure.ByReference.class.isAssignableFrom(structureSubType)) {

            // ignore tagging interfaces
            return;
        }

        final Method methodGetFieldOrder;
        try {
            methodGetFieldOrder = structureSubType.getDeclaredMethod("getFieldOrder", new Class[]{});
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("The Structure sub type: " + structureSubType.getName()
                    + " \nmust define the method: getFieldOrder()."
                    + " \nSee the javadoc for Structure.getFieldOrder() for details.", e);
        }


        if (Modifier.isAbstract(structureSubType.getModifiers())) {
            // do not try to construct abstract Structure sub types
            return;
        }
        final Constructor<? extends Structure> structConstructor;
        try {
            structConstructor = structureSubType.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            if (structureSubType == Structure.FFIType.class) {
                // ignore this case
                // @todo Allow user to pass in list of classes for which to skip construction?
                return;
            }
            throw new RuntimeException("Parameterless constructor failed on Structure sub type: " + structureSubType.getName());
        }

        if (!structConstructor.isAccessible()) {
            structConstructor.setAccessible(true);
        }
        final Structure structure;
        try {
            structure= structConstructor.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Could not instantiate Structure sub type: " + structureSubType.getName(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Could not instantiate Structure sub type: " + structureSubType.getName(), e);
        } catch (InvocationTargetException e) {
            // this is triggered by checks in Structure.getFields()
            throw new RuntimeException("Could not instantiate Structure sub type: " + structureSubType.getName(), e);
        }

        final List methodCallFieldList;
        try {
            methodCallFieldList = (List) methodGetFieldOrder.invoke(structure);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Could not invoke getFieldOrder() on Structure sub type: " + structureSubType.getName(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Could not invoke getFieldOrder() on Structure sub type: " + structureSubType.getName(), e);
        }

        final Field[] actualFields = structureSubType.getDeclaredFields();
        final List actualFieldNames = new ArrayList(actualFields.length);
        for (final Field field : actualFields) {
            final String actualFieldName = field.getName();
            if (!methodCallFieldList.contains(actualFieldName)) {
                throw new IllegalArgumentException(structureSubType.getName() + ".getFieldOrder() [" + methodCallFieldList
                        + "] does not include declared field: " + actualFieldName);
            }
            actualFieldNames.add(actualFieldName);
        }

        for (final Object methodCallField : methodCallFieldList) {
            if (!actualFieldNames.contains(methodCallField)) {
                throw new IllegalArgumentException(structureSubType.getName() + ".getFieldOrder() [" + methodCallFieldList
                        + "] includes undeclared field: " + methodCallField);
            }
        }
    }
}

@@ -19,48 +19,8 @@
 a;%0A%0A
-import com.google.common.collect.Lists;%0A
 impo
@@ -249,212 +249,59 @@
 er;%0A
-import org.reflections.util.FilterBuilder;%0A%0Aimport java.io.File;%0Aimport java.lang.reflect.*;%0Aimport java.net.MalformedURLException;%0Aimport java.net.URL;%0Aimport java.util.ArrayList;%0Aimport java.util.Linked
+%0Aimport java.lang.reflect.*;%0Aimport java.util.Array
 List

remove stale imports