Getting a Qi4j project creation tool working.
diff --git a/build.gradle b/build.gradle
index 20f215e..575d6c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -558,8 +558,15 @@
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
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 @@
// 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
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 )
}
+
+
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>
diff --git a/tools/shell/src/bin/qi4j b/tools/shell/src/main/dist/bin/qi4j
similarity index 100%
rename from tools/shell/src/bin/qi4j
rename to tools/shell/src/main/dist/bin/qi4j
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
+
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@@
+ }
+}
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@@
+}
diff --git a/tools/shell/src/etc/templates/defaultproject/project.properties b/tools/shell/src/main/dist/etc/project-templates/simple/src/main/Main.java
similarity index 100%
rename from tools/shell/src/etc/templates/defaultproject/project.properties
rename to 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/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
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();
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 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 @@
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();
+ }
+ }
}
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 )
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();
+ }
+}
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";
- }
-}
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";
+ }
+ }
+}
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";
+ }
+ }
+}
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 @@
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";
+ }
}
}
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 );
+ }
+ }
+}
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();
+ }
+ }
+}
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 );
+ }
+ }
+}
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 );
+ }
+ }
+}
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;
+ }
+ }
+}
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();
+ }
+ }
+}
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();
+ }
+ }
+}
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;
+ }
+}
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;
+ }
+ }
+}
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;
+ }
+}
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 "";
+ }
+}
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;
+ }
+}
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";
+}