# ApplicationBuilder.java

    /*
     * Copyright 2014 Niclas Hedhman.
     * Copyright 2014 Paul Merlin.
     *
     * 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 org.qi4j.bootstrap.builder;

    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Scanner;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    import org.qi4j.api.activation.ActivationEventListener;
    import org.qi4j.api.activation.ActivationEventListenerRegistration;
    import org.qi4j.api.activation.ActivationException;
    import org.qi4j.api.activation.ApplicationPassivationThread;
    import org.qi4j.api.structure.Application;
    import org.qi4j.api.structure.ApplicationDescriptor;
    import org.qi4j.bootstrap.ApplicationAssembler;
    import org.qi4j.bootstrap.ApplicationAssembly;
    import org.qi4j.bootstrap.ApplicationAssemblyFactory;
    import org.qi4j.bootstrap.AssemblyException;
    import org.qi4j.bootstrap.Energy4Java;
    import org.qi4j.bootstrap.LayerAssembly;

    /**
     * Application Builder.
     */
    public class ApplicationBuilder
        implements ActivationEventListenerRegistration
    {
        private final String applicationName;
        private final Map<String, LayerDeclaration> layers = new HashMap<>();
        private final List<ActivationEventListener> activationListeners = new ArrayList<>();

        public ApplicationBuilder( String applicationName )
        {
            this.applicationName = applicationName;
        }

        /**
         * Create and activate a new Application.
         * @return Activated Application
         * @throws AssemblyException if the assembly failed
         * @throws ActivationException if the activation failed
         */
        public Application newApplication()
            throws AssemblyException, ActivationException
        {
            Energy4Java qi4j = new Energy4Java();
            ApplicationDescriptor model = qi4j.newApplicationModel( new ApplicationAssembler()
            {
                @Override
                public ApplicationAssembly assemble( ApplicationAssemblyFactory factory )
                    throws AssemblyException
                {
                    ApplicationAssembly assembly = factory.newApplicationAssembly();
                    assembly.setName( applicationName );
                    HashMap<String, LayerAssembly> createdLayers = new HashMap<>();
                    for( Map.Entry<String, LayerDeclaration> entry : layers.entrySet() )
                    {
                        LayerAssembly layer = entry.getValue().createLayer( assembly );
                        createdLayers.put( entry.getKey(), layer );
                    }
                    for( LayerDeclaration layer : layers.values() )
                    {
                        layer.initialize( createdLayers );
                    }
                    return assembly;
                }
            } );
            Application application = model.newInstance( qi4j.api() );
            for( ActivationEventListener activationListener : activationListeners )
            {
                application.registerActivationEventListener( activationListener );
            }
            beforeActivation();
            application.activate();
            afterActivation();
            return application;
        }

        /**
         * Called before application activation.
         */
        protected void beforeActivation()
        {
        }

        /**
         * Called after application activation.
         */
        protected void afterActivation()
        {
        }

        @Override
        public void registerActivationEventListener( ActivationEventListener listener )
        {
            activationListeners.add( listener );
        }

        @Override
        public void deregisterActivationEventListener( ActivationEventListener listener )
        {
            activationListeners.remove( listener );
        }

        /**
         * Declare Layer.
         * @param layerName Name of the Layer
         * @return Layer declaration for the given name, new if did not already exists
         */
        public LayerDeclaration withLayer( String layerName )
        {
            LayerDeclaration layerDeclaration = layers.get( layerName );
            if( layerDeclaration != null )
            {
                return layerDeclaration;
            }
            layerDeclaration = new LayerDeclaration( layerName );
            layers.put( layerName, layerDeclaration );
            return layerDeclaration;
        }

        /**
         * Load an ApplicationBuilder from a JSON String.
         * @param json JSON String
         * @return Application Builder loaded from JSON
         * @throws JSONException if unable to read JSON
         * @throws AssemblyException if unable to declare the assembly
         */
        public static ApplicationBuilder fromJson( String json )
            throws JSONException, AssemblyException
        {
            JSONObject root = new JSONObject( json );
            return fromJson( root );
        }

        /**
         * Load an ApplicationBuilder from a JSON InputStream.
         * @param json JSON input
         * @return Application Builder loaded from JSON
         * @throws JSONException if unable to read JSON
         * @throws AssemblyException if unable to declare the assembly
         */
        public static ApplicationBuilder fromJson( InputStream json )
            throws JSONException, AssemblyException
        {
            String jsonString = new Scanner( json, "UTF-8" ).useDelimiter( "\\A" ).next();
            return fromJson( jsonString );
        }

        /**
         * Load an ApplicationBuilder from a JSONObject.
         * @param root JSON object
         * @return Application Builder loaded from JSON
         * @throws JSONException if unable to read JSON
         * @throws AssemblyException if unable to declare the assembly
         */
        public static ApplicationBuilder fromJson( JSONObject root )
            throws JSONException, AssemblyException
        {
            String applicationName = root.getString( "name" );
            ApplicationBuilder builder = new ApplicationBuilder( applicationName );
            builder.configureWithJson( root );
            return builder;
        }

        /** Configures the application struucture from a JSON document.
         *
         * @param root The JSON document root.
         * @throws JSONException if the JSON document isn't valid.
         * @throws AssemblyException if probelms in the Assemblers provided in the JSON document.
         */
        protected void configureWithJson( JSONObject root )
            throws JSONException, AssemblyException
        {
            JSONArray layers = root.optJSONArray( "layers" );
            if( layers != null )
            {
                for( int i = 0; i < layers.length(); i++ )
                {
                    JSONObject layerObject = layers.getJSONObject( i );
                    String layerName = layerObject.getString( "name" );
                    LayerDeclaration layerDeclaration = withLayer( layerName );
                    JSONArray using = layerObject.optJSONArray( "uses" );
                    if( using != null )
                    {
                        for( int j = 0; j < using.length(); j++ )
                        {
                            layerDeclaration.using( using.getString( j ) );
                        }
                    }
                    JSONArray modules = layerObject.optJSONArray( "modules" );
                    if( modules != null )
                    {
                        for( int k = 0; k < modules.length(); k++ )
                        {
                            JSONObject moduleObject = modules.getJSONObject( k );
                            String moduleName = moduleObject.getString( "name" );
                            ModuleDeclaration moduleDeclaration = layerDeclaration.withModule( moduleName );
                            JSONArray assemblers = moduleObject.optJSONArray( "assemblers" );
                            if( assemblers != null )
                            {
                                for( int m = 0; m < assemblers.length(); m++ )
                                {
                                    String string = assemblers.getString( m );
                                    moduleDeclaration.withAssembler( string );
                                }
                            }
                        }
                    }
                }
            }
        }

        /**
         * {@literal main} method that read JSON from STDIN.
         * <p>Passivation exceptions are written to STDERR if any.</p>
         * @param args Unused
         * @throws JSONException if unable to read JSON
         * @throws AssemblyException if the assembly failed
         * @throws ActivationException if the activation failed
         */
        public static void main( String[] args )
            throws JSONException, ActivationException, AssemblyException
        {
            ApplicationBuilder builder = fromJson( System.in );
            Application application = builder.newApplication();
            Runtime.getRuntime().addShutdownHook( new ApplicationPassivationThread( application, System.err ) );
        }
    }