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 }