package xhl.core;

import java.lang.annotation.*;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import xhl.core.elements.DataList;
import xhl.core.elements.Symbol;
import xhl.core.exceptions.EvaluationException;

/**
 * Base class for implementing new modules.
 *
 * Allows to implement module functions as Java methods marked with annotation
 * <code>&#64;Function</code>.
 *
 * @author Sergej Chodarev
 */
public abstract class GenericModule implements Module {

    protected Evaluator evaluator;

    @Override
    public SymbolTable getSymbols() {
        SymbolTable table = new SymbolTable();
        Method[] methods = this.getClass().getDeclaredMethods();
        for (Method method : methods) {
            tryAddFunction(table, method);
        }
        return table;
    }

    @Override
    public void setEvaluator(Evaluator evaluator) {
        this.evaluator = evaluator;
    }

    /**
     * If method has <code>&#64;Function</code> annotation, add it to symbols
     * table.
     *
     * @param table
     * @param method
     */
    private void tryAddFunction(SymbolTable table, Method method) {
        Function fann = method.getAnnotation(Function.class);
        if (fann != null) {
            // Find function name
            String name;
            if (fann.name().equals(""))
                name = method.getName();
            else
                name = fann.name();

            Executable exec = new GenericExecutable(method, fann.evaluateArgs());
            table.put(new Symbol(name), exec);
        }
    }

    /**
     * Indicates that method should be exported as module function.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Function {
        /** Function name. If not specified, method name is uses. */
        public String name() default "";

        public boolean evaluateArgs() default true;
    }

    private class GenericExecutable implements Executable {
        private final Method method;
        private final boolean evaluateArgs;

        public GenericExecutable(Method method, boolean evaluateArgs) {
            this.method = method;
            this.evaluateArgs = evaluateArgs;
        }

        @Override
        public Object exec(DataList args) throws EvaluationException {
            // Prepare arguments
            List<Object> evArgs = new ArrayList<Object>(args.size());
            for (Object arg : args) {
                if (evaluateArgs)
                    evArgs.add(evaluator.eval(arg));
                else
                    evArgs.add(arg);
            }
            if (method.isVarArgs())
                packVarArgs(evArgs);

            try {
                return method.invoke(GenericModule.this, evArgs.toArray());
            } catch (Exception e) {
                throw new EvaluationException();
            }
        }

        /**
         * Pack arguments to array for method with variable number of arguments.
         *
         * @param evArgs
         */
        private void packVarArgs(List<Object> evArgs) {
            Class<?>[] params = method.getParameterTypes();
            int last = params.length - 1; // index of varargs parameter
            Class<?> type = params[last].getComponentType(); // it's type

            Object[] varArgs = evArgs.subList(last, evArgs.size()).toArray();

            // New array for varargs, properly typed
            Object[] varArgsT = (Object[]) Array.newInstance(type,
                    varArgs.length);
            System.arraycopy(varArgs, 0, varArgsT, 0, varArgs.length);

            // Pack varargs
            evArgs.set(last, varArgsT);
            evArgs.subList(last + 1, evArgs.size()).clear();
        }
    }
}

@@ -568,107 +568,542 @@
 or;%0A
-%0A    @Override%0A    public SymbolTable getSymbols() %7B%0A        SymbolTable table = new SymbolTable();
+    private final SymbolTable table = new SymbolTable();%0A%0A    public GenericModule() %7B%0A        findFunctions();%0A    %7D%0A%0A    @Override%0A    public SymbolTable getSymbols() %7B%0A        return table;%0A    %7D%0A%0A    @Override%0A    public void setEvaluator(Evaluator evaluator) %7B%0A        this.evaluator = evaluator;%0A    %7D%0A%0A    /** Export symbol from module%0A     *%0A     * @param symbol%0A     * @param value%0A     */%0A    protected void addSymbol(Symbol symbol, Object value) %7B%0A        table.put(symbol, value);%0A    %7D%0A%0A    private void findFunctions() %7B
 %0A   
@@ -1261,139 +1261,8 @@
   %7D%0A
-        return table;%0A    %7D%0A%0A    @Override%0A    public void setEvaluator(Evaluator evaluator) %7B%0A        this.evaluator = evaluator;%0A
     
@@ -2079,21 +2079,24 @@
 D)%0A    p
-ublic
+rotected
  @interf

Allow to export additional symbols from GenericModule subclass