View Javadoc
1   package org.andromda.andromdapp;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.IOException;
6   import java.io.InputStreamReader;
7   import java.io.StringWriter;
8   import java.net.URL;
9   import java.util.ArrayList;
10  import java.util.Collection;
11  import java.util.LinkedHashMap;
12  import java.util.LinkedHashSet;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Set;
16  import org.andromda.core.common.ClassUtils;
17  import org.andromda.core.common.ComponentContainer;
18  import org.andromda.core.common.Constants;
19  import org.andromda.core.common.ResourceFinder;
20  import org.andromda.core.common.ResourceUtils;
21  import org.andromda.core.common.ResourceWriter;
22  import org.andromda.core.templateengine.TemplateEngine;
23  import org.apache.commons.lang.ObjectUtils;
24  import org.apache.commons.lang.StringUtils;
25  
26  /**
27   * Represents an AndroMDApp type (j2ee, .net, etc).
28   *
29   * @author Chad Brandon
30   */
31  public class AndroMDAppType
32  {
33      /**
34       * The velocity template context.
35       */
36      private final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
37  
38      /**
39       * The namespace used to initialize the template engine.
40       */
41      private static final String NAMESPACE = "andromdapp";
42  
43      /**
44       * The location to which temporary merge location files are written.
45       */
46      private static final String TEMPORARY_MERGE_LOCATION = Constants.TEMPORARY_DIRECTORY + '/' + NAMESPACE;
47  
48      /**
49       * Performs any required initialization for the type.
50       *
51       * @throws Exception
52       */
53      protected void initialize()
54          throws Exception
55      {
56          if (this.configurations != null)
57          {
58              for (final Configuration configuration : this.configurations)
59              {
60                  this.templateContext.putAll(configuration.getAllProperties());
61              }
62          }
63      }
64  
65      /**
66       * Prompts the user for the input required to generate an application with
67       * the correct information and returns the descriptor contents after being interpolated by all
68       * properties in the template context.
69       *
70       * @return the results of the interpolated descriptor.
71       * @throws Exception
72       */
73      protected String promptUser()
74          throws Exception
75      {
76          for (final Prompt prompt : this.getPrompts())
77          {
78              final String id = prompt.getId();
79  
80              boolean validPreconditions = true;
81              for (final Conditions preconditions : prompt.getPreconditions())
82              {
83                  final String conditionsType = preconditions.getType();
84                  for (final Condition precondition : preconditions.getConditions())
85                  {
86                      validPreconditions = precondition.evaluate(this.templateContext.get(precondition.getId()));
87  
88                      // - if we're 'anding' the conditions, we break at the first false
89                      if (Conditions.TYPE_AND.equals(conditionsType))
90                      {
91                          if (!validPreconditions)
92                          {
93                              break;
94                          }
95                      }
96                      else
97                      {
98                          // otherwise we break at the first true condition
99                          if (validPreconditions)
100                         {
101                             break;
102                         }
103                     }
104                 }
105             }
106 
107             if (validPreconditions)
108             {
109                 Object response = this.templateContext.get(id);
110 
111                 // - only prompt when the id isn't already in the context
112                 if (response == null)
113                 {
114                     do
115                     {
116                         response = this.promptForInput(prompt);
117                     }
118                     while (!prompt.isValidResponse(ObjectUtils.toString(response)));
119                 }
120                 this.setConditionalProperties(
121                     prompt.getConditions(),
122                     response);
123                 if (prompt.isSetResponseAsTrue())
124                 {
125                     this.templateContext.put(
126                         response.toString(),
127                         Boolean.TRUE);
128                 }
129                 this.templateContext.put(
130                     id,
131                     prompt.getResponse(response));
132             }
133         }
134         return this.getTemplateEngine().getEvaluatedExpression(
135             ResourceUtils.getContents(this.resource),
136             this.templateContext);
137     }
138 
139     /**
140      * Prompts the user for the information contained in the given
141      * <code>prompt</code>.
142      *
143      * @param prompt the prompt from which to format the prompt text.
144      * @return the response of the prompt.
145      */
146     private String promptForInput(final Prompt prompt)
147     {
148         this.printPromptText(prompt.getText());
149         return this.readLine();
150     }
151 
152     /**
153      * Prompts the user for the information contained in the given
154      * <code>prompt</code>.
155      *
156      * @param conditions the prompt from which to format the prompt text.
157      * @param value
158      */
159     private void setConditionalProperties(
160         final List<Condition> conditions,
161         final Object value)
162     {
163         for (final Condition condition : conditions)
164         {
165             final String equalCondition = condition.getEqual();
166             if (equalCondition != null && equalCondition.equals(value))
167             {
168                 this.setProperties(condition);
169             }
170             final String notEqualCondition = condition.getNotEqual();
171             if (notEqualCondition != null && !notEqualCondition.equals(value))
172             {
173                 this.setProperties(condition);
174             }
175         }
176     }
177 
178     /**
179      * Sets the prompt values from the given <code>condition</code>.
180      *
181      * @param condition the condition from which to populate the values.
182      */
183     private void setProperties(final Condition condition)
184     {
185         if (condition != null)
186         {
187             final Map<String, Object> values = condition.getProperties();
188             this.templateContext.putAll(values);
189         }
190     }
191 
192     /**
193      * The template engine class.
194      */
195     private String templateEngineClass;
196 
197     /**
198      * Sets the class of the template engine to use.
199      *
200      * @param templateEngineClass the Class of the template engine
201      *        implementation.
202      */
203     public void setTemplateEngineClass(final String templateEngineClass)
204     {
205         this.templateEngineClass = templateEngineClass;
206     }
207 
208     /**
209      * The template engine that this plugin will use.
210      */
211     private TemplateEngine templateEngine = null;
212 
213     /**
214      * Gets the template that that will process the templates.
215      *
216      * @return the template engine instance.
217      * @throws Exception
218      */
219     private TemplateEngine getTemplateEngine()
220         throws Exception
221     {
222         if (this.templateEngine == null)
223         {
224             this.templateEngine =
225                 (TemplateEngine)ComponentContainer.instance().newComponent(
226                     this.templateEngineClass,
227                     TemplateEngine.class);
228             this.getTemplateEngine().setMergeLocation(TEMPORARY_MERGE_LOCATION);
229             this.getTemplateEngine().initialize(NAMESPACE);
230         }
231         return this.templateEngine;
232     }
233 
234     /**
235      * Stores the template engine exclusions.
236      */
237     private final Map<String, String[]> templateEngineExclusions = new LinkedHashMap<String, String[]>();
238 
239     /**
240      * Adds a template engine exclusion (these are the things that the template engine
241      * will exclude when processing templates)
242      *
243      * @param path the path to the resulting output
244      * @param patterns any patterns to which the conditions should apply
245      */
246     public void addTemplateEngineExclusion(
247         final String path,
248         final String patterns)
249     {
250         this.templateEngineExclusions.put(
251             path,
252             AndroMDAppUtils.stringToArray(patterns));
253     }
254 
255     /**
256      * Gets the template engine exclusions.
257      *
258      * @return the map of template engine exclusion paths and its patterns (if it has any defined).
259      */
260     final Map<String, String[]> getTemplateEngineExclusions()
261     {
262         return this.templateEngineExclusions;
263     }
264 
265     /**
266      * The 'yes' response.
267      */
268     private static final String RESPONSE_YES = "yes";
269 
270     /**
271      * The 'no' response.
272      */
273     private static final String RESPONSE_NO = "no";
274 
275     /**
276      * A margin consisting of some whitespace.
277      */
278     private static final String MARGIN = "    ";
279 
280     /**
281      * Stores the forward slash character.
282      */
283     private static final String FORWARD_SLASH = "/";
284 
285     /**
286      * Processes the files for the project.
287      *
288      * @param write whether or not the resources should be written when collected.
289      * @return processedResources
290      * @throws Exception
291      */
292     protected List<File> processResources(final boolean write)
293         throws Exception
294     {
295         // - all resources that have been processed.
296         final List<File> processedResources = new ArrayList<File>();
297         final File rootDirectory = this.verifyRootDirectory(new File(this.getRoot()));
298         final String bannerStart = write ? "G e n e r a t i n g" : "R e m o v i n g";
299         this.printLine();
300         this.printText(MARGIN + bannerStart + "   A n d r o M D A   P o w e r e d   A p p l i c a t i o n");
301         this.printLine();
302         rootDirectory.mkdirs();
303 
304         final Map<String, Set<String>> locations = new LinkedHashMap<String, Set<String>>();
305 
306         // - first write any mapped resources
307         for (final String location : this.resourceLocations)
308         {
309             final URL[] resourceDirectories = ResourceFinder.findResources(location);
310             if (resourceDirectories != null)
311             {
312                 final int numberOfResourceDirectories = resourceDirectories.length;
313                 for (int ctr = 0; ctr < numberOfResourceDirectories; ctr++)
314                 {
315                     final URL resourceDirectory = resourceDirectories[ctr];
316                     final List<String> contents = ResourceUtils.getDirectoryContents(
317                             resourceDirectory,
318                             false,
319                             null);
320                     final Set<String> newContents = new LinkedHashSet<String>();
321                     locations.put(
322                             location,
323                             newContents);
324                     for (final String path : contents)
325                     {
326                         if (path != null && !path.endsWith(FORWARD_SLASH))
327                         {
328                             boolean hasNewPath = false;
329                             for (final Mapping mapping : this.mappings)
330                             {
331                                 String newPath = mapping.getMatch(path);
332                                 if (StringUtils.isNotBlank(newPath))
333                                 {
334                                     final URL absolutePath = ResourceUtils.getResource(path);
335                                     if (absolutePath != null)
336                                     {
337                                         newPath =
338                                                 this.getTemplateEngine().getEvaluatedExpression(
339                                                         newPath,
340                                                         this.templateContext);
341                                         /*newPath = ResourceUtils.normalizePath(TEMPORARY_MERGE_LOCATION + '/' + newPath);
342                                         File outputFile = new File(newPath);
343                                         if (this.isOverwrite() || !outputFile.exists())
344                                         {
345                                             this.printText(MARGIN + "Output: '" + outputFile.toURI().toURL() + '\'');*/
346                                             ResourceWriter.instance().writeUrlToFile(
347                                                 absolutePath,
348                                                 //newPath);
349                                                 ResourceUtils.normalizePath(TEMPORARY_MERGE_LOCATION + '/' + newPath));
350                                             newContents.add(newPath);
351                                             hasNewPath = true;
352                                         /*}
353                                         else
354                                         {
355                                             this.printText(MARGIN + "Not overwritten: '" + outputFile.toURI().toURL() + '\'');
356                                         }*/
357                                     }
358                                 }
359                             }
360                             if (!hasNewPath)
361                             {
362                                 newContents.add(path);
363                             }
364                         }
365                     }
366                 }
367             }
368         }
369 
370         // - second process and write any output from the defined resource locations.
371         for (final String location : locations.keySet())
372         {
373             final Collection<String> contents = locations.get(location);
374             if (contents != null)
375             {
376                 for (final String path : contents)
377                 {
378                     final String projectRelativePath = StringUtils.replace(
379                             path,
380                             location,
381                             "");
382                     if (this.isWriteable(projectRelativePath))
383                     {
384                         if (this.isValidTemplate(path))
385                         {
386                             final File outputFile =
387                                 new File(
388                                     rootDirectory.getAbsolutePath(),
389                                     this.trimTemplateExtension(projectRelativePath));
390                             if (write)
391                             {
392                                 final StringWriter writer = new StringWriter();
393                                 try
394                                 {
395                                     this.getTemplateEngine().processTemplate(
396                                         path,
397                                         this.templateContext,
398                                         writer);
399                                 }
400                                 catch (final Throwable throwable)
401                                 {
402                                     throw new AndroMDAppException("An error occurred while processing template --> '" +
403                                         path + "' with template context '" + this.templateContext + '\'', throwable);
404                                 }
405                                 writer.flush();
406                                 //if (this.isOverwrite() || !outputFile.exists())
407                                 //{
408                                     this.printText(MARGIN + "Output: '" + outputFile.toURI().toURL() + '\'');
409                                     ResourceWriter.instance().writeStringToFile(
410                                         writer.toString(),
411                                         outputFile);
412                                 /*}
413                                 else
414                                 {
415                                     this.printText(MARGIN + "Not overwritten: '" + outputFile.toURI().toURL() + '\'');
416                                 }*/
417                             }
418                             processedResources.add(outputFile);
419                         }
420                         else if (!path.endsWith(FORWARD_SLASH))
421                         {
422                             final File outputFile = new File(
423                                     rootDirectory.getAbsolutePath(),
424                                     projectRelativePath);
425 
426                             // - try the template engine merge location first
427                             URL resource =
428                                 ResourceUtils.toURL(ResourceUtils.normalizePath(TEMPORARY_MERGE_LOCATION + '/' + path));
429                             if (resource == null)
430                             {
431                                 // - if we didn't find the file in the merge location, try the classpath
432                                 resource = ClassUtils.getClassLoader().getResource(path);
433                             }
434                             if (resource != null)
435                             {
436                                 //if (write && (this.isOverwrite() || !outputFile.exists()))
437                                 if (write)
438                                 {
439                                     ResourceWriter.instance().writeUrlToFile(
440                                         resource,
441                                         outputFile.toString());
442                                     this.printText(MARGIN + "Output: '" + outputFile.toURI().toURL() + '\'');
443                                 }
444                                 else
445                                 {
446                                     this.printText(MARGIN + "Not overwritten: '" + outputFile.toURI().toURL() + '\'');
447                                 }
448                                 processedResources.add(outputFile);
449                             }
450                         }
451                     }
452                 }
453             }
454         }
455 
456         // - write any directories that are defined.
457         for (final String directoryPath : this.directories)
458         {
459             final File directory = new File(rootDirectory, directoryPath);
460             if (this.isWriteable(directoryPath))
461             {
462                 directory.mkdirs();
463                 this.printText(MARGIN + "Output: '" + directory.toURI().toURL() + '\'');
464             }
465         }
466 
467         if (write)
468         {
469             // - write the "instructions can be found" information
470             this.printLine();
471             this.printText(MARGIN + "New application generated to --> '" + rootDirectory.toURI().toURL() + '\'');
472             if (StringUtils.isNotBlank(this.instructions))
473             {
474                 File instructions = new File(
475                         rootDirectory.getAbsolutePath(),
476                         this.instructions);
477                 if (!instructions.exists())
478                 {
479                     throw new AndroMDAppException("No instructions are available at --> '" + instructions +
480                         "', please make sure you have the correct instructions defined in your descriptor --> '" +
481                         this.resource + '\'');
482                 }
483                 this.printText(MARGIN + "Instructions for your new application --> '" + instructions.toURI().toURL() + '\'');
484             }
485             this.printLine();
486         }
487         return processedResources;
488     }
489 
490     /**
491      * Indicates whether or not this path is <em>writable</em>
492      * based on the path and any output conditions that may be specified.
493      *
494      * @param path the path to check.
495      * @return true/false
496      */
497     private boolean isWriteable(String path)
498     {
499         path = path.replaceAll(
500                 "\\\\+",
501                 FORWARD_SLASH);
502         if (path.startsWith(FORWARD_SLASH))
503         {
504             path = path.substring(
505                     1,
506                     path.length());
507         }
508 
509         Boolean writable = null;
510 
511         final Map<String, Boolean> evaluatedPaths = new LinkedHashMap<String, Boolean>();
512         for (final Conditions conditions : this.outputConditions)
513         {
514             final Map<String, String[]> outputPaths = conditions.getOutputPaths();
515             final String conditionsType = conditions.getType();
516 
517             for (final String outputPath : outputPaths.keySet())
518             {
519                 // - only evaluate if we haven't yet evaluated
520                 writable = evaluatedPaths.get(path);
521                 if (writable == null)
522                 {
523                     if (path.startsWith(outputPath))
524                     {
525                         final String[] patterns = outputPaths.get(outputPath);
526                         if (ResourceUtils.matchesAtLeastOnePattern(
527                                 path,
528                                 patterns))
529                         {
530                             // - assume writable is false, since the path matches at least one conditions path.
531                             for (final Condition condition : conditions.getConditions())
532                             {
533                                 final String id = condition.getId();
534                                 if (StringUtils.isNotBlank(id))
535                                 {
536                                     final boolean result = condition.evaluate(this.templateContext.get(id));
537                                     writable = Boolean.valueOf(result);
538                                     if (Conditions.TYPE_AND.equals(conditionsType) && !result)
539                                     {
540                                         // - if we 'and' the conditions, we break at the first false
541                                         break;
542                                     }
543                                     else if (Conditions.TYPE_OR.equals(conditionsType) && result)
544                                     {
545                                         // - otherwise we break at the first true condition
546                                         break;
547                                     }
548                                 }
549                             }
550                         }
551                     }
552                     if (writable != null)
553                     {
554                         evaluatedPaths.put(
555                             path,
556                             writable);
557                     }
558                 }
559             }
560         }
561 
562         // - if writable is still null, set to true
563         if (writable == null)
564         {
565             writable = Boolean.TRUE;
566         }
567         return writable.booleanValue();
568     }
569 
570     /**
571      * Indicates whether or not the given <code>path</code> matches at least
572      * one of the file extensions stored in the {@link #templateExtensions}
573      * and isn't in the template engine exclusions.
574      *
575      * @param path the path to check.
576      * @return true/false
577      */
578     private boolean isValidTemplate(final String path)
579     {
580         boolean exclude = false;
581         final Map<String, String[]> exclusions = this.getTemplateEngineExclusions();
582         for (final String exclusionPath : (Iterable<String>) exclusions.keySet())
583         {
584             if (path.startsWith(exclusionPath))
585             {
586                 final String[] patterns = exclusions.get(exclusionPath);
587                 // See http://forum.andromda.org/viewtopic.php?f=20&t=4206&sid=87c343e5550f5386d6c64df53e9f5910
588                 exclude = ResourceUtils.matchesAtLeastOnePattern(
589                     exclusionPath,
590                     patterns);
591                 if (exclude)
592                 {
593                     break;
594                 }
595             }
596         }
597         boolean validTemplate = false;
598         if (!exclude)
599         {
600             if (this.templateExtensions != null)
601             {
602                 final int numberOfExtensions = this.templateExtensions.length;
603                 for (int ctr = 0; ctr < numberOfExtensions; ctr++)
604                 {
605                     final String extension = '.' + this.templateExtensions[ctr];
606                     validTemplate = path.endsWith(extension);
607                     if (validTemplate)
608                     {
609                         break;
610                     }
611                 }
612             }
613         }
614         return validTemplate;
615     }
616 
617     /**
618      * Trims the first template extension it encounters and returns.
619      *
620      * @param path the path of which to trim the extension.
621      * @return the trimmed path.
622      */
623     private String trimTemplateExtension(String path)
624     {
625         if (this.templateExtensions != null)
626         {
627             final int numberOfExtensions = this.templateExtensions.length;
628             for (int ctr = 0; ctr < numberOfExtensions; ctr++)
629             {
630                 final String extension = '.' + this.templateExtensions[ctr];
631                 if (path.endsWith(extension))
632                 {
633                     path = path.substring(
634                             0,
635                             path.length() - extension.length());
636                     break;
637                 }
638             }
639         }
640         return path;
641     }
642 
643     /**
644      * Prints a line separator.
645      */
646     private void printLine()
647     {
648         this.printText("-------------------------------------------------------------------------------------");
649     }
650 
651     /**
652      * Verifies that if the root directory already exists, the user is prompted
653      * to make sure its ok if we generate over it, otherwise the user can change
654      * his/her application directory.
655      *
656      * @param rootDirectory the root directory that will be verified.
657      * @return the appropriate root directory.
658      */
659     private File verifyRootDirectory(final File rootDirectory)
660     {
661         File applicationRoot = rootDirectory;
662         if (rootDirectory.exists() && !this.isOverwrite())
663         {
664             this.printPromptText(
665                 '\'' + rootDirectory.getAbsolutePath() +
666                     "' already exists, would you like to try a new name? [yes, no]: ");
667             String response = this.readLine();
668             while (!RESPONSE_YES.equals(response) && !RESPONSE_NO.equals(response))
669             {
670                 response = this.readLine();
671             }
672             if (RESPONSE_YES.equals(response))
673             {
674                 this.printPromptText("Please enter the name for your application root directory: ");
675                 String rootName;
676                 do
677                 {
678                     rootName = this.readLine();
679                 }
680                 while (StringUtils.isBlank(rootName));
681                 applicationRoot = this.verifyRootDirectory(new File(rootName));
682             }
683         }
684         return applicationRoot;
685     }
686 
687     /**
688      * Indicates whether or not this andromdapp type should overwrite any
689      * previous applications with the same name.  This returns true on the first
690      * configuration that has that flag set to true.
691      *
692      * @return true/false
693      */
694     private boolean isOverwrite()
695     {
696         boolean overwrite = false;
697         if (this.configurations != null)
698         {
699             for (final Configuration configuration : this.configurations)
700             {
701                 overwrite = configuration.isOverwrite();
702                 if (overwrite)
703                 {
704                     break;
705                 }
706             }
707         }
708         return overwrite;
709     }
710 
711     /**
712      * Prints text to the console.
713      *
714      * @param text the text to print to the console;
715      */
716     private void printPromptText(final String text)
717     {
718         System.out.println();  // NOPMD - have to print to console prompt
719         this.printText(text);
720     }
721 
722     /**
723      * Prints text to the console.
724      *
725      * @param text the text to print to the console;
726      */
727     private void printText(final String text)
728     {
729         System.out.println(text);  // NOPMD - have to print to console prompt
730         System.out.flush();
731     }
732 
733     /**
734      * Reads a line from standard input and returns the value.
735      *
736      * @return the value read from standard input.
737      */
738     private String readLine()
739     {
740         final BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
741         String inputString = null;
742         try
743         {
744             inputString = input.readLine();
745         }
746         catch (final IOException exception)
747         {
748             this.printText(MARGIN + "IOException reading line: '" + exception);
749         }
750         return StringUtils.trimToNull(inputString);
751     }
752 
753     /**
754      * The type of this AndroMDAppType (i.e. 'j2ee', '.net', etc).
755      */
756     private String type;
757 
758     /**
759      * Gets the type of this AndroMDAppType.
760      *
761      * @return Returns the type.
762      */
763     public String getType()
764     {
765         return this.type;
766     }
767 
768     /**
769      * Sets the type of this AndroMDAppType.
770      *
771      * @param type The type to set.
772      */
773     public void setType(final String type)
774     {
775         this.type = type;
776     }
777 
778     /**
779      * The root directory in which the application will be created.
780      */
781     private String root;
782 
783     /**
784      * Gets the application root directory name.
785      *
786      * @return Returns the root.
787      */
788     public String getRoot()
789     {
790         return this.root;
791     }
792 
793     /**
794      * Sets the application root directory name.
795      *
796      * @param root The root to set.
797      */
798     public void setRoot(final String root)
799     {
800         this.root = root;
801     }
802 
803     /**
804      * Stores any configuration information used when running this type.
805      */
806     private List<Configuration> configurations;
807 
808     /**
809      * Sets the configuration instance for this type.
810      *
811      * @param configurations the optional configuration instance.
812      */
813     final void setConfigurations(final List<Configuration> configurations)
814     {
815         this.configurations = configurations;
816     }
817 
818     /**
819      * Stores the available prompts for this andromdapp.
820      */
821     private final List<Prompt> prompts = new ArrayList<Prompt>();
822 
823     /**
824      * Adds a prompt to the collection of prompts contained within this
825      * instance.
826      *
827      * @param prompt the prompt to add.
828      */
829     public void addPrompt(final Prompt prompt)
830     {
831         this.prompts.add(prompt);
832     }
833 
834     /**
835      * Gets all available prompts.
836      *
837      * @return the list of prompts.
838      */
839     public List<Prompt> getPrompts()
840     {
841         return this.prompts;
842     }
843 
844     /**
845      * The locations where templates are stored.
846      */
847     private final List<String> resourceLocations = new ArrayList<String>();
848 
849     /**
850      * Adds a location where templates and or project files are located.
851      *
852      * @param resourceLocation the path to location.
853      */
854     public void addResourceLocation(final String resourceLocation)
855     {
856         this.resourceLocations.add(resourceLocation);
857     }
858 
859     /**
860      * The any empty directories that should be created when generating the
861      * application.
862      */
863     private final List<String> directories = new ArrayList<String>();
864 
865     /**
866      * The relative path to the directory to be created.
867      *
868      * @param directory the path to the directory.
869      */
870     public void addDirectory(final String directory)
871     {
872         this.directories.add(directory);
873     }
874 
875     /**
876      * Stores the output conditions (that is the conditions
877      * that must apply for the defined output to be written).
878      */
879     private final List<Conditions> outputConditions = new ArrayList<Conditions>();
880 
881     /**
882      * Adds an conditions element to the output conditions..
883      *
884      * @param outputConditions the output conditions to add.
885      */
886     public void addOutputConditions(final Conditions outputConditions)
887     {
888         this.outputConditions.add(outputConditions);
889     }
890 
891     /**
892      * Stores the patterns of the templates that the template engine should
893      * process.
894      */
895     private String[] templateExtensions;
896 
897     /**
898      * @param templateExtensions The templateExtensions to set.
899      */
900     public void setTemplateExtensions(final String templateExtensions)
901     {
902         this.templateExtensions = AndroMDAppUtils.stringToArray(templateExtensions);
903     }
904 
905     /**
906      * The path to the instructions on how to operation the build of the new
907      * application.
908      */
909     private String instructions;
910 
911     /**
912      * Sets the path to the instructions (i.e.could be a path to a readme file).
913      *
914      * @param instructions the path to the instructions.
915      */
916     public void setInstructions(final String instructions)
917     {
918         this.instructions = instructions;
919     }
920 
921     /**
922      * @see Object#toString()
923      */
924     public String toString()
925     {
926         return super.toString() + '[' + this.getType() + ']';
927     }
928 
929     /**
930      * The resource that configured this AndroMDAppType instance.
931      */
932     private URL resource;
933 
934     /**
935      * Sets the resource that configured this AndroMDAppType instance.
936      *
937      * @param resource the resource.
938      */
939     final void setResource(final URL resource)
940     {
941         this.resource = resource;
942     }
943 
944     /**
945      * Gets the resource that configured this instance.
946      *
947      * @return the resource.
948      */
949     final URL getResource()
950     {
951         return this.resource;
952     }
953 
954     /**
955      * Stores any of the mappings available to this type.
956      */
957     private final List<Mapping> mappings = new ArrayList<Mapping>();
958 
959     /**
960      * Adds a new mapping to this type.
961      *
962      * @param mapping the mapping which maps the new output paths.
963      */
964     public void addMapping(final Mapping mapping)
965     {
966         this.mappings.add(mapping);
967     }
968 
969     /**
970      * Adds the given map of properties to the current template context.
971      *
972      * @param map the map of properties.
973      */
974     final void addToTemplateContext(final Map map)
975     {
976         this.templateContext.putAll(map);
977     }
978 
979     /**
980      * Gets the current template context for this instance.
981      *
982      * @return the template context.
983      */
984     final Map getTemplateContext()
985     {
986         return this.templateContext;
987     }
988 
989     /**
990      * Instantiates the template object with the given <code>className</code> and adds
991      * it to the current template context.
992      *
993      * @param name the name of the template variable.
994      * @param className the name of the class to instantiate.
995      */
996     public void addTemplateObject(
997         final String name,
998         final String className)
999     {
1000         this.templateContext.put(
1001             name,
1002             ClassUtils.newInstance(className));
1003     }
1004 }