polygene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nic...@apache.org
Subject zest-qi4j git commit: Getting a Qi4j project creation tool working.
Date Fri, 17 Apr 2015 16:45:05 GMT
Repository: zest-qi4j
Updated Branches:
  refs/heads/Gradle_archetype_toolchain [created] 28e0817dc


Getting a Qi4j project creation tool working.


Project: http://git-wip-us.apache.org/repos/asf/zest-qi4j/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-qi4j/commit/28e0817d
Tree: http://git-wip-us.apache.org/repos/asf/zest-qi4j/tree/28e0817d
Diff: http://git-wip-us.apache.org/repos/asf/zest-qi4j/diff/28e0817d

Branch: refs/heads/Gradle_archetype_toolchain
Commit: 28e0817dc1b0e3475002a877c39f6a528d7db691
Parents: d7159b1
Author: Niclas Hedhman <niclas@hedhman.org>
Authored: Wed Apr 8 19:05:29 2015 +0800
Committer: Niclas Hedhman <niclas@hedhman.org>
Committed: Wed Apr 8 19:05:29 2015 +0800

----------------------------------------------------------------------
 build.gradle                                    |   7 +
 .../qi4j/runtime/composite/CompositeModel.java  |   2 +-
 tools/shell/build.gradle                        |   9 +-
 tools/shell/dev-status.xml                      |  20 +++
 tools/shell/src/bin/qi4j                        |   4 -
 .../templates/defaultproject/project.properties |   0
 tools/shell/src/main/dist/bin/qi4j              |   4 +
 .../project-templates/simple/project.properties |  31 ++++
 .../simple/src/main/ApplicationTemplate.java    |  17 ++
 .../simple/src/main/LayerTemplate.java          |  22 +++
 .../project-templates/simple/src/main/Main.java |   0
 .../simple/src/main/ModuleTemplate.java         |  16 ++
 .../main/java/org/qi4j/tools/shell/Command.java |   3 +-
 .../java/org/qi4j/tools/shell/FileUtils.java    |  62 ++++++++
 .../main/java/org/qi4j/tools/shell/Main.java    |  93 +++++++++--
 .../java/org/qi4j/tools/shell/StringUtils.java  |  32 ++++
 .../qi4j/tools/shell/create/CreateProject.java  |  43 -----
 .../shell/create/project/CreateProject.java     |  78 +++++++++
 .../org/qi4j/tools/shell/generate/Generate.java |  76 +++++++++
 .../org/qi4j/tools/shell/help/HelpCommand.java  |  64 +++++---
 .../tools/shell/model/ApplicationTemplate.java  |  46 ++++++
 .../qi4j/tools/shell/model/AssemblerModel.java  |  78 +++++++++
 .../java/org/qi4j/tools/shell/model/Layer.java  |  83 ++++++++++
 .../java/org/qi4j/tools/shell/model/Model.java  |  96 ++++++++++++
 .../java/org/qi4j/tools/shell/model/Module.java |  74 +++++++++
 .../org/qi4j/tools/shell/model/Nameable.java    |  57 +++++++
 .../org/qi4j/tools/shell/model/Project.java     | 157 +++++++++++++++++++
 .../model/ProjectAlreadyExistsException.java    |  17 ++
 .../model/ProjectDescriptorByProperties.java    |  86 ++++++++++
 .../model/TemplateAlreadyExistsException.java   |  16 ++
 .../model/generation/AssemblerGenerator.java    |  92 +++++++++++
 .../tools/shell/templating/TemplateEngine.java  |  40 +++++
 .../shell/templating/TemplatingEngineTest.java  |  40 +++++
 33 files changed, 1377 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index 20f215e..575d6c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -558,8 +558,15 @@ def tutorialsImage = copySpec {
   exclude '**/*.iml'
 }
 
+def shellImage = copySpec {
+  from( "$projectDir/tools/shell/src/main/dist" )
+  into( "." )
+  include '**'
+}
+
 def binDistImage = copySpec {
   into "qi4j-sdk-$version"
+  with shellImage
   with docsImage
   with reportsDistImage
   with runtimeDependenciesListImage

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/core/runtime/src/main/java/org/qi4j/runtime/composite/CompositeModel.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/composite/CompositeModel.java b/core/runtime/src/main/java/org/qi4j/runtime/composite/CompositeModel.java
index c689929..abb0bbd 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/composite/CompositeModel.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/composite/CompositeModel.java
@@ -246,7 +246,7 @@ public abstract class CompositeModel
 //        if (!matchesAny( isAssignableFrom( mixinType ), types ))
         if( !mixinsModel.isImplemented( mixinType ) )
         {
-            throw new IllegalArgumentException( "Composite does not implement type " + mixinType.getName() );
+            throw new IllegalArgumentException( "Composite " + types().iterator().next() + " does not implement type " + mixinType.getName() );
         }
 
         // Instantiate proxy for given mixin interface

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/build.gradle
----------------------------------------------------------------------
diff --git a/tools/shell/build.gradle b/tools/shell/build.gradle
index a3d89ac..fb89106 100644
--- a/tools/shell/build.gradle
+++ b/tools/shell/build.gradle
@@ -1,12 +1,17 @@
-apply plugin: 'application'
 
 description = "Command line tools for building Qi4j applications."
-mainClassName = "org.qi4j.tools.shell.Main"
 
 jar { manifest { name = "Qi4j Command Line" } }
 
 dependencies {
+  compile( project( ":org.qi4j.core:org.qi4j.core.api" ) )
   compile( project( ":org.qi4j.core:org.qi4j.core.bootstrap" ) )
+  compile( project( ":org.qi4j.extensions:org.qi4j.extension.entitystore-file" ) )
+  compile( project( ":org.qi4j.extensions:org.qi4j.extension.valueserialization-jackson" ) )
+  runtime( project( ":org.qi4j.core:org.qi4j.core.runtime" ) )
+
 
   testRuntime( libraries.logback )
 }
+
+

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/dev-status.xml
----------------------------------------------------------------------
diff --git a/tools/shell/dev-status.xml b/tools/shell/dev-status.xml
new file mode 100644
index 0000000..edd1bc6
--- /dev/null
+++ b/tools/shell/dev-status.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<module xmlns="http://www.qi4j.org/schemas/2008/dev-status/1"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://www.qi4j.org/schemas/2008/dev-status/1
+        http://www.qi4j.org/schemas/2008/dev-status/1/dev-status.xsd">
+  <status>
+    <!--none,early,beta,stable,mature-->
+    <codebase>stable</codebase>
+
+    <!-- none, brief, good, complete -->
+    <documentation>brief</documentation>
+
+    <!-- none, some, good, complete -->
+    <unittests>some</unittests>
+
+  </status>
+  <licenses>
+    <license>ALv2</license>
+  </licenses>
+</module>

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/bin/qi4j
----------------------------------------------------------------------
diff --git a/tools/shell/src/bin/qi4j b/tools/shell/src/bin/qi4j
deleted file mode 100644
index d678cfd..0000000
--- a/tools/shell/src/bin/qi4j
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-java -jar org.qi4j.tools.shell-@@version@@.jar "$@"
-

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/etc/templates/defaultproject/project.properties
----------------------------------------------------------------------
diff --git a/tools/shell/src/etc/templates/defaultproject/project.properties b/tools/shell/src/etc/templates/defaultproject/project.properties
deleted file mode 100644
index e69de29..0000000

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/bin/qi4j
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/bin/qi4j b/tools/shell/src/main/dist/bin/qi4j
new file mode 100644
index 0000000..d678cfd
--- /dev/null
+++ b/tools/shell/src/main/dist/bin/qi4j
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+java -jar org.qi4j.tools.shell-@@version@@.jar "$@"
+

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/etc/project-templates/simple/project.properties
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/etc/project-templates/simple/project.properties b/tools/shell/src/main/dist/etc/project-templates/simple/project.properties
new file mode 100644
index 0000000..db3e344
--- /dev/null
+++ b/tools/shell/src/main/dist/etc/project-templates/simple/project.properties
@@ -0,0 +1,31 @@
+
+layers=5
+layer.0.name=configuration layer
+layer.1.name=infrastructure layer
+layer.2.name=domain layer
+layer.3.name=service layer
+layer.4.name=interface layer
+
+layer.0.modules=1
+layer.0.module.0.name=configuration
+
+layer.1.modules=2
+layer.1.uses=configuration layer
+layer.1.module.0.name=persistence
+layer.1.module.1.name=indexing
+
+layer.2.modules=2
+layer.2.uses=configuration layer, infrastructure layer
+layer.2.module.0.name=first
+layer.2.module.1.name=second
+
+layer.3.modules=2
+layer.3.uses=domain layer
+layer.3.module.0.name=first
+layer.3.module.1.name=second
+
+layer.4.modules=2
+layer.4.uses=service layer
+layer.4.module.0.name=rest
+layer.4.module.1.name=web
+

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ApplicationTemplate.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ApplicationTemplate.java b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ApplicationTemplate.java
new file mode 100644
index 0000000..638127c
--- /dev/null
+++ b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ApplicationTemplate.java
@@ -0,0 +1,17 @@
+
+package @@ROOT_PACKAGE@@.boot;
+
+import org.qi4j.bootstrap.ApplicationAssembly;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.LayerAssembly;
+
+@@IMPORTS@@
+
+public class @@APPLICATION_NAME@@Assembler
+{
+    public LayerAssembly assemble( ApplicationAssembly assembly )
+        throws AssemblyException
+    {
+@@LAYER_CREATION@@
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/etc/project-templates/simple/src/main/LayerTemplate.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/etc/project-templates/simple/src/main/LayerTemplate.java b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/LayerTemplate.java
new file mode 100644
index 0000000..6878195
--- /dev/null
+++ b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/LayerTemplate.java
@@ -0,0 +1,22 @@
+
+package @@ROOT_PACKAGE@@.boot;
+
+import org.qi4j.bootstrap.ApplicationAssembly;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.LayerAssembly;
+
+@@IMPORTS@@
+
+public class @@LAYER_NAME@@Assembler
+{
+    public @@LAYER_NAME@@Assembler()
+    {}
+
+    public LayerAssembly assemble( ApplicationAssembly assembly )
+        throws AssemblyException
+    {
+@@MODULE_CREATION@@
+    }
+
+@@MODULE_CREATE_METHODS@@
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/etc/project-templates/simple/src/main/Main.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/etc/project-templates/simple/src/main/Main.java b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/Main.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ModuleTemplate.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ModuleTemplate.java b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ModuleTemplate.java
new file mode 100644
index 0000000..166de66
--- /dev/null
+++ b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/ModuleTemplate.java
@@ -0,0 +1,16 @@
+
+package @@ROOT_PACKAGE@@.boot;
+
+import org.qi4j.bootstrap.ApplicationAssembler;
+
+
+public class @@MODULE_NAME@@Assembler
+    implements Assembler
+{
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        @@MODULE_ASSEMBLY@@
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/Command.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/Command.java b/tools/shell/src/main/java/org/qi4j/tools/shell/Command.java
index 046de85..0b82675 100644
--- a/tools/shell/src/main/java/org/qi4j/tools/shell/Command.java
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/Command.java
@@ -1,12 +1,13 @@
 package org.qi4j.tools.shell;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.PrintWriter;
 
 public interface Command
 {
     void execute( String[] args, BufferedReader input, PrintWriter output )
-        throws HelpNeededException;
+        throws HelpNeededException, IOException;
 
     String description();
 

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/FileUtils.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/FileUtils.java b/tools/shell/src/main/java/org/qi4j/tools/shell/FileUtils.java
index e0f96ba..5dac990 100644
--- a/tools/shell/src/main/java/org/qi4j/tools/shell/FileUtils.java
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/FileUtils.java
@@ -1,8 +1,15 @@
 package org.qi4j.tools.shell;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -19,6 +26,15 @@ public class FileUtils
         }
     }
 
+    public static void createDir( File dir )
+    {
+        if( !dir.mkdirs() )
+        {
+            System.err.println( "Unable to create directory " + dir );
+            System.exit( 1 );
+        }
+    }
+
     public static Map<String, String> readPropertiesResource( String resourceName )
     {
         ClassLoader cl = FileUtils.class.getClassLoader();
@@ -48,4 +64,50 @@ public class FileUtils
         p.load( in );
         return p;
     }
+
+    public static void writeFile( File file, String data )
+        throws IOException
+    {
+        try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ), "UTF-8" ) ))
+        {
+            writer.write( data );
+        }
+    }
+
+    public static Map<String, String> readPropertiesFile( File file )
+    {
+        try (InputStream in = new BufferedInputStream( new FileInputStream( file ) ))
+        {
+            Properties properties = readProperties( in );
+            Map<String, String> result = new HashMap<String, String>();
+            for( Map.Entry prop : properties.entrySet() )
+            {
+                result.put( prop.getKey().toString(), prop.getValue().toString() );
+            }
+            return result;
+        }
+        catch( IOException e )
+        {
+            System.err.println( "Unable to read file " + file.getAbsolutePath() );
+            System.exit( 2 );
+            return null;
+        }
+    }
+
+    public static String readFile( File file )
+        throws IOException
+    {
+        try (BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( file ), "UTF-8" ) ))
+        {
+            StringBuilder builder = new StringBuilder();
+            String line = reader.readLine();
+            while( line != null )
+            {
+                builder.append( line );
+                builder.append( '\n' );
+                line = reader.readLine();
+            }
+            return builder.toString();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/Main.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/Main.java b/tools/shell/src/main/java/org/qi4j/tools/shell/Main.java
index 9a5966d..08810af 100644
--- a/tools/shell/src/main/java/org/qi4j/tools/shell/Main.java
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/Main.java
@@ -1,40 +1,107 @@
 package org.qi4j.tools.shell;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-import java.util.TreeSet;
-import org.qi4j.tools.shell.create.CreateProject;
+import org.qi4j.api.activation.ActivationException;
+import org.qi4j.api.activation.PassivationException;
+import org.qi4j.api.common.Visibility;
+import org.qi4j.api.service.ServiceReference;
+import org.qi4j.api.structure.Module;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.bootstrap.SingletonAssembler;
+import org.qi4j.entitystore.file.assembly.FileEntityStoreAssembler;
+import org.qi4j.entitystore.memory.MemoryEntityStoreService;
+import org.qi4j.functional.Specification;
+import org.qi4j.tools.shell.create.project.CreateProject;
+import org.qi4j.tools.shell.generate.Generate;
+import org.qi4j.tools.shell.model.Model;
 import org.qi4j.tools.shell.help.HelpCommand;
+import org.qi4j.tools.shell.model.Layer;
+import org.qi4j.tools.shell.model.Project;
+import org.qi4j.valueserialization.jackson.JacksonValueSerializationAssembler;
+
+import static org.qi4j.functional.Iterables.filter;
+import static org.qi4j.functional.Iterables.first;
 
 public class Main
 {
-    private TreeSet<Command> commands = new TreeSet<Command>();
-
     public static void main( String[] args )
+        throws Exception
     {
         new Main().run( args );
     }
 
-    public Main()
-    {
-        this.commands.add( new HelpCommand() );
-        this.commands.add( new CreateProject() );
-    }
-
     private void run( String[] args )
+        throws ActivationException, AssemblyException, IOException
     {
         if( !contains( args, "-q" ) )
         {
             System.out.println( "Qi4j - Classes are Dead. Long Live Interfaces!" );
             System.out.println( "----------------------------------------------\n" );
         }
+        String commandText;
         if( args.length == 0 )
         {
-            HelpCommand helpCommand = new HelpCommand();
-            helpCommand.setCommands( commands );
-            helpCommand.execute( args, input(), output() );
+            commandText = "help";
         }
+        else
+        {
+            commandText = args[ 0 ];
+        }
+        final SingletonAssembler assembler = new SingletonAssembler()
+        {
+            @Override
+            public void assemble( ModuleAssembly module )
+                throws AssemblyException
+            {
+                module.services( HelpCommand.class ).identifiedBy( "help" ).instantiateOnStartup();
+                module.services( CreateProject.class ).identifiedBy( "create-project" ).instantiateOnStartup();
+                module.services( Model.class ).instantiateOnStartup();
+                module.services( Generate.class ).instantiateOnStartup();
+
+                module.entities( Project.class, Layer.class, org.qi4j.tools.shell.model.Module.class );
+                new JacksonValueSerializationAssembler().assemble( module );
+
+                ModuleAssembly configModule = module.layer().module( "config" );
+                new FileEntityStoreAssembler().withConfig( configModule, Visibility.layer ).assemble( module );
+                new JacksonValueSerializationAssembler().assemble( configModule );
+                configModule.services( MemoryEntityStoreService.class );
+            }
+        };
+        Runtime.getRuntime().addShutdownHook( new Thread( new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    assembler.application().passivate();
+                }
+                catch( PassivationException e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        } ) );
+        executeCommand( commandText, args, assembler.module() );
+        output().flush();
+    }
+
+    private void executeCommand( final String command, String[] args, Module module )
+        throws IOException
+    {
+        ServiceReference<Command> ref = first( filter( new Specification<ServiceReference<Command>>()
+        {
+            @Override
+            public boolean satisfiedBy( ServiceReference<Command> item )
+            {
+                return item.get().name().equals( command );
+            }
+        }, module.findServices( Command.class ) ) );
+        ref.get().execute( args, input(), output() );
     }
 
     private boolean contains( String[] args, String s )

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/StringUtils.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/StringUtils.java b/tools/shell/src/main/java/org/qi4j/tools/shell/StringUtils.java
new file mode 100644
index 0000000..2ad31b8
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/StringUtils.java
@@ -0,0 +1,32 @@
+package org.qi4j.tools.shell;
+
+public class StringUtils
+{
+    private StringUtils()
+    {
+    }
+
+    public static String camelCase( String text, boolean firstUpper )
+    {
+        StringBuilder builder = new StringBuilder( text.length() );
+        boolean initial = firstUpper;
+        for( int i = 0; i < text.length(); i++ )
+        {
+            char ch = text.charAt( i );
+            if( initial )
+            {
+                ch = Character.toUpperCase( ch );
+                initial = false;
+            }
+            if( ch != ' ' )
+            {
+                builder.append( ch );
+            }
+            else
+            {
+                initial = true;
+            }
+        }
+        return builder.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/create/CreateProject.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/create/CreateProject.java b/tools/shell/src/main/java/org/qi4j/tools/shell/create/CreateProject.java
deleted file mode 100644
index 0c864ab..0000000
--- a/tools/shell/src/main/java/org/qi4j/tools/shell/create/CreateProject.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.qi4j.tools.shell.create;
-
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Properties;
-import org.qi4j.tools.shell.AbstractCommand;
-import org.qi4j.tools.shell.FileUtils;
-import org.qi4j.tools.shell.HelpNeededException;
-
-public class CreateProject extends AbstractCommand
-{
-
-    @Override
-    public void execute( String[] args, BufferedReader input, PrintWriter output )
-        throws HelpNeededException
-    {
-        if( args.length < 1 )
-            throw new HelpNeededException();
-        String projectName = args[0];
-        String template = "defaultproject";
-        if( args.length < 2 )
-            template = args[1];
-        FileUtils.createDir( projectName );
-        Map<String, String> props = FileUtils.readPropertiesResource( "templates/" + template + "/project.properties" );
-        for( Map.Entry<String,String> p: props.entrySet() )
-        {
-
-        }
-    }
-
-    @Override
-    public String description()
-    {
-        return "create-project";
-    }
-
-    @Override
-    public String name()
-    {
-        return "create-project";
-    }
-}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/create/project/CreateProject.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/create/project/CreateProject.java b/tools/shell/src/main/java/org/qi4j/tools/shell/create/project/CreateProject.java
new file mode 100644
index 0000000..58d69ff
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/create/project/CreateProject.java
@@ -0,0 +1,78 @@
+package org.qi4j.tools.shell.create.project;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import org.qi4j.api.concern.Concerns;
+import org.qi4j.api.injection.scope.Service;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkConcern;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+import org.qi4j.tools.shell.Command;
+import org.qi4j.tools.shell.HelpNeededException;
+import org.qi4j.tools.shell.model.Model;
+import org.qi4j.tools.shell.model.Project;
+import org.qi4j.tools.shell.model.ProjectDescriptorByProperties;
+
+@Mixins( CreateProject.Mixin.class )
+@Concerns( UnitOfWorkConcern.class )
+public interface CreateProject extends Command
+{
+    public class Mixin
+        implements Command
+    {
+        @Service
+        private Model model;
+
+        @Service
+        private ProjectDescriptorByProperties loadFromProperties;
+
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        @Override
+        @UnitOfWorkPropagation
+        public void execute( String[] args, BufferedReader input, PrintWriter output )
+            throws HelpNeededException, IOException
+        {
+            if( args.length < 2 )
+            {
+                throw new HelpNeededException();
+            }
+            String projectName = args[ 1 ];
+
+            String rootPackage = "com.example." + projectName.toLowerCase().replace( '-', '_' );
+            if( args.length >= 3 )
+            {
+                rootPackage = args[ 2 ];
+            }
+            String template = "simple";
+            if( args.length >= 4 )
+            {
+                template = args[ 3 ];
+            }
+            model.setHomeDirectory( new File( System.getProperty( "homeDir" ) ) );
+            String propsLocation = System.getProperty( "homeDir" ) + "/project-templates/" + template + "/project.properties";
+
+            loadFromProperties.parse( projectName, new File( propsLocation ) );
+            Project project = loadFromProperties.findProject( projectName );
+            project.setApplicationVersion( System.getProperty( "version", "1" ) );
+            project.setRootPackageName( rootPackage );
+        }
+
+        @Override
+        public String description()
+        {
+            return "Creates a managed project.";
+        }
+
+        @Override
+        public String name()
+        {
+            return "create-project";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/generate/Generate.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/generate/Generate.java b/tools/shell/src/main/java/org/qi4j/tools/shell/generate/Generate.java
new file mode 100644
index 0000000..0e8eb87
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/generate/Generate.java
@@ -0,0 +1,76 @@
+package org.qi4j.tools.shell.generate;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import org.qi4j.api.concern.Concerns;
+import org.qi4j.api.injection.scope.Service;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkConcern;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+import org.qi4j.tools.shell.Command;
+import org.qi4j.tools.shell.FileUtils;
+import org.qi4j.tools.shell.HelpNeededException;
+import org.qi4j.tools.shell.StringUtils;
+import org.qi4j.tools.shell.model.Layer;
+import org.qi4j.tools.shell.model.Model;
+import org.qi4j.tools.shell.model.Project;
+import org.qi4j.tools.shell.templating.TemplateEngine;
+
+@Mixins( Generate.Mixin.class )
+@Concerns( UnitOfWorkConcern.class )
+public interface Generate extends Command
+{
+
+    public class Mixin
+        implements Generate
+    {
+        @Service
+        private Model model;
+
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        @Override
+        @UnitOfWorkPropagation
+        public void execute( String[] args, BufferedReader input, PrintWriter output )
+            throws HelpNeededException, IOException
+        {
+            if( args.length < 2 )
+            {
+                throw new HelpNeededException();
+            }
+
+            Project project = Project.Support.get( uowf.currentUnitOfWork(), args[ 1 ] );
+            if( project == null )
+            {
+                System.err.print( "Project " + args[ 1 ] + " does not exist." );
+                return;
+            }
+            model.setHomeDirectory( new File( System.getProperty( "homeDir" ) ) );
+            File projectDir = new File( project.applicationName() );
+            if( args.length >= 3 )
+            {
+                projectDir = new File(args[2]).getAbsoluteFile();
+            }
+            project.generate( projectDir );
+        }
+
+        @Override
+        public String description()
+        {
+            return "Generates the file structure from what has been built up in the database.";
+        }
+
+        @Override
+        public String name()
+        {
+            return "generate";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/help/HelpCommand.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/help/HelpCommand.java b/tools/shell/src/main/java/org/qi4j/tools/shell/help/HelpCommand.java
index 45131d7..1d9ead3 100644
--- a/tools/shell/src/main/java/org/qi4j/tools/shell/help/HelpCommand.java
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/help/HelpCommand.java
@@ -2,40 +2,56 @@ package org.qi4j.tools.shell.help;
 
 import java.io.BufferedReader;
 import java.io.PrintWriter;
-import org.qi4j.tools.shell.AbstractCommand;
+import org.qi4j.api.injection.scope.Service;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.service.ServiceReference;
 import org.qi4j.tools.shell.Command;
 
-public class HelpCommand extends AbstractCommand
+@Mixins( HelpCommand.Mixin.class )
+public interface HelpCommand extends Command
 {
-    private Iterable<Command> commands;
 
-    public HelpCommand()
+    public class Mixin
+        implements Command
     {
-    }
 
-    public void setCommands( Iterable<Command> comands )
-    {
-        this.commands = commands;
-    }
+        @Service
+        private Iterable<ServiceReference<Command>> commands;
 
-    @Override
-    public void execute( String[] args, BufferedReader input, PrintWriter output )
-    {
-        for( Command command : commands )
+        @Override
+        public void execute( String[] args, BufferedReader input, PrintWriter output )
         {
-            output.println( command.name() + "\t" + command.description() );
+            for( ServiceReference<Command> ref : commands )
+            {
+                Command command = ref.get();
+                String name = command.name();
+                String spacing = createSpacing( name );
+                output.println( name + spacing + command.description() );
+                output.flush();
+            }
         }
-    }
 
-    @Override
-    public String description()
-    {
-        return "help";
-    }
+        private String createSpacing( String name )
+        {
+            int length = 20 - name.length();
+            StringBuilder builder = new StringBuilder( length + 1 );
+            for( int i = 0; i < length; i++ )
+            {
+                builder.append( ' ' );
+            }
+            return builder.toString();
+        }
 
-    @Override
-    public String name()
-    {
-        return "Prints this help text.";
+        @Override
+        public String description()
+        {
+            return "Prints this help text.";
+        }
+
+        @Override
+        public String name()
+        {
+            return "help";
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/ApplicationTemplate.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/ApplicationTemplate.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ApplicationTemplate.java
new file mode 100644
index 0000000..a901b0a
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ApplicationTemplate.java
@@ -0,0 +1,46 @@
+package org.qi4j.tools.shell.model;
+
+import java.util.Map;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.tools.shell.templating.TemplateEngine;
+
+@Mixins(ApplicationTemplate.Mixin.class)
+public interface ApplicationTemplate extends Nameable
+{
+    String evaluate( Map<String, String> variables );
+    
+    interface State
+    {
+        Property<String> template();
+    }
+    
+    public class Support
+    {
+        public static ApplicationTemplate get( UnitOfWork uow, String name )
+        {
+            return uow.get( ApplicationTemplate.class, identity(name) );
+        }
+
+        public static String identity( String name )
+        {
+            return "Template:" + name;
+        }
+    }
+    
+    abstract class Mixin
+        implements ApplicationTemplate
+    {
+        @This
+        private State state;
+
+        @Override
+        public String evaluate( Map<String, String> variables )
+        {
+            TemplateEngine engine = new TemplateEngine( state.template().get() );
+            return engine.create( variables );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/AssemblerModel.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/AssemblerModel.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/AssemblerModel.java
new file mode 100644
index 0000000..c830d34
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/AssemblerModel.java
@@ -0,0 +1,78 @@
+package org.qi4j.tools.shell.model;
+
+import java.io.File;
+import java.io.IOException;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.tools.shell.model.generation.AssemblerGenerator;
+
+@Mixins( { AssemblerModel.Mixin.class, AssemblerGenerator.class } )
+public interface AssemblerModel
+{
+    // Commands
+    void generate( Project project, File projectDir )
+        throws IOException;
+
+    // Queries
+    String packageName();
+
+    // Queries
+    File mainJavaRootPackageDirectory( File projectDir );
+
+    File mainResourcesRootPackageDirectory( File projectDir );
+
+    File testJavaRootPackageDirectory( File projectDir );
+
+    File testResourcesRootPackageDirectory( File projectDir );
+
+    interface State
+    {
+        Property<String> packageName();
+    }
+
+    abstract class Mixin
+        implements AssemblerModel
+    {
+        @This
+        private State state;
+
+        @Override
+        public File mainJavaRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/bootstrap/src/main/java" );
+        }
+
+        @Override
+        public File mainResourcesRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/bootstrap/src/main/resources" );
+        }
+
+        @Override
+        public File testJavaRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/bootstrap/src/test/java" );
+        }
+
+        @Override
+        public File testResourcesRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/bootstrap/src/test/resources" );
+        }
+
+        private File normalize( File projectDir, String location )
+        {
+            File dir = new File( projectDir, location );
+            dir = new File( dir, state.packageName().get().replace( '.', '/' ) );
+            dir.mkdirs();
+            return dir;
+        }
+
+        @Override
+        public String packageName()
+        {
+            return state.packageName().get();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/Layer.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/Layer.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Layer.java
new file mode 100644
index 0000000..adf610e
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Layer.java
@@ -0,0 +1,83 @@
+package org.qi4j.tools.shell.model;
+
+import java.util.List;
+import org.qi4j.api.association.Association;
+import org.qi4j.api.association.ManyAssociation;
+import org.qi4j.api.common.UseDefaults;
+import org.qi4j.api.entity.Aggregated;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+import org.qi4j.tools.shell.StringUtils;
+
+@Mixins( Layer.Mixin.class )
+public interface Layer extends Nameable
+{
+    // Commands
+    void usesLayer( String layer );
+
+    void createModule( String moduleName );
+
+    // Queries
+    Iterable<String> uses();
+
+    String packageName();
+
+    interface State
+    {
+        @UseDefaults
+        Property<String> packageName();
+
+        @UseDefaults
+        @Aggregated
+        ManyAssociation<Module> modules();
+
+        @UseDefaults
+        Property<List<String>> uses();
+    }
+
+    abstract class Mixin
+        implements Layer
+    {
+        @This
+        private Layer self;
+
+        @This
+        private State state;
+
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        @Override
+        public Iterable<String> uses()
+        {
+            return state.uses().get();
+        }
+
+        @Override
+        public String packageName()
+        {
+            return state.packageName().get();
+        }
+
+        @Override
+        @UnitOfWorkPropagation
+        public void createModule( String moduleName )
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            Nameable.Support.create( uow, Module.class, self, moduleName );
+            Module module = Nameable.Support.get( uow, Module.class, self, moduleName );
+            state.modules().add( module );
+        }
+
+        @Override
+        public void usesLayer( String layer )
+        {
+            state.uses().get().add( layer );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/Model.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/Model.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Model.java
new file mode 100644
index 0000000..5f16038
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Model.java
@@ -0,0 +1,96 @@
+package org.qi4j.tools.shell.model;
+
+import java.io.File;
+import org.qi4j.api.entity.EntityBuilder;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+
+@Mixins( Model.Mixin.class )
+public interface Model
+{
+    // Commands
+    void setHomeDirectory( File homeDir );
+
+    void createProject( String name )
+        throws ProjectAlreadyExistsException;
+
+    void addTemplate( String name, String template )
+        throws TemplateAlreadyExistsException;
+
+    // Queries
+    File homeDirectory();
+
+    Project findProject( String name );
+
+    interface State
+    {
+        Property<File> homeDirectory();
+    }
+
+    class Mixin
+        implements Model
+    {
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        @This
+        private State state;
+
+        @Override
+        public void setHomeDirectory( File homeDir )
+        {
+            state.homeDirectory().set( homeDir );
+        }
+
+        @UnitOfWorkPropagation
+        public void createProject( String name )
+            throws ProjectAlreadyExistsException
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            Project existing = Project.Support.get( uow, name );
+            if( existing != null )
+            {
+                throw new ProjectAlreadyExistsException( name );
+            }
+            Nameable.Support.create( uow, Project.class, null, name );
+        }
+
+        @Override
+        public void addTemplate( String name, String template )
+            throws TemplateAlreadyExistsException
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            ApplicationTemplate existing = ApplicationTemplate.Support.get( uow, name );
+            if( existing != null )
+            {
+                throw new TemplateAlreadyExistsException( name );
+            }
+            EntityBuilder<ApplicationTemplate> builder =
+                uow.newEntityBuilder( ApplicationTemplate.class, "Template(" + name + ")" );
+            ApplicationTemplate.State prototype = builder.instanceFor( ApplicationTemplate.State.class );
+            prototype.template().set( template );
+            Nameable.State naming = builder.instanceFor( Nameable.State.class );
+            naming.name().set( name );
+            builder.newInstance();
+        }
+
+        @Override
+        public File homeDirectory()
+        {
+            return state.homeDirectory().get();
+        }
+
+        @Override
+        @UnitOfWorkPropagation
+        public Project findProject( String name )
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            return Nameable.Support.get(uow, Project.class, null, name );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/Module.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/Module.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Module.java
new file mode 100644
index 0000000..ca20992
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Module.java
@@ -0,0 +1,74 @@
+package org.qi4j.tools.shell.model;
+
+import java.io.File;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+
+@Mixins( Module.Mixin.class )
+public interface Module extends Nameable
+{
+    // Commands
+    void setPackageName( String packageName );
+
+    // Queries
+    File mainJavaRootPackageDirectory( File projectDir );
+
+    File mainResourcesRootPackageDirectory( File projectDir );
+
+    File testJavaRootPackageDirectory( File projectDir );
+
+    File testResourcesRootPackageDirectory( File projectDir );
+
+    public interface State
+    {
+        Property<String> name();
+
+        Property<String> packageName();
+    }
+
+    abstract class Mixin
+        implements Module
+    {
+        @This
+        private State state;
+
+        @Override
+        public void setPackageName( String name )
+        {
+            state.packageName().set( name );
+        }
+
+        @Override
+        public File mainJavaRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/src/main/java" );
+        }
+
+        @Override
+        public File mainResourcesRootPackageDirectory( File projectDir )
+        {
+            return normalize( projectDir, "/src/main/resources" );
+        }
+
+        @Override
+        public File testJavaRootPackageDirectory(File projectDir)
+        {
+            return normalize( projectDir, "/src/test/java" );
+        }
+
+        @Override
+        public File testResourcesRootPackageDirectory(File projectDir)
+        {
+            return normalize( projectDir, "/src/test/resources" );
+        }
+
+        private File normalize( File projectDir, String location )
+        {
+            File dir = new File( projectDir, name() + location );
+            dir = new File( dir, state.packageName().get().replace( '.', '/' ) );
+            dir.mkdirs();
+            return dir;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/Nameable.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/Nameable.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Nameable.java
new file mode 100644
index 0000000..2dac152
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Nameable.java
@@ -0,0 +1,57 @@
+package org.qi4j.tools.shell.model;
+
+import org.qi4j.api.entity.EntityBuilder;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+
+@Mixins( Nameable.Mixin.class )
+public interface Nameable
+{
+    String name();
+
+    interface State
+    {
+        Property<String> name();
+    }
+
+    public class Mixin
+        implements Nameable
+    {
+        @This
+        private State state;
+
+        @Override
+        public String name()
+        {
+            return state.name().get();
+        }
+    }
+
+    public class Support
+    {
+        public static <T extends Nameable> String identity( Class<T> type, Nameable parent, String name )
+        {
+            if( parent == null )
+            {
+                return type.getSimpleName() + "(" + name + ")";
+            }
+            return parent.name() + "." + type.getSimpleName() + "(" + name + ")";
+        }
+
+        public static <T extends Nameable> T get( UnitOfWork uow, Class<T> type, Nameable parent, String name )
+        {
+            String identity = identity( type, parent, name );
+            return uow.get( type, identity );
+        }
+
+        public static <T extends Nameable> void create( UnitOfWork uow, Class<T> type, Nameable parent, String name )
+        {
+            EntityBuilder<T> builder = uow.newEntityBuilder( type, identity( type, parent, name ) );
+            Nameable.State prototype = builder.instanceFor( Nameable.State.class );
+            prototype.name().set( name );
+            builder.newInstance();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/Project.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/Project.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Project.java
new file mode 100644
index 0000000..f26b7ec
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/Project.java
@@ -0,0 +1,157 @@
+package org.qi4j.tools.shell.model;
+
+import java.io.File;
+import java.io.IOException;
+import org.qi4j.api.association.Association;
+import org.qi4j.api.association.ManyAssociation;
+import org.qi4j.api.common.UseDefaults;
+import org.qi4j.api.entity.Aggregated;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+import org.qi4j.tools.shell.FileUtils;
+
+@Mixins( Project.Mixin.class )
+public interface Project extends Nameable
+{
+    // Commands
+    void createLayer( String layerName );
+
+    void applyTemplate( ApplicationTemplate template );
+
+    void setApplicationVersion( String version );
+
+    void setRootPackageName( String name );
+
+    void generate( File directory );
+
+    // Query Methods
+    String applicationName();
+
+    String applicationVersion();
+
+    String rootPackageName();
+
+    ApplicationTemplate template();
+
+    Iterable<Layer> layers();
+
+    Layer findLayer( String layerName );
+
+    public class Support
+    {
+        public static String identity( String name )
+        {
+            return "Project:" + name;
+        }
+
+        public static Project get( UnitOfWork uow, String name )
+        {
+            return uow.get( Project.class, identity( name ) );
+        }
+    }
+
+    interface State
+    {
+
+        Property<String> name();
+
+        Property<String> version();
+
+        Property<String> rootPackage();
+
+        @UseDefaults
+        @Aggregated
+        ManyAssociation<Layer> layers();
+
+        @Aggregated
+        Association<ApplicationTemplate> template();
+    }
+
+    abstract class Mixin
+        implements Project
+    {
+        @This
+        private Project self;
+
+        @This
+        private State state;
+
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        @Override
+        public String applicationName()
+        {
+            return state.name().get();
+        }
+
+        @Override
+        public String applicationVersion()
+        {
+            return state.version().get();
+        }
+
+        @Override
+        @UnitOfWorkPropagation
+        public void createLayer( String layerName )
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            Nameable.Support.create( uow, Layer.class, self, layerName );
+        }
+
+        @Override
+        public void applyTemplate( ApplicationTemplate template )
+        {
+            state.template().set(template);
+        }
+
+        @Override
+        public void setApplicationVersion( String version )
+        {
+            state.version().set( version );
+        }
+
+        @Override
+        public void setRootPackageName( String name )
+        {
+            state.rootPackage().set( name );
+        }
+
+        @Override
+        public void generate( File directory )
+        {
+
+        }
+
+        @Override
+        public String rootPackageName()
+        {
+            return state.rootPackage().get();
+        }
+
+        @Override
+        public ApplicationTemplate template()
+        {
+            return state.template().get();
+        }
+
+        @Override
+        @UnitOfWorkPropagation
+        public Layer findLayer( String name )
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            return Nameable.Support.get( uow, Layer.class, self, name );
+        }
+
+        @Override
+        public Iterable<Layer> layers()
+        {
+            return state.layers();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectAlreadyExistsException.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectAlreadyExistsException.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectAlreadyExistsException.java
new file mode 100644
index 0000000..4c798c0
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectAlreadyExistsException.java
@@ -0,0 +1,17 @@
+package org.qi4j.tools.shell.model;
+
+public class ProjectAlreadyExistsException extends Exception
+{
+    private final String name;
+
+    public ProjectAlreadyExistsException( String name )
+    {
+        super( "Project " + Project.Support.identity( name ) + " already exists." );
+        this.name = name;
+    }
+
+    public String name()
+    {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectDescriptorByProperties.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectDescriptorByProperties.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectDescriptorByProperties.java
new file mode 100644
index 0000000..5a69452
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/ProjectDescriptorByProperties.java
@@ -0,0 +1,86 @@
+package org.qi4j.tools.shell.model;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.qi4j.api.injection.scope.Service;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.unitofwork.UnitOfWorkFactory;
+import org.qi4j.api.unitofwork.concern.UnitOfWorkPropagation;
+import org.qi4j.tools.shell.FileUtils;
+
+public interface ProjectDescriptorByProperties
+{
+    void parse( String projectName, File propertiesFile );
+
+    Project findProject( String projectName );
+
+    public class Mixin
+        implements ProjectDescriptorByProperties
+    {
+        @Service
+        private Model model;
+
+        @Structure
+        private UnitOfWorkFactory uowf;
+
+        public void parse( String projectName, File file )
+        {
+            Map<String, String> props = FileUtils.readPropertiesFile( file );
+            Project project = findProject( projectName );
+
+            int layers = Integer.parseInt( props.get( "layers" ) );
+            for( int i = 0; i < layers; i++ )
+            {
+                String layerName = props.get( "layer." + i + ".name" );
+                project.createLayer( layerName );
+                Layer layer = project.findLayer( layerName );
+
+                for( String use : extractUses( props, i ) )
+                {
+                    layer.usesLayer( use );
+                }
+
+                int modules = Integer.parseInt( props.get( "layer." + i + ".modules" ) );
+                for( int j = 0; j < modules; j++ )
+                {
+                    String moduleName = props.get( "layer." + i + ".module." + j + ".name" );
+                    layer.createModule( moduleName );
+                }
+            }
+        }
+
+        private List<String> extractUses( Map<String, String> props, int i )
+        {
+            String uses = props.get( "layer." + i + ".uses" );
+            if( uses == null )
+            {
+                return Collections.emptyList();
+            }
+            return Arrays.asList( uses.split( "," ) );
+        }
+
+        @UnitOfWorkPropagation
+        public Project findProject( String projectName )
+        {
+            UnitOfWork uow = uowf.currentUnitOfWork();
+            Project project = Project.Support.get( uow, projectName );
+            if( project == null )
+            {
+                try
+                {
+                    model.createProject( projectName );
+                }
+                catch( ProjectAlreadyExistsException e )
+                {
+                    // Can not happen
+                    e.printStackTrace();
+                }
+            }
+            return project;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/TemplateAlreadyExistsException.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/TemplateAlreadyExistsException.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/TemplateAlreadyExistsException.java
new file mode 100644
index 0000000..0d1edd7
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/TemplateAlreadyExistsException.java
@@ -0,0 +1,16 @@
+package org.qi4j.tools.shell.model;
+
+public class TemplateAlreadyExistsException extends Exception
+{
+    private final String name;
+
+    public TemplateAlreadyExistsException( String name )
+    {
+        this.name = name;
+    }
+
+    public String name()
+    {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/model/generation/AssemblerGenerator.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/model/generation/AssemblerGenerator.java b/tools/shell/src/main/java/org/qi4j/tools/shell/model/generation/AssemblerGenerator.java
new file mode 100644
index 0000000..2d6c187
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/model/generation/AssemblerGenerator.java
@@ -0,0 +1,92 @@
+package org.qi4j.tools.shell.model.generation;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.tools.shell.FileUtils;
+import org.qi4j.tools.shell.StringUtils;
+import org.qi4j.tools.shell.model.AssemblerModel;
+import org.qi4j.tools.shell.model.Layer;
+import org.qi4j.tools.shell.model.Project;
+
+public abstract class AssemblerGenerator
+    implements AssemblerModel
+{
+    @This
+    private AssemblerModel self;
+
+    @Override
+    public void generate( Project project, File projectDir )
+        throws IOException
+    {
+        File bootDir = self.mainJavaRootPackageDirectory( projectDir );
+        File topAssembler = new File( bootDir, project.applicationName() + "Assembler.java" );
+
+        Map<String, String> variables = new HashMap<>();
+        variables.put( "APPLICATION_NAME", project.applicationName() );
+        variables.put( "IMPORTS", createImports() );
+        variables.put( "LAYER_CREATION", createLayerCreation( project ) );
+        variables.put( "LAYER_CREATE_METHODS", createLayerMethods( project ) );
+        variables.put( "ROOT_PACKAGE", project.rootPackageName() );
+        variables.put( "MAIN_JAVA_ROOT_PACKAGE_PATH", bootDir.getAbsolutePath() );
+        variables.put( "MAIN_RESOURCES_ROOT_PACKAGE_PATH",
+                       self.mainResourcesRootPackageDirectory( projectDir ).getAbsolutePath() );
+        variables.put( "TEST_JAVA_ROOT_PACKAGE_PATH",
+                       self.testJavaRootPackageDirectory( projectDir ).getAbsolutePath() );
+        variables.put( "TEST_RESOURCES_ROOT_PACKAGE_PATH",
+                       self.testResourcesRootPackageDirectory( projectDir ).getAbsolutePath() );
+        String application = project.template().evaluate( variables );
+        FileUtils.writeFile( topAssembler, application );
+    }
+
+    private String createLayerCreation( Project project )
+    {
+        StringBuilder builder = new StringBuilder();
+        for( Layer layer : project.layers() )
+        {
+            builder.append( "        " );
+            builder.append( "LayerAssembly " );
+            builder.append( StringUtils.camelCase( layer.name(), false ) );
+            builder.append( "Assembler = new " );
+            builder.append( StringUtils.camelCase( layer.name(), true ) );
+            builder.append( "().assemble( assembly );" );
+            builder.append( "\n" );
+        }
+        builder.append( "\n" );
+
+        for( Layer layer : project.layers() )
+        {
+            for( String use : layer.uses() )
+            {
+                builder.append( "        " );
+                builder.append( StringUtils.camelCase( layer.name(), false ) );
+                builder.append( ".uses( " );
+                builder.append( StringUtils.camelCase( use, false ) );
+                builder.append( ");\n" );
+            }
+        }
+        return builder.toString();
+    }
+
+    private String createLayerMethods( Project project )
+    {
+        StringBuilder builder = new StringBuilder();
+        for( Layer layer : project.layers() )
+        {
+            builder.append( "\n    private LayerAssembly create" );
+            builder.append( StringUtils.camelCase( layer.name(), true ) );
+            builder.append( "( ApplicationAssembly assembly )\n    {\n" );
+            builder.append( "        LayerAssembly layer = assembly.layer( \"" + layer.name() + "\" );\n" );
+            builder.append( "        return layer;\n" );
+            builder.append( "    }\n" );
+        }
+        return builder.toString();
+    }
+
+    private String createImports()
+    {
+        return "";
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/main/java/org/qi4j/tools/shell/templating/TemplateEngine.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/main/java/org/qi4j/tools/shell/templating/TemplateEngine.java b/tools/shell/src/main/java/org/qi4j/tools/shell/templating/TemplateEngine.java
new file mode 100644
index 0000000..c3fbf8d
--- /dev/null
+++ b/tools/shell/src/main/java/org/qi4j/tools/shell/templating/TemplateEngine.java
@@ -0,0 +1,40 @@
+package org.qi4j.tools.shell.templating;
+
+import java.util.Map;
+
+public class TemplateEngine
+{
+    private final String template;
+
+    public TemplateEngine( String template )
+    {
+        this.template = template;
+    }
+
+    public String create(Map<String, String> variables)
+    {
+        StringBuilder builder = new StringBuilder( template.length() * 2 );
+        for( int i = 0; i < template.length() - 1; i++ )
+        {
+            char ch1 = template.charAt( i );
+            char ch2 = template.charAt( i + 1 );
+            if( ch1 == '@' && ch2 == '@' )
+            {
+                i = replace( i + 2, builder, variables );
+            }
+            else
+            {
+                builder.append( ch1 );
+            }
+        }
+        return builder.toString();
+    }
+
+    private int replace( int current, StringBuilder builder, Map<String, String> variables )
+    {
+        int pos = template.indexOf( '@', current );
+        String name = template.substring( current, pos );
+        builder.append( variables.get( name ) );
+        return pos + 1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/28e0817d/tools/shell/src/test/java/org/qi4j/tools/shell/templating/TemplatingEngineTest.java
----------------------------------------------------------------------
diff --git a/tools/shell/src/test/java/org/qi4j/tools/shell/templating/TemplatingEngineTest.java b/tools/shell/src/test/java/org/qi4j/tools/shell/templating/TemplatingEngineTest.java
new file mode 100644
index 0000000..fcfb675
--- /dev/null
+++ b/tools/shell/src/test/java/org/qi4j/tools/shell/templating/TemplatingEngineTest.java
@@ -0,0 +1,40 @@
+package org.qi4j.tools.shell.templating;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+public class TemplatingEngineTest
+{
+    @Test
+    public void givenCorrectTemplateWhenGeneratingExpectGoodResult()
+        throws Exception
+    {
+        Map<String, String> variables = new HashMap<>();
+        variables.put( "REPLACE", "def" );
+        TemplateEngine engine = new TemplateEngine( TEMPLATE1 );
+        String result = engine.create( variables );
+        assertThat( result, equalTo( "abcdefghi" ) );
+    }
+
+    @Test
+    public void givenCorrectTemplateWhenGeneratingMultipleOutputsExpectGoodResult()
+        throws Exception
+    {
+        TemplateEngine engine = new TemplateEngine( TEMPLATE1 );
+
+        Map<String, String> variables = new HashMap<>();
+        variables.put( "REPLACE", "def" );
+        String result = engine.create( variables );
+        assertThat( result, equalTo( "abcdefghi" ) );
+
+        variables.put( "REPLACE", "rst" );
+        result = engine.create( variables );
+        assertThat( result, equalTo( "abcrstghi" ) );
+    }
+
+    private static final String TEMPLATE1 = "abc@@REPLACE@@ghi";
+}


Mime
View raw message