org.ow2.mind.unit.Launcher.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.mind.unit.Launcher.java

Source

/**
 * Copyright (C) 2013 Schneider-Electric
 *
 * This file is part of "Mind Compiler" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public License 
 * as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: mind@ow2.org
 *
 * Authors: Stephane Seyvoz
 * Contributors: 
 */

package org.ow2.mind.unit;

import static org.ow2.mind.adl.membrane.ControllerInterfaceDecorationHelper.setReferencedInterface;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.filefilter.FileFilterUtils;
import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.CompilerError;
import org.objectweb.fractal.adl.Definition;
import org.objectweb.fractal.adl.Loader;
import org.objectweb.fractal.adl.Node;
import org.objectweb.fractal.adl.NodeFactory;
import org.objectweb.fractal.adl.error.Error;
import org.objectweb.fractal.adl.error.GenericErrors;
import org.objectweb.fractal.adl.interfaces.Interface;
import org.objectweb.fractal.adl.interfaces.InterfaceContainer;
import org.objectweb.fractal.adl.merger.MergeException;
import org.objectweb.fractal.adl.merger.NodeMerger;
import org.objectweb.fractal.adl.types.TypeInterface;
import org.objectweb.fractal.adl.types.TypeInterfaceUtil;
import org.objectweb.fractal.adl.util.FractalADLLogManager;
import org.ow2.mind.ADLCompiler;
import org.ow2.mind.ADLCompiler.CompilationStage;
import org.ow2.mind.ForceRegenContextHelper;
import org.ow2.mind.adl.annotation.predefined.Singleton;
import org.ow2.mind.adl.ast.ASTHelper;
import org.ow2.mind.adl.ast.Binding;
import org.ow2.mind.adl.ast.BindingContainer;
import org.ow2.mind.adl.ast.Component;
import org.ow2.mind.adl.ast.ComponentContainer;
import org.ow2.mind.adl.ast.DefinitionReference;
import org.ow2.mind.adl.ast.ImplementationContainer;
import org.ow2.mind.adl.ast.MindInterface;
import org.ow2.mind.adl.ast.Source;
import org.ow2.mind.adl.idl.InterfaceDefinitionDecorationHelper;
import org.ow2.mind.adl.membrane.ControllerInterfaceDecorationHelper;
import org.ow2.mind.adl.membrane.ast.Controller;
import org.ow2.mind.adl.membrane.ast.ControllerContainer;
import org.ow2.mind.adl.membrane.ast.ControllerInterface;
import org.ow2.mind.adl.membrane.ast.InternalInterfaceContainer;
import org.ow2.mind.adl.membrane.ast.MembraneASTHelper;
import org.ow2.mind.annotation.AnnotationHelper;
import org.ow2.mind.cli.CmdFlag;
import org.ow2.mind.cli.CmdOption;
import org.ow2.mind.cli.CmdOptionBooleanEvaluator;
import org.ow2.mind.cli.CommandLine;
import org.ow2.mind.cli.CommandLineOptionExtensionHelper;
import org.ow2.mind.cli.CommandOptionHandler;
import org.ow2.mind.cli.InvalidCommandLineException;
import org.ow2.mind.cli.Options;
import org.ow2.mind.cli.OutPathOptionHandler;
import org.ow2.mind.cli.PrintStackTraceOptionHandler;
import org.ow2.mind.cli.SrcPathOptionHandler;
import org.ow2.mind.error.ErrorManager;
import org.ow2.mind.idl.IDLLoader;
import org.ow2.mind.idl.ast.IDL;
import org.ow2.mind.idl.ast.InterfaceDefinition;
import org.ow2.mind.idl.ast.Method;
import org.ow2.mind.idl.ast.Parameter;
import org.ow2.mind.idl.ast.PrimitiveType;
import org.ow2.mind.idl.ast.Type;
import org.ow2.mind.inject.GuiceModuleExtensionHelper;
import org.ow2.mind.io.OutputFileLocator;
import org.ow2.mind.plugin.PluginLoaderModule;
import org.ow2.mind.plugin.PluginManager;
import org.ow2.mind.unit.annotations.Cleanup;
import org.ow2.mind.unit.annotations.Init;
import org.ow2.mind.unit.annotations.Test;
import org.ow2.mind.unit.annotations.TestSuite;
import org.ow2.mind.unit.cli.CUnitModeOptionHandler;
import org.ow2.mind.unit.model.Suite;
import org.ow2.mind.unit.model.TestCase;
import org.ow2.mind.unit.model.TestInfo;
import org.ow2.mind.unit.st.BasicSuiteSourceGenerator;
import org.ow2.mind.unit.st.SuiteSourceGenerator;

import com.google.inject.Guice;
import com.google.inject.Injector;
//import org.ow2.mind.adl.annotations.DumpASTAnnotationProcessor;

public class Launcher {

    protected static final String PROGRAM_NAME_PROPERTY_NAME = "mindunit.launcher.name";
    protected static final String ID_PREFIX = "org.ow2.mind.unit.test.";

    protected final CmdFlag helpOpt = new CmdFlag(ID_PREFIX + "Help", "h", "help", "Print this help and exit");

    protected final CmdFlag versionOpt = new CmdFlag(ID_PREFIX + "Version", "v", "version",
            "Print version number and exit");

    protected final CmdFlag extensionPointsListOpt = new CmdFlag(ID_PREFIX + "PrintExtensionPoints", null,
            "extension-points", "Print the list of available extension points and exit.");

    protected final Options options = new Options();

    protected static Logger logger = FractalADLLogManager.getLogger("mindunit");

    // Used for ADL files listing through directory exploration
    protected List<File> validTestFoldersList = new ArrayList<File>();
    // Used by source-path configuration, as it uses a URLClassLoader
    protected List<URL> urlList = new ArrayList<URL>();

    protected List<String> testFolderADLList = new ArrayList<String>();

    protected List<Definition> validTestSuitesDefsList = new ArrayList<Definition>();
    protected List<Suite> testSuites = new ArrayList<Suite>();

    protected Map<Object, Object> compilerContext = new HashMap<Object, Object>();

    protected final String adlName = "mindunit.MindUnitApplication";

    // compiler components :
    protected Injector injector;

    protected ErrorManager errorManager;
    protected ADLCompiler adlCompiler;

    protected NodeFactory nodeFactoryItf;
    protected SuiteSourceGenerator suiteCSrcGenerator;
    protected Loader loaderItf;
    protected IDLLoader idlLoaderItf;
    protected OutputFileLocator outputFileLocatorItf;
    protected NodeMerger nodeMergerItf;

    // default mode
    private CUnitMode cunit_mode = CUnitMode.AUTOMATED;

    // build configuration
    String exeName = null;
    String rootAdlName = null;

    protected void init(final String... args) throws InvalidCommandLineException {

        List<String> testFoldersList;

        if (logger.isLoggable(Level.CONFIG)) {
            for (final String arg : args) {
                logger.config("[arg] " + arg);
            }
        }

        /****** Initialization of the PluginManager Component *******/

        final Injector bootStrapPluginManagerInjector = getBootstrapInjector();
        final PluginManager pluginManager = bootStrapPluginManagerInjector.getInstance(PluginManager.class);

        addOptions(pluginManager);

        // parse arguments to a CommandLine.
        final CommandLine cmdLine = CommandLine.parseArgs(options, false, args);
        checkExclusiveGroups(pluginManager, cmdLine);
        compilerContext.put(CmdOptionBooleanEvaluator.CMD_LINE_CONTEXT_KEY, cmdLine);
        invokeOptionHandlers(pluginManager, cmdLine, compilerContext);

        // If help is asked, print it and exit.
        if (helpOpt.isPresent(cmdLine)) {
            printHelp(System.out);
            System.exit(0);
        }

        // If version is asked, print it and exit.
        if (versionOpt.isPresent(cmdLine)) {
            printVersion(System.out);
            System.exit(0);
        }

        // If the extension points list is asked, print it and exit.
        if (extensionPointsListOpt.isPresent(cmdLine)) {
            printExtensionPoints(pluginManager, System.out);
            System.exit(0);
        }

        // get the test folders list
        testFoldersList = cmdLine.getArguments();

        if (!testFoldersList.isEmpty()) {
            checkAndStoreValidTargetFolders(testFoldersList);
        } else {
            logger.severe("You must specify a target directory.");
            printHelp(System.out);
            System.exit(1);
        }

        // also add the URL of the generated files folder (for the mindUnitSuite.c file to be accessible)
        createAndAddGeneratedFilesFolderToPathURLs();

        // add the computed test folders to the src-path
        addTestFoldersToPath();

        // initialize compiler
        initInjector(pluginManager, compilerContext);

        initCompiler();

        /*
         *  We need to force regeneration (which leads to lose incremental compilation advantages... :( )
         *  In order for:
         *  - The Container not to be reloaded with already existing @TestSuite-s, leading to duplicates
         *  (we don't check for consistency yet)
         *  - The @TestSuites not to be reloaded with already added exported test interfaces (with their internal
         *  dual interface, membrane code with stubs when needed, and internal binding...)
         *  (as previously we want to avoid duplicates since we only ADD elements (no diff/remove), and our check
         *  for no outer interface on @TestSuite-s would fail)
         *  - The MindUnitSuiteDefinition has to be regenerated according to @TestSuite-s and @Test-s,
         *  as the C implementation and client interfaces may change according to what we find
         *  - The Container also has already been completed with according bindings at the previous compile time
         *  and we again do not check for consistency
         *  
         *  TODO: consider incremental compilation solutions ? What we only want to reload every time are 
         *  the components containing the user content and not the user files (only when there is change there...).
         *  How to discriminate such behavior ?
         */
        ForceRegenContextHelper.setForceRegen(compilerContext, true);

    }

    private void constructTestApplication() {

        // Build list of ADL files
        listTestFolderADLs();

        if (testFolderADLList.isEmpty()) {
            logger.info("No .adl file found: exit");
            System.exit(0);
        }

        /*
         * Then parse/load them (but stay at CHECK_ADL stage).
         * We obtain a list of Definition-s
         */
        filterValidTestSuites();

        if (validTestSuitesDefsList.isEmpty()) {
            logger.info("No @TestSuite found: exit");
            System.exit(0);
        }

        /*
         * Get the test container 
         */
        String testContainerName = "mindunit.MindUnitContainer";

        Definition containerDef = getContainerFromName(testContainerName);

        /*
         * Add all test cases to the test container as sub-component instances
         */
        logger.info("Adding TestSuites to the MindUnit container");

        // create a component instance for each @TestSuite definition and add it to the container
        addTestSuitesToContainer(containerDef);

        /*
         * Load the application
         */
        logger.info("Preparing the host application");

        configureApplicationTemplateFromCUnitMode();

        /*
         * Write the C implementation of the "Suite" component with the good
         * struct referencing all the TestEntries ("run" method) previously found.
         * Needs to write an according StringTemplate. If we made all TestCases as
         * @Singleton, we could reference CALLs (no "this") and create according bindings ?
         * A bit heavy but more architecture-friendly. And may simplify function names calculations.
         */
        logger.info(
                "@TestSuites introspection - Find @Tests - Export test interfaces - Create internal bindings in the @TestSuites");

        // prepare to store information needed to create client interfaces on our Suite component
        Map<String, String> suiteClientItfsNameSignature = new LinkedHashMap<String, String>();

        // prepare to store bindings to be created from suite to @TestSuite-s interfaces in the container
        // note: only targets will be initialized and the source component name will be configured at addBinding time
        List<Binding> containerBindings = new ArrayList<Binding>();

        // build the Suite list
        for (Definition currDef : validTestSuitesDefsList)
            // note: at each iteration the lists are growing
            // note2: the suite definition will be modified to export interfaces containing @Test-s
            introspectAndPrepareTestSuite(currDef, suiteClientItfsNameSignature, containerBindings);

        logger.info("Generating Suite source implementation");

        try {
            // injected (see Module and initInjector)
            suiteCSrcGenerator.visit(testSuites, compilerContext);
        } catch (ADLException e1) {
            logger.severe("Error while generating the C source implementation of the Suite ! Cause:");
            e1.printStackTrace();
        }

        logger.info("Creating the Suite component");

        /*
         * Create a primitive with the source file as implementation
         */
        Definition mindUnitSuiteDefinition = createSuiteDefinition();

        logger.info("Adding unit-gen/mindUnitSuite.c as source");

        addCSourceToDefinition(mindUnitSuiteDefinition, BasicSuiteSourceGenerator.getSuiteFileName());

        /*
         * Create the good client interfaces matching the test ones (need to store the latter !)
         * And create bindings from the suite client interfaces to the @TestSuite-s
         */

        logger.info("Creating client interfaces on the Suite component");

        instantiateNeededClientInterfaces(mindUnitSuiteDefinition, suiteClientItfsNameSignature);

        logger.info("Adding the Suite to the test container");

        /*
         * Then create a "Suite" component instance and add it to the test container
         */

        String mindUnitSuiteInstanceName = addSuiteToContainer(containerDef, mindUnitSuiteDefinition);

        logger.info(
                "Creating bindings in the container, from generated Suite component interfaces to @TestSuite-s exported test interfaces");

        configureAndAddBindingsFromSuiteToTestSuitesInContainer(containerDef, containerBindings,
                mindUnitSuiteInstanceName);

        // Debug
        //DumpASTAnnotationProcessor.showDefinitionContent(containerDef);

        /*
         * Then configure target exe name
         */
        String simpleOutput = "mindUnitOutput";
        switch (cunit_mode) {
        case CONSOLE:
            exeName = "console_" + simpleOutput;
            break;
        case GCOV:
            exeName = "gcov_" + simpleOutput;
            break;
        default:
        case AUTOMATED:
            exeName = "automated_" + simpleOutput;
            break;
        }

        // The "compile" method is naturally following in the main when we return...
    }

    private void configureAndAddBindingsFromSuiteToTestSuitesInContainer(Definition containerDef,
            List<Binding> containerBindings, String mindUnitSuiteInstanceName) {

        assert containerDef instanceof BindingContainer;

        BindingContainer containerDefAsBdgCtr = (BindingContainer) containerDef;
        for (Binding currBinding : containerBindings) {
            currBinding.setFromComponent(mindUnitSuiteInstanceName);
            containerDefAsBdgCtr.addBinding(currBinding);
        }

    }

    private String addSuiteToContainer(Definition containerDef, Definition mindUnitSuiteDefinition) {

        DefinitionReference mindUnitSuiteDefRef = ASTHelper.newDefinitionReference(nodeFactoryItf,
                mindUnitSuiteDefinition.getName());
        ASTHelper.setResolvedDefinition(mindUnitSuiteDefRef, mindUnitSuiteDefinition);
        String mindUnitSuiteInstanceName = mindUnitSuiteDefRef.getName().replace(".", "_") + "Instance";
        Component mindUnitSuiteComp = ASTHelper.newComponent(nodeFactoryItf, mindUnitSuiteInstanceName,
                mindUnitSuiteDefRef);
        mindUnitSuiteComp.setDefinitionReference(mindUnitSuiteDefRef);
        ASTHelper.setResolvedComponentDefinition(mindUnitSuiteComp, mindUnitSuiteDefinition);
        ((ComponentContainer) containerDef).addComponent(mindUnitSuiteComp);

        return mindUnitSuiteInstanceName;
    }

    /**
     * For each pair of "interface name - signature" create a client interface instance on the Suite.
     * @param mindUnitSuiteDefinition
     * @param suiteClientItfsNameSignature
     */
    private void instantiateNeededClientInterfaces(Definition mindUnitSuiteDefinition,
            Map<String, String> suiteClientItfsNameSignature) {

        // the newPrimitiveDefinitionNode enforces ImplementationContainer.class compatibility
        InterfaceContainer mindUnitSuiteDefAsItfCtr = (InterfaceContainer) mindUnitSuiteDefinition;
        for (String currItfInstanceName : suiteClientItfsNameSignature.keySet()) {
            String currItfSignature = suiteClientItfsNameSignature.get(currItfInstanceName);

            // we get the InterfaceDefinition from the compiler's cache (instead of creating a new map...)
            IDL currIDL = null;
            try {
                currIDL = idlLoaderItf.load(currItfSignature, compilerContext);
            } catch (ADLException e) {
                logger.severe("Could not load " + currItfSignature
                        + " interface ! - Exit to prevent C suite file inconsistency !");
                System.exit(1);
            }

            MindInterface newSuiteCltItf = ASTHelper.newClientInterfaceNode(nodeFactoryItf, currItfInstanceName,
                    currItfSignature);

            assert currIDL instanceof InterfaceDefinition;
            InterfaceDefinitionDecorationHelper.setResolvedInterfaceDefinition(newSuiteCltItf,
                    (InterfaceDefinition) currIDL);

            mindUnitSuiteDefAsItfCtr.addInterface(newSuiteCltItf);
        }

    }

    /**
     * Create a Source node with path pointing to the generated C file and add it to the Suite definition.
     * @param mindUnitSuiteDefinition The Suite definition.
     * @param cSuiteFileName The name of the C file implementing the Suite.
     */
    private void addCSourceToDefinition(Definition mindUnitSuiteDefinition, String cSuiteFileName) {

        ImplementationContainer mindUnitSuiteDefAsImplCtr = (ImplementationContainer) mindUnitSuiteDefinition;
        Source mindUnitSuiteSource = ASTHelper.newSource(nodeFactoryItf);
        mindUnitSuiteSource.setPath(cSuiteFileName);
        mindUnitSuiteDefAsImplCtr.addSource(mindUnitSuiteSource);
    }

    /**
     * Create a @Singleton primitive definition: "MindUnitSuiteDefinition" 
     * @return The new (empty) definition
     */
    private Definition createSuiteDefinition() {

        Definition mindUnitSuiteDefinition = ASTHelper.newPrimitiveDefinitionNode(nodeFactoryItf,
                "MindUnitSuiteDefinition", (DefinitionReference[]) null);
        // it has to be a @Singleton for our test "void func(void) { CALL(itf, meth)(); }" functions to be able to enter the Mind world (no 'mind_this')
        // both following methods are needed since we won't trigger the Singleton Annotation Processor, and the struct definitions rely on both
        // ASTHelper.isSingleton doesn't check the "singleton decoration" by the way but the Annotation and is used to name singleton instances.
        ASTHelper.setSingletonDecoration(mindUnitSuiteDefinition);
        try {
            AnnotationHelper.addAnnotation(mindUnitSuiteDefinition, new Singleton());
        } catch (ADLException e1) {
            // will never happen since the exception is raised only when you try to put an annotation two times on a definition... which is not our case
        }
        // the newPrimitiveDefinitionNode enforces ImplementationContainer.class compatibility

        return mindUnitSuiteDefinition;
    }

    /**
     * This method introspects the @TestSuite definition to find @Tests and edit the definition to export them.
     * As an optimization we also prepare content for later processing.
     * 
     * @param suiteDef The @TestSuite definition containing @Test-s. The definition will be modified to export interfaces containing @Tests and internal bindings to these interfaces will be created.
     * @param suiteClientItfsNameSignature A list to keep track of the exported interfaces so as to create matching client interfaces on the @Suite component.
     * @param containerBindings A return parameter in which are added pre-filled bindings (as exported interfaces are found here) to be completed and added to the container later.
     */
    private void introspectAndPrepareTestSuite(Definition suiteDef,
            Map<String, String> suiteClientItfsNameSignature, List<Binding> containerBindings) {

        String description = (AnnotationHelper.getAnnotation(suiteDef, TestSuite.class)).value;
        if (description == null)
            description = suiteDef.getName();

        if (testSuiteHasClientInterface(suiteDef))
            return;

        if (suiteDef instanceof ComponentContainer) {
            // handle all sub-components (no recursion)
            ComponentContainer currCompContainer = (ComponentContainer) suiteDef;
            for (Component currComp : currCompContainer.getComponents()) {
                Definition currCompDef = null;
                try {
                    currCompDef = ASTHelper.getResolvedComponentDefinition(currComp, loaderItf, compilerContext);
                } catch (ADLException e) {
                    logger.severe("Could not resolve definition of " + suiteDef.getName() + "." + currComp.getName()
                            + " ! - skip");
                    continue;
                }
                if (currCompDef instanceof InterfaceContainer) {
                    // handle sub-component server interfaces, find the list of @Test-annotated methods
                    InterfaceContainer currItfContainer = (InterfaceContainer) currCompDef;

                    for (Interface currItf : currItfContainer.getInterfaces()) {
                        // according to test interfaces we will not only prepare the current TestSuite where needed
                        // with export interfaces, internal bindings, but also prepare the list of parent container bindings
                        // and matching client interfaces info for them to be created on the client Suite
                        Suite currSuite = introspectInterfaceAndPrepareTestSuite(suiteDef, description, currComp,
                                currItf, suiteClientItfsNameSignature, containerBindings);
                        if (currSuite != null)
                            testSuites.add(currSuite);
                    }
                }

            }
        }
    }

    /**
     * Check if the @TestSuite component has external interfaces. Must have none.
     * @param currDef The TestSuite to be checked.
     * @return true on error
     */
    private boolean testSuiteHasClientInterface(Definition currDef) {

        assert currDef instanceof InterfaceContainer;
        InterfaceContainer currDefAsItfCtr = (InterfaceContainer) currDef;

        for (Interface currItf : currDefAsItfCtr.getInterfaces()) {
            if (((TypeInterface) currItf).getRole().equals(TypeInterface.CLIENT_ROLE)) {
                logger.warning("While handling @TestSuite + " + currDef.getName()
                        + ": A test suite must not have any client interface !! - Skipping TestSuite !");
                return true;
            }
        }

        return false;
    }

    private Suite introspectInterfaceAndPrepareTestSuite(Definition suiteDef, String suiteDescription,
            Component currComp, Interface currItf, Map<String, String> suiteClientItfsNameSignature,
            List<Binding> containerBindings) {

        assert suiteDef instanceof BindingContainer; // should be as a default anyway
        BindingContainer currDefAsBdgCtr = (BindingContainer) suiteDef;
        assert suiteDef instanceof InterfaceContainer; // should be as a default anyway
        InterfaceContainer currDefAsItfCtr = (InterfaceContainer) suiteDef;

        // as we will want to export interfaces and create internal bindings, we need
        // to create the dual internal interface and have the definition as an InternalInterfaceContainer
        // inspired from the CompositeInternalInterfaceLoader
        turnToInternalInterfaceContainer(suiteDef);
        InternalInterfaceContainer currDefAsInternalItfCtr = (InternalInterfaceContainer) suiteDef;

        turnToControllerContainer(suiteDef);
        ControllerContainer currDefAsCtrlCtr = (ControllerContainer) suiteDef;

        // return
        Suite suite = null;

        // useful vars
        String currItfInitFuncName = null;
        String currItfInitMethName = null;
        String currItfCleanupFuncName = null;
        String currItfCleanupMethName = null;
        String itfSignature = null;
        String itfExportName = null;
        InterfaceDefinition currItfDef = null;

        String itfName = currItf.getName();

        // prepare test cases list - each TestCase is the same as a CU_TestInfo row
        List<TestCase> currItfValidTestCases = new ArrayList<TestCase>();

        // should be everywhere isn't it ?
        assert currItf instanceof TypeInterface;
        TypeInterface currTypeItf = (TypeInterface) currItf;

        // we only are concerned by server interfaces
        if (!currTypeItf.getRole().equals(TypeInterface.SERVER_ROLE))
            return suite;

        boolean hasTests = false;
        itfSignature = currTypeItf.getSignature();

        // we need interface details
        IDL currIDL;
        try {
            currIDL = idlLoaderItf.load(itfSignature, compilerContext);
        } catch (ADLException e) {
            logger.warning("Could not load interface definition " + itfSignature + " - skipping");
            return suite;
        }

        assert currIDL instanceof InterfaceDefinition;
        currItfDef = (InterfaceDefinition) currIDL;

        // handle methods in the interface to find existing @Test, @Init, @Cleanup
        for (Method currMethod : currItfDef.getMethods()) {

            boolean isTest = AnnotationHelper.getAnnotation(currMethod, Test.class) != null;
            boolean isInit = AnnotationHelper.getAnnotation(currMethod, Init.class) != null;
            boolean isCleanup = AnnotationHelper.getAnnotation(currMethod, Cleanup.class) != null;

            // Maybe replace the algorithm for a switch-case ?

            // ^ = XOR
            if (isTest ^ isInit ^ isCleanup) {

                if (isTest) {

                    Test testCase = AnnotationHelper.getAnnotation(currMethod, Test.class);

                    String testDescription = null;
                    if (testCase.value == null)
                        testDescription = currMethod.getName();
                    else
                        testDescription = testCase.value;

                    // return type should be void
                    Type methodType = currMethod.getType();
                    if (!(methodType instanceof PrimitiveType
                            && ((PrimitiveType) methodType).getName().equals("void"))) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Test method return type should be \"void\" - Adding to test list anyway");
                    }

                    // argument must be void ( = no argument)
                    Parameter[] methodParams = currMethod.getParameters();
                    if (methodParams.length > 0) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Test method arguments must be \"(void)\" - Skipping method");
                        continue;
                    }

                    // compute the relay function name we'll provide to CUnit that will CALL the test
                    // we compute complex names to avoid clashes (as a tester is not needed to be @Singleton)
                    String cRelayFuncName = "__cunit_relay_" // prefix
                            + suiteDef.getName().substring(suiteDef.getName().lastIndexOf(".") + 1).replace(".",
                                    "_") // the @TestSuite simple definition name
                            + "_" + currComp.getName() // sub-component instance
                            + "_" + currTypeItf.getName() // interface instance
                            + "_" + currMethod.getName(); // method instance

                    // remember which interfaces should be instantiated as clients on the Suite component
                    itfExportName = suiteDef.getName().replace(".", "_") + "_" + currComp.getName() + "_"
                            + currTypeItf.getName();
                    suiteClientItfsNameSignature.put(itfExportName, itfSignature);

                    // create the test case
                    currItfValidTestCases.add(new TestCase(testDescription, cRelayFuncName, currMethod.getName()));

                    // need to know if we have to export the interface to the surrounding @TestSuite composite
                    hasTests = true;

                } else if (isInit) {
                    if (currItfInitFuncName != null) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": An @Init method was already defined - Skipping");
                        continue;
                    }

                    // compute the relay function name we'll provide to CUnit that will CALL the test
                    // we compute complex names to avoid clashes (as a tester is not needed to be @Singleton)
                    currItfInitFuncName = "__cunit_relay_" // prefix
                            + suiteDef.getName().substring(suiteDef.getName().lastIndexOf(".") + 1).replace(".",
                                    "_") // the @TestSuite definition name
                            + "_" + currComp.getName() // sub-component instance
                            + "_" + currTypeItf.getName() // interface instance
                            + "_" + currMethod.getName(); // method instance

                    // simple name for the CALL
                    currItfInitMethName = currMethod.getName();

                    // return type should be int (according to CUnit)
                    Type methodType = currMethod.getType();
                    if (!(methodType instanceof PrimitiveType
                            && ((PrimitiveType) methodType).getName().equals("int"))) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Init method return type should be \"int\" - Adding to test list anyway");
                    }

                    // argument must be void ( = no argument)
                    Parameter[] methodParams = currMethod.getParameters();
                    if (methodParams.length > 0) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Init method arguments must be \"(void)\" - Skipping method");
                        continue;
                    }

                } else if (isCleanup) {
                    if (currItfCleanupFuncName != null) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": An @Cleanup method was already defined - Skipping");
                        continue;
                    }
                    // compute the relay function name we'll provide to CUnit that will CALL the test
                    // we compute complex names to avoid clashes (as a tester is not needed to be @Singleton)
                    currItfCleanupFuncName = "__cunit_relay_" // prefix
                            + suiteDef.getName().substring(suiteDef.getName().lastIndexOf(".") + 1).replace(".",
                                    "_") // the @TestSuite definition name
                            + "_" + currComp.getName() // sub-component instance
                            + "_" + currTypeItf.getName() // interface instance
                            + "_" + currMethod.getName(); // method instance

                    // simple name for the CALL
                    currItfCleanupMethName = currMethod.getName();

                    // return type should be int (according to CUnit)
                    Type methodType = currMethod.getType();
                    if (!(methodType instanceof PrimitiveType
                            && ((PrimitiveType) methodType).getName().equals("int"))) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Cleanup method return type should be \"int\" - Adding to test list anyway");
                    }

                    // argument must be void ( = no argument)
                    Parameter[] methodParams = currMethod.getParameters();
                    if (methodParams.length > 0) {
                        logger.warning("While handling " + currItfDef.getName() + "#" + currMethod.getName()
                                + ": @Cleanup method arguments must be \"(void)\" - Skipping method");
                        continue;
                    }
                }
            } else if (isTest || isInit || isCleanup) {
                // if the XOR failed and there was at least one annotation it means we had 2 or more... 
                // and we didn't want to raise an error when there was no annotation at all
                logger.warning("@Init, @Test and @Cleanup are mutually exclusive - Please clarify "
                        + currItfDef.getName() + "#" + currMethod.getName() + " role - Skipping method");
                continue;
            }
        } // end for all methods

        /*
         * Export interface containing @Test to the surrounding @TestSuite
         * And create the matching internal binding
         * And prepare the outer binding (in the container) from the generated Suite component to the current @TestSuite
         */
        if (hasTests) {
            MindInterface newSuiteServerItf = ASTHelper.newServerInterfaceNode(nodeFactoryItf, itfExportName,
                    itfSignature);
            InterfaceDefinitionDecorationHelper.setResolvedInterfaceDefinition(newSuiteServerItf, currItfDef);

            //logger.info("Creating " + newSuiteServerItf.getName() + " interface instance for definition " + currDef.getName() + " with interface definition " + currItfDef.getName());
            currDefAsItfCtr.addInterface(newSuiteServerItf);

            // also create the INTERNAL interface
            // inspired by the CompositeInternalInterfaceLoader
            TypeInterface newSuiteServerInternalClientItf = getInternalInterface(newSuiteServerItf);
            InterfaceDefinitionDecorationHelper
                    .setResolvedInterfaceDefinition((TypeInterface) newSuiteServerInternalClientItf, currItfDef);
            currDefAsInternalItfCtr.addInternalInterface(newSuiteServerInternalClientItf);

            //-- the following is for the @TestSuite membrane (_ctrl_impl.c) to have
            // it's "interface delegator" generated
            ControllerInterfaceDecorationHelper.setDelegatedInterface(newSuiteServerInternalClientItf,
                    newSuiteServerItf);
            ControllerInterfaceDecorationHelper.setDelegatedInterface(newSuiteServerItf,
                    newSuiteServerInternalClientItf);

            // add controller
            Controller ctrl = newControllerNode();
            ControllerInterface externalCtrlItf = newControllerInterfaceNode(newSuiteServerItf.getName(), false);
            ControllerInterface internalCtrlItf = newControllerInterfaceNode(newSuiteServerItf.getName(), true);
            ctrl.addControllerInterface(externalCtrlItf);
            ctrl.addControllerInterface(internalCtrlItf);
            ctrl.addSource(newSourceNode("InterfaceDelegator"));
            currDefAsCtrlCtr.addController(ctrl);

            setReferencedInterface(externalCtrlItf, newSuiteServerItf);

            // -- create internal binding
            Binding newInternalBinding = newInternalDelegationBinding(itfExportName, currComp, itfName);

            // add it to the @TestSuite composite definition
            currDefAsBdgCtr.addBinding(newInternalBinding);
            //logger.info("Created binding from the surrounding @TestSuite to the sub-component interface");

            // -- create outer binding
            Binding newOuterBinding = newOuterBinding(itfExportName, suiteDef);

            containerBindings.add(newOuterBinding);
        }

        // build the test suite
        if (!currItfValidTestCases.isEmpty()) {
            String structName = "_cu_ti_"
                    + suiteDef.getName().substring(suiteDef.getName().lastIndexOf(".") + 1).replace(".", "_") // the @TestSuite definition name
                    + "_" + currComp.getName() // sub-component instance
                    + "_" + currTypeItf.getName();

            TestInfo currTestInfo = new TestInfo(structName, currItfValidTestCases);

            // we want to be able to discriminate Mind Suites where multiple tester component instances provide the same interface
            String fullSuiteDescription = suiteDescription // @TestSuite description
                    + " - " + currComp.getName() // sub-comp
                    + " - " + currTypeItf.getName(); // itf

            suite = new Suite(fullSuiteDescription, currItfInitFuncName, currItfInitMethName,
                    currItfCleanupFuncName, currItfCleanupMethName, currTestInfo, itfExportName);
        }

        return suite;

    }

    /**
     * Create and initialize an internal delegation binding for our test interface, like:
     * "binds this.exportItf to tester.itf" where "this" is the current TestSuite. 
     * @param itfExportName The exported interface instance name - A detailed name to keep debug easy.
     * @param currComp The target tester sub-component.
     * @param itfName The target test interface.
     * @return A new internal delegation binding.
     */
    private Binding newInternalDelegationBinding(String itfExportName, Component currComp, String itfName) {

        Binding newInternalBinding = ASTHelper.newBinding(nodeFactoryItf);
        newInternalBinding.setFromComponent(Binding.THIS_COMPONENT);
        newInternalBinding.setFromInterface(itfExportName);
        // TODO: Support collections ? // setFromInterfaceNumber
        newInternalBinding.setToComponent(currComp.getName());
        newInternalBinding.setToInterface(itfName);
        // TODO: Support collections ?

        return newInternalBinding;
    }

    /**
     * Create and initialize a binding for which we know the destination (the currently
     * exported interface) and its source (that will use the same name) but wish
     * to configure the source later.
     * @param itfExportName The source and destination interface instance name.
     * @param currDef The destination TestSuite definition (we'll compute it's instance name using name convention).
     * @return A new binding instance to be exported to the test container.
     */
    private Binding newOuterBinding(String itfExportName, Definition currDef) {

        Binding newOuterBinding = ASTHelper.newBinding(nodeFactoryItf);

        // no "setFromComponent" since it will be completed later with the Suite instance name
        // itf name won't change though
        newOuterBinding.setFromInterface(itfExportName);
        // TODO: Support collections ? // setFromInterfaceNumber
        // target instance name is convention-based (see addComponents to the container way before in this code)
        newOuterBinding.setToComponent(currDef.getName().replace(".", "_") + "Instance");
        // same in client and server
        newOuterBinding.setToInterface(itfExportName);
        // TODO: Support collections ? // setFromInterfaceNumber

        return newOuterBinding;
    }

    /**
     * According to command-line configured --cunit-mode, load the
     * application templated with Console or Automated sub-component.
     * @return The whole application definition to be compiled in the end.
     */
    private void configureApplicationTemplateFromCUnitMode() {
        //List<Object> loadedDefs = null;
        rootAdlName = adlName + "<";

        String cunitModeUserInput = (String) compilerContext.get(CUnitModeOptionHandler.CUNITMODE_CONTEXT_KEY);
        if (cunitModeUserInput.equals("console")) {
            cunit_mode = CUnitMode.CONSOLE;
            logger.info("Loading container in Console mode");
            rootAdlName += "mindunit.MindUnitConsole";
        } else {
            if (cunitModeUserInput.equals("gcov")) {
                cunit_mode = CUnitMode.GCOV;
                logger.info("Loading container in GCov Automated mode");
            } else {
                cunit_mode = CUnitMode.AUTOMATED;
                logger.info("Loading container in basic Automated mode");
            }

            rootAdlName += "mindunit.MindUnitAutomated";
        }

        rootAdlName += ">";

        /*
         * If we wanted to check the application container...
        try {
           loadedDefs = adlCompiler.compile(rootAdlName, "", CompilationStage.CHECK_ADL, compilerContext);
        } catch (ADLException e) {
           if (!errorManager.getErrors().contains(e.getError())) {
        // the error has not been logged in the error manager, print it.
        try {
           errorManager.logError(e.getError());
        } catch (final ADLException e2) {
           System.exit(1);
        }
           }
        } catch (InterruptedException e) {
           throw new CompilerError(GenericErrors.INTERNAL_ERROR, "Interrupted while executing compilation tasks");
        }
         */

    }

    /**
     * Instantiate each @TestSuite as a component instance and add the instance to the container.
     * @param containerDef The container definition to be filled.
     */
    private void addTestSuitesToContainer(Definition containerDef) {
        for (Definition currTestDef : validTestSuitesDefsList) {
            DefinitionReference currTestDefRef = ASTHelper.newDefinitionReference(nodeFactoryItf,
                    currTestDef.getName());
            ASTHelper.setResolvedDefinition(currTestDefRef, currTestDef);

            // Instantiate a component of @TestSuite type
            Component currComp = ASTHelper.newComponent(nodeFactoryItf,
                    currTestDef.getName().replace(".", "_") + "Instance", currTestDefRef);
            currComp.setDefinitionReference(currTestDefRef);
            ASTHelper.setResolvedComponentDefinition(currComp, currTestDef);

            // Add the component to the container
            ((ComponentContainer) containerDef).addComponent(currComp);
        }
    }

    private Definition getContainerFromName(String testContainerName) {
        Definition containerDef = null;

        try {
            List<Object> containerDefs = adlCompiler.compile(testContainerName, "", CompilationStage.CHECK_ADL,
                    compilerContext);
            if (containerDefs == null) {
                logger.severe(
                        "Test container could not be loaded - Check availability of the MindUnit components in your runtime folder/source-path !");
                System.exit(1);
            }

            // should be size 1 and of course a definition... why would it be different ?
            if (containerDefs.size() > 1)
                logger.warning("Container loading returned multiple definitions - Using only the first one");

            Object containerObj = containerDefs.get(0);
            if (!(containerObj instanceof Definition)) {
                logger.severe("Container type wasn't a definition or could not be loaded");
                System.exit(1);
            }

            containerDef = (Definition) containerDefs.get(0);

            if (!ASTHelper.isComposite(containerDef)) {
                logger.severe("Container wasn't a composite ! Please be serious.");
                System.exit(1);
            }

        } catch (ADLException e) {
            if (!errorManager.getErrors().contains(e.getError())) {
                // the error has not been logged in the error manager, print it.
                try {
                    errorManager.logError(e.getError());
                } catch (final ADLException e2) {
                    System.exit(1);
                }
            }
        } catch (InterruptedException e) {
            throw new CompilerError(GenericErrors.INTERNAL_ERROR, "Interrupted while executing compilation tasks");
        }

        return containerDef;
    }

    /**
     * Filters the testFolderADLList of ADLs to keep only the @TestSuite-annotated ones.
     * Those TestSuite-s Definitions are added to the validTestSuitesDefsList list.
     */
    private void filterValidTestSuites() {
        logger.info("Loading definitions from the ADL files to find @TestSuites...");
        for (String currentADL : testFolderADLList) {
            List<Object> l;
            try {
                // Here the components may be reloaded from the incremental compilation cache 
                // if no modification happened
                l = adlCompiler.compile(currentADL, "", CompilationStage.CHECK_ADL, compilerContext);
                if (l != null && !l.isEmpty()) {
                    for (Object currObj : l) {
                        if (!(currObj instanceof Definition))
                            // error case that should never happen
                            logger.warning("Encountered object \"" + currObj.toString() + "\" while handling "
                                    + currentADL + " isn't a definition !");
                        else {
                            // we've got a definition
                            Definition currDef = (Definition) currObj;
                            // Then keep only if annotated with @TestSuite
                            if (AnnotationHelper.getAnnotation(currDef, TestSuite.class) != null) {
                                validTestSuitesDefsList.add(currDef);
                                logger.info("@TestSuite found: " + currDef.getName());
                            }
                        }
                    }
                } else {
                    logger.info(currentADL + " definition load failed, invalid ADL");
                    System.exit(1);
                }
            } catch (ADLException e) {
                logger.info(currentADL + " definition load failed, invalid ADL");
                if (!errorManager.getErrors().contains(e.getError())) {
                    // the error has not been logged in the error manager, print it.
                    try {
                        errorManager.logError(e.getError());
                    } catch (final ADLException e2) {
                        // ignore
                    }
                }
            } catch (InterruptedException e) {
                logger.info(
                        currentADL + " definition load failed, thread was interrupted ! detailed error below: ");
                e.printStackTrace();
            }
        }

    }

    /**
     * Create "unit-gen" folder in the user-defined output folder,
     * then add it to the URL list that will be added to the source-path,
     * for the generated files to be accessible to loading.
     */
    private void createAndAddGeneratedFilesFolderToPathURLs() {
        File outDir = OutPathOptionHandler.getOutPath(compilerContext);
        File genFilesDir = new File(outDir.getAbsolutePath(), "unit-gen");
        while (!genFilesDir.exists())
            genFilesDir.mkdirs();
        try {
            urlList.add(genFilesDir.toURI().toURL());
        } catch (MalformedURLException e2) {
            logger.severe("Could not access to " + outDir.getPath() + "/" + "unit-gen" + " file generation path !");
        }
    }

    /**
     * A method to validate the folders the user provided as command-line arguments.
     * The valid test folders are then stored in 2 fields:
     * - List<File> validTestFoldersList: Used for ADL files listing through directory exploration
     * - List<URL> urlList: Used by source-path configuration, as it uses a URLClassLoader
     * @param testFoldersList The user-defined target tests folders list.
     */
    private void checkAndStoreValidTargetFolders(List<String> testFoldersList) {
        for (String testFolder : testFoldersList) {
            final File testDirectory = new File(testFolder);
            if (!testDirectory.isDirectory() || !testDirectory.canRead()) {
                logger.severe(String.format("Cannot read source path '%s' - skipped.", testDirectory.getPath()));
            } else {
                validTestFoldersList.add(testDirectory);
                try {
                    URL testDirURL = testDirectory.toURI().toURL();
                    urlList.add(testDirURL);
                } catch (final MalformedURLException e) {
                    // will never happen since we already checked File
                }
            }
        }
    }

    /**
     * Inspired from org.ow2.mind.cli.SrcPathOptionHandler.
     * We extend the original ClassLoader by using it as a parent to a new ClassLoader.
     * Valid test folders are taken from this class urlList attribute.
     * We also extend the ClassLoader to the current jar in order to use local (hidden)
     * resources.
     */
    protected void addTestFoldersToPath() {
        // get the --src-path elements
        URLClassLoader srcPathClassLoader = (URLClassLoader) SrcPathOptionHandler
                .getSourceClassLoader(compilerContext);

        // URL array of test path, replace the original source class-loader with our enriched one
        // and use the original source class-loader as parent so as to keep everything intact
        ClassLoader srcAndTestPathClassLoader = new URLClassLoader(urlList.toArray(new URL[0]), srcPathClassLoader);

        // replace the original source classloader with the new one in the context
        compilerContext.remove("classloader");
        compilerContext.put("classloader", srcAndTestPathClassLoader);
    }

    /**
     * Inspired by Mindoc's DocumentationIndexGenerator.
     * @throws IOException
     */
    protected void listTestFolderADLs() {
        logger.info("Searching for .adl files in the target directories...");
        for (final File directory : validTestFoldersList) {
            try {
                exploreDirectory(directory.getCanonicalFile(), null);
            } catch (IOException e) {
                logger.severe(String.format("Cannot find directory '%s' - skipped.", directory.getPath()));
            }
        }
    }

    /**
     * Recursively find ADL files from the root directory.
     * Inspired by Mindoc's DocumentationIndexGenerator.
     * FileFilterUtils comes from Apache commons-io.
     * @throws IOException
     */
    private void exploreDirectory(final File directory, String currentPackage) {
        if (directory.isHidden())
            return;

        String subPackage = "";

        for (final File file : directory.listFiles((FileFilter) FileFilterUtils.suffixFileFilter(".adl"))) {
            String compName = file.getName();
            // remove ".adl" extension
            compName = compName.substring(0, compName.length() - 4);
            // add package
            compName = currentPackage + "." + compName;

            // save component info
            testFolderADLList.add(compName);
        }

        for (final File subDirectory : directory.listFiles(new FileFilter() {
            public boolean accept(final File pathname) {
                return pathname.isDirectory();
            }
        })) {

            // base folder
            if (currentPackage == null)
                subPackage = subDirectory.getName();
            else // already in sub-folder
                subPackage = currentPackage + "." + subDirectory.getName();

            // recursion
            exploreDirectory(subDirectory, subPackage);
        }
    }

    protected Injector getBootstrapInjector() {
        return Guice.createInjector(new PluginLoaderModule());
    }

    /**
     * Here we use the standard compiler initialization + A number of internals usually coming later.
     */
    protected void initCompiler() {
        errorManager = injector.getInstance(ErrorManager.class);
        adlCompiler = injector.getInstance(ADLCompiler.class);

        // Our additions
        nodeFactoryItf = injector.getInstance(NodeFactory.class);
        suiteCSrcGenerator = injector.getInstance(SuiteSourceGenerator.class);
        loaderItf = injector.getInstance(Loader.class);
        idlLoaderItf = injector.getInstance(IDLLoader.class);
        outputFileLocatorItf = injector.getInstance(OutputFileLocator.class);
        nodeMergerItf = injector.getInstance(NodeMerger.class);
    }

    protected void initInjector(final PluginManager pluginManager, final Map<Object, Object> compilerContext) {
        injector = Guice.createInjector(GuiceModuleExtensionHelper.getModules(pluginManager, compilerContext));
    }

    public List<Object> compile(final List<Error> errors, final List<Error> warnings)
            throws InvalidCommandLineException {

        logger.info("Launching executable compilation (executable name: " + exeName + ")");

        final List<Object> result = new ArrayList<Object>();
        try {
            /*final HashMap<Object, Object> contextMap = new HashMap<Object, Object>(
                  compilerContext);*/

            // Force compilation stage to be CompilationStage.COMPILE_EXE
            final List<Object> l = adlCompiler.compile(rootAdlName, exeName, CompilationStage.COMPILE_EXE,
                    compilerContext);

            if (l != null)
                result.addAll(l);

        } catch (final InterruptedException e1) {
            throw new CompilerError(GenericErrors.INTERNAL_ERROR, "Interrupted while executing compilation tasks");
        } catch (final ADLException e1) {
            if (!errorManager.getErrors().contains(e1.getError())) {
                // the error has not been logged in the error manager, print it.
                try {
                    errorManager.logError(e1.getError());
                } catch (final ADLException e2) {
                    // ignore
                }
            }
        }
        if (errors != null)
            errors.addAll(errorManager.getErrors());
        if (warnings != null)
            warnings.addAll(errorManager.getWarnings());
        return result;
    }

    protected void addOptions(final PluginManager pluginManagerItf) {
        options.addOptions(helpOpt, versionOpt, extensionPointsListOpt);

        options.addOptions(CommandLineOptionExtensionHelper.getCommandOptions(pluginManagerItf));
    }

    protected void checkExclusiveGroups(final PluginManager pluginManagerItf, final CommandLine cmdLine)
            throws InvalidCommandLineException {
        final Collection<Set<String>> exclusiveGroups = CommandLineOptionExtensionHelper
                .getExclusiveGroups(pluginManagerItf);
        for (final Set<String> exclusiveGroup : exclusiveGroups) {
            CmdOption opt = null;
            for (final String id : exclusiveGroup) {
                final CmdOption opt1 = cmdLine.getOptions().getById(id);
                if (opt1.isPresent(cmdLine)) {
                    if (opt != null) {
                        throw new InvalidCommandLineException("Options '" + opt.getPrototype() + "' and '"
                                + opt1.getPrototype() + "' cannot be specified simultaneously on the command line.",
                                1);
                    }
                    opt = opt1;
                }
            }
        }
    }

    protected void invokeOptionHandlers(final PluginManager pluginManagerItf, final CommandLine cmdLine,
            final Map<Object, Object> context) throws InvalidCommandLineException {
        final List<CmdOption> toBeExecuted = new LinkedList<CmdOption>(cmdLine.getOptions().getOptions());
        final Set<String> executedId = new HashSet<String>(toBeExecuted.size());
        while (!toBeExecuted.isEmpty()) {
            final int toBeExecutedSize = toBeExecuted.size();
            final Iterator<CmdOption> iter = toBeExecuted.iterator();
            while (iter.hasNext()) {
                final CmdOption option = iter.next();
                final List<String> precedenceIds = CommandLineOptionExtensionHelper.getPrecedenceIds(option,
                        pluginManagerItf);
                if (executedId.containsAll(precedenceIds)) {
                    // task ready to be executed
                    for (final CommandOptionHandler handler : CommandLineOptionExtensionHelper.getHandler(option,
                            pluginManagerItf)) {
                        handler.processCommandOption(option, cmdLine, context);
                    }
                    executedId.add(option.getId());
                    iter.remove();
                }
            }
            if (toBeExecutedSize == toBeExecuted.size()) {
                // nothing has been executed. there is a circular dependency
                throw new CompilerError(GenericErrors.GENERIC_ERROR,
                        "Circular dependency in command line option handlers: " + toBeExecuted);
            }
        }
    }

    // ---------------------------------------------------------------------------
    // Utility methods
    // ---------------------------------------------------------------------------

    //-- new utility methods imported from CompositeInterfaceLoader & AbstractMembraneLoader since there is no helper for this
    protected TypeInterface getInternalInterface(final Interface itf) {
        if (!(itf instanceof TypeInterface)) {
            throw new CompilerError(GenericErrors.INTERNAL_ERROR, itf, "Interface is not a TypeInterface");
        }

        // clone external interface to create its dual internal interface.
        final TypeInterface internalItf;
        try {
            internalItf = (TypeInterface) nodeMergerItf
                    .merge(nodeFactoryItf.newNode("internalInterface", TypeInterface.class.getName()), itf, null);
            internalItf.astSetSource(itf.astGetSource());
        } catch (final ClassNotFoundException e) {
            throw new CompilerError(GenericErrors.INTERNAL_ERROR, e, "Node factory error");
        } catch (final MergeException e) {
            throw new CompilerError(GenericErrors.INTERNAL_ERROR, e, "Node merge error");
        }
        if (TypeInterfaceUtil.isClient(itf))
            internalItf.setRole(TypeInterface.SERVER_ROLE);
        else
            internalItf.setRole(TypeInterface.CLIENT_ROLE);

        return internalItf;
    }

    protected Controller newControllerNode() {
        return MembraneASTHelper.newControllerNode(nodeFactoryItf);
    }

    protected ControllerInterface newControllerInterfaceNode(final String itfName, final boolean isInternal) {
        return MembraneASTHelper.newControllerInterfaceNode(nodeFactoryItf, itfName, isInternal);
    }

    protected Source newSourceNode(final String path) {
        return MembraneASTHelper.newSourceNode(nodeFactoryItf, path);
    }

    protected ControllerContainer turnToControllerContainer(final Node node) {
        return MembraneASTHelper.turnToControllerContainer(node, nodeFactoryItf, nodeMergerItf);
    }

    protected InternalInterfaceContainer turnToInternalInterfaceContainer(final Node node) {
        return MembraneASTHelper.turnToInternalInterfaceContainer(node, nodeFactoryItf, nodeMergerItf);
    }

    public enum CUnitMode {
        AUTOMATED, CONSOLE, GCOV
    }

    //-- original utility methods

    private void printExtensionPoints(final PluginManager pluginManager, final PrintStream out) {
        final Iterable<String> extensionPoints = pluginManager.getExtensionPointNames();
        System.out.println("Supported extension points are : ");
        for (final String extensionPoint : extensionPoints) {
            System.out.println("\t'" + extensionPoint + "'");
        }
    }

    protected static void checkDir(final File d) throws InvalidCommandLineException {
        if (d.exists() && !d.isDirectory())
            throw new InvalidCommandLineException(
                    "Invalid build directory '" + d.getAbsolutePath() + "' not a directory", 6);
    }

    protected String getVersion() {
        final String pkgVersion = this.getClass().getPackage().getImplementationVersion();
        return (pkgVersion == null) ? "unknown" : pkgVersion;
    }

    protected String getProgramName() {
        return System.getProperty(PROGRAM_NAME_PROPERTY_NAME, getClass().getName());
    }

    protected void printVersion(final PrintStream ps) {
        ps.println(getProgramName() + " version " + getVersion());
    }

    protected void printHelp(final PrintStream ps) {
        printUsage(ps);
        ps.println();
        ps.println("Available options are :");
        int maxCol = 0;

        for (final CmdOption opt : options.getOptions()) {
            final int col = 2 + opt.getPrototype().length();
            if (col > maxCol)
                maxCol = col;
        }
        for (final CmdOption opt : options.getOptions()) {
            final StringBuffer sb = new StringBuffer("  ");
            sb.append(opt.getPrototype());
            while (sb.length() < maxCol)
                sb.append(' ');
            sb.append("  ").append(opt.getDescription());
            ps.println(sb);
        }
    }

    protected void printUsage(final PrintStream ps) {
        ps.println("Usage: " + getProgramName() + " [OPTIONS] (<test_path>)+");
        ps.println("  where <test_path> is a path where to find test cases to be ran.");
    }

    protected void handleException(final InvalidCommandLineException e) {
        logger.log(Level.FINER, "Caught an InvalidCommandLineException", e);
        if (PrintStackTraceOptionHandler.getPrintStackTrace(compilerContext)) {
            e.printStackTrace();
        } else {
            System.err.println(e.getMessage());
            printHelp(System.err);
            System.exit(e.getExitValue());
        }
    }

    /**
     * Entry point.
     * 
     * @param args
     */
    public static void main(final String... args) {
        final Launcher l = new Launcher();
        try {
            l.init(args);
            l.constructTestApplication();
            l.compile(null, null);
        } catch (final InvalidCommandLineException e) {
            l.handleException(e);
        }
        if (!l.errorManager.getErrors().isEmpty())
            System.exit(1);
    }

    public static void nonExitMain(final String... args) throws InvalidCommandLineException, ADLException {
        nonExitMain(null, null, args);
    }

    public static void nonExitMain(final List<Error> errors, final List<Error> warnings, final String... args)
            throws InvalidCommandLineException, ADLException {
        final Launcher l = new Launcher();
        l.init(args);
        l.constructTestApplication();
        l.compile(errors, warnings);
    }

}