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
28
29
30
31 public class AndroMDAppType
32 {
33
34
35
36 private final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
37
38
39
40
41 private static final String NAMESPACE = "andromdapp";
42
43
44
45
46 private static final String TEMPORARY_MERGE_LOCATION = Constants.TEMPORARY_DIRECTORY + '/' + NAMESPACE;
47
48
49
50
51
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
67
68
69
70
71
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
89 if (Conditions.TYPE_AND.equals(conditionsType))
90 {
91 if (!validPreconditions)
92 {
93 break;
94 }
95 }
96 else
97 {
98
99 if (validPreconditions)
100 {
101 break;
102 }
103 }
104 }
105 }
106
107 if (validPreconditions)
108 {
109 Object response = this.templateContext.get(id);
110
111
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
141
142
143
144
145
146 private String promptForInput(final Prompt prompt)
147 {
148 this.printPromptText(prompt.getText());
149 return this.readLine();
150 }
151
152
153
154
155
156
157
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
180
181
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
194
195 private String templateEngineClass;
196
197
198
199
200
201
202
203 public void setTemplateEngineClass(final String templateEngineClass)
204 {
205 this.templateEngineClass = templateEngineClass;
206 }
207
208
209
210
211 private TemplateEngine templateEngine = null;
212
213
214
215
216
217
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
236
237 private final Map<String, String[]> templateEngineExclusions = new LinkedHashMap<String, String[]>();
238
239
240
241
242
243
244
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
257
258
259
260 final Map<String, String[]> getTemplateEngineExclusions()
261 {
262 return this.templateEngineExclusions;
263 }
264
265
266
267
268 private static final String RESPONSE_YES = "yes";
269
270
271
272
273 private static final String RESPONSE_NO = "no";
274
275
276
277
278 private static final String MARGIN = " ";
279
280
281
282
283 private static final String FORWARD_SLASH = "/";
284
285
286
287
288
289
290
291
292 protected List<File> processResources(final boolean write)
293 throws Exception
294 {
295
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
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
342
343
344
345
346 ResourceWriter.instance().writeUrlToFile(
347 absolutePath,
348
349 ResourceUtils.normalizePath(TEMPORARY_MERGE_LOCATION + '/' + newPath));
350 newContents.add(newPath);
351 hasNewPath = true;
352
353
354
355
356
357 }
358 }
359 }
360 if (!hasNewPath)
361 {
362 newContents.add(path);
363 }
364 }
365 }
366 }
367 }
368 }
369
370
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
407
408 this.printText(MARGIN + "Output: '" + outputFile.toURI().toURL() + '\'');
409 ResourceWriter.instance().writeStringToFile(
410 writer.toString(),
411 outputFile);
412
413
414
415
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
427 URL resource =
428 ResourceUtils.toURL(ResourceUtils.normalizePath(TEMPORARY_MERGE_LOCATION + '/' + path));
429 if (resource == null)
430 {
431
432 resource = ClassUtils.getClassLoader().getResource(path);
433 }
434 if (resource != null)
435 {
436
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
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
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
492
493
494
495
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
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
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
541 break;
542 }
543 else if (Conditions.TYPE_OR.equals(conditionsType) && result)
544 {
545
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
563 if (writable == null)
564 {
565 writable = Boolean.TRUE;
566 }
567 return writable.booleanValue();
568 }
569
570
571
572
573
574
575
576
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
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
619
620
621
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
645
646 private void printLine()
647 {
648 this.printText("-------------------------------------------------------------------------------------");
649 }
650
651
652
653
654
655
656
657
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
689
690
691
692
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
713
714
715
716 private void printPromptText(final String text)
717 {
718 System.out.println();
719 this.printText(text);
720 }
721
722
723
724
725
726
727 private void printText(final String text)
728 {
729 System.out.println(text);
730 System.out.flush();
731 }
732
733
734
735
736
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
755
756 private String type;
757
758
759
760
761
762
763 public String getType()
764 {
765 return this.type;
766 }
767
768
769
770
771
772
773 public void setType(final String type)
774 {
775 this.type = type;
776 }
777
778
779
780
781 private String root;
782
783
784
785
786
787
788 public String getRoot()
789 {
790 return this.root;
791 }
792
793
794
795
796
797
798 public void setRoot(final String root)
799 {
800 this.root = root;
801 }
802
803
804
805
806 private List<Configuration> configurations;
807
808
809
810
811
812
813 final void setConfigurations(final List<Configuration> configurations)
814 {
815 this.configurations = configurations;
816 }
817
818
819
820
821 private final List<Prompt> prompts = new ArrayList<Prompt>();
822
823
824
825
826
827
828
829 public void addPrompt(final Prompt prompt)
830 {
831 this.prompts.add(prompt);
832 }
833
834
835
836
837
838
839 public List<Prompt> getPrompts()
840 {
841 return this.prompts;
842 }
843
844
845
846
847 private final List<String> resourceLocations = new ArrayList<String>();
848
849
850
851
852
853
854 public void addResourceLocation(final String resourceLocation)
855 {
856 this.resourceLocations.add(resourceLocation);
857 }
858
859
860
861
862
863 private final List<String> directories = new ArrayList<String>();
864
865
866
867
868
869
870 public void addDirectory(final String directory)
871 {
872 this.directories.add(directory);
873 }
874
875
876
877
878
879 private final List<Conditions> outputConditions = new ArrayList<Conditions>();
880
881
882
883
884
885
886 public void addOutputConditions(final Conditions outputConditions)
887 {
888 this.outputConditions.add(outputConditions);
889 }
890
891
892
893
894
895 private String[] templateExtensions;
896
897
898
899
900 public void setTemplateExtensions(final String templateExtensions)
901 {
902 this.templateExtensions = AndroMDAppUtils.stringToArray(templateExtensions);
903 }
904
905
906
907
908
909 private String instructions;
910
911
912
913
914
915
916 public void setInstructions(final String instructions)
917 {
918 this.instructions = instructions;
919 }
920
921
922
923
924 public String toString()
925 {
926 return super.toString() + '[' + this.getType() + ']';
927 }
928
929
930
931
932 private URL resource;
933
934
935
936
937
938
939 final void setResource(final URL resource)
940 {
941 this.resource = resource;
942 }
943
944
945
946
947
948
949 final URL getResource()
950 {
951 return this.resource;
952 }
953
954
955
956
957 private final List<Mapping> mappings = new ArrayList<Mapping>();
958
959
960
961
962
963
964 public void addMapping(final Mapping mapping)
965 {
966 this.mappings.add(mapping);
967 }
968
969
970
971
972
973
974 final void addToTemplateContext(final Map map)
975 {
976 this.templateContext.putAll(map);
977 }
978
979
980
981
982
983
984 final Map getTemplateContext()
985 {
986 return this.templateContext;
987 }
988
989
990
991
992
993
994
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 }