View Javadoc

1   package org.andromda.core.anttasks;
2   
3   import java.io.BufferedWriter;
4   import java.io.ByteArrayOutputStream;
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileNotFoundException;
8   import java.io.FileOutputStream;
9   import java.io.IOException;
10  import java.io.OutputStream;
11  import java.io.OutputStreamWriter;
12  import java.io.Writer;
13  import java.net.MalformedURLException;
14  import java.net.URL;
15  import java.util.ArrayList;
16  import java.util.Collection;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Properties;
20  
21  import org.andromda.cartridges.interfaces.IAndroMDACartridge;
22  import org.andromda.cartridges.interfaces.OutletDictionary;
23  import org.andromda.cartridges.interfaces.TemplateConfiguration;
24  import org.andromda.cartridges.mgmt.CartridgeDictionary;
25  import org.andromda.cartridges.mgmt.CartridgeFinder;
26  
27  import org.andromda.core.common.DbMappingTable;
28  import org.andromda.core.common.RepositoryFacade;
29  import org.andromda.core.common.RepositoryReadException;
30  import org.andromda.core.common.ScriptHelper;
31  import org.andromda.core.common.StringUtilsHelper;
32  
33  import org.apache.commons.collections.ExtendedProperties;
34  
35  import org.apache.tools.ant.BuildException;
36  import org.apache.tools.ant.DirectoryScanner;
37  import org.apache.tools.ant.Project;
38  import org.apache.tools.ant.taskdefs.MatchingTask;
39  
40  import org.apache.velocity.Template;
41  import org.apache.velocity.VelocityContext;
42  import org.apache.velocity.app.VelocityEngine;
43  import org.apache.velocity.runtime.RuntimeConstants;
44  import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
45  
46  /***
47   * This class represents the <code>&lt;andromda&gt;</code> custom task which can
48   * be called from an ant script. 
49   * 
50   * The &lt;andromda&gt; task facilitates Model Driven Architecture by enabling
51   * the generation of source code, configuration files, and other such artifacts
52   * from a UML model.
53   * 
54   * @author    <a href="http://www.mbohlen.de">Matthias Bohlen</a>
55   * @author    <A HREF="http://www.amowers.com">Anthony Mowers</A>
56   */
57  public class AndroMDAGenTask extends MatchingTask
58  {
59      private static final String DEFAULT_DBMAPPING_TABLE_CLASSNAME =
60          "org.andromda.core.dbmapping.DigesterDbMappingTable";
61  
62      /***
63       *  the base directory
64       */
65      private File baseDir = null;
66  
67      /***
68       *  check the last modified date on files. defaults to true
69       */
70      private boolean lastModifiedCheck = true;
71  
72      /***
73       *  the mappings from java data types to JDBC and SQL datatypes.
74       */
75      private DbMappingTable typeMappings = null;
76  
77      /***
78       *  the file to get the velocity properties file
79       */
80      private File velocityPropertiesFile = null;
81  
82      /***
83       *  the VelocityEngine instance to use
84       */
85      private VelocityEngine ve;
86  
87      /***
88       *  User properties that were specified by nested tags in the ant script.
89       */
90      private ArrayList userProperties = new ArrayList();
91  
92      private RepositoryConfiguration repositoryConfiguration = null;
93  
94      /***
95       * An optional URL to a model
96       */
97      private URL modelURL = null;
98  
99      /***
100      * Dictionary of defined outlets. An outlet is a symbolic alias name
101      * for a physical directory.
102      */
103     private OutletDictionary outletDictionary = new OutletDictionary();
104 
105     /***
106      * Temporary list of mappings from the &lt;outlet&gt; subtask.
107      * Will be transferred to the outletDictionary before execution starts.
108      */
109     private ArrayList outletMappingList = new ArrayList();
110 
111     /***
112      * Default properties for the Velocity scripting engine.
113      */
114     private Properties velocityProperties;
115 
116     /***
117      * Dictionary of installed cartridges, searchable by stereotype.
118      */
119     private CartridgeDictionary cartridgeDictionary;
120 
121     /***
122      * <p>
123      * Creates a new <code>AndroMDAGenTask</code> instance.
124      * </p>
125      */
126     public AndroMDAGenTask()
127     {
128     }
129 
130     public void setModelURL(URL modelURL)
131     {
132         this.modelURL = modelURL;
133     }
134 
135     /***
136      * Adds a mapping for a cartridge outlet to a physical directory.
137      * Example from a build.xml file:
138      * &lt;outlet cartridge="ejb" outlet="beans" dir="${my.beans.dir}" /&gt;
139      * 
140      * @param om the outlet mapping javabean supplied by Ant
141      */
142     public void addOutlet(OutletMapping om)
143     {
144         outletMappingList.add(om);
145     }
146 
147     /***
148      *  <p>
149      *
150      *  Sets the base directory from which the object model files are read. This
151      *  defaults to the base directory of the ant project if not provided.</p>
152      *
153      *@param  dir  a <code>File</code> with the path to the base directory
154      */
155     public void setBasedir(File dir)
156     {
157         baseDir = dir;
158     }
159 
160     /***
161      *  <p>
162      *
163      *  Reads the configuration file for mappings of Java types to JDBC and SQL
164      *  types.</p>
165      *
166      *@param  dbMappingConfig  XML file with type to database mappings
167      *@throws  BuildException  if the file is not accessible
168      */
169     public void setTypeMappings(File dbMappingConfig)
170     {
171         try
172         {
173             Class mappingClass =
174                 Class.forName(DEFAULT_DBMAPPING_TABLE_CLASSNAME);
175             typeMappings = (DbMappingTable) mappingClass.newInstance();
176 
177             typeMappings.read(dbMappingConfig);
178         }
179         catch (IllegalAccessException iae)
180         {
181             throw new BuildException(iae);
182         }
183         catch (ClassNotFoundException cnfe)
184         {
185             throw new BuildException(cnfe);
186         }
187         catch (RepositoryReadException rre)
188         {
189             throw new BuildException(rre);
190         }
191         catch (IOException ioe)
192         {
193             throw new BuildException(ioe);
194         }
195         catch (InstantiationException ie)
196         {
197             throw new BuildException(ie);
198         }
199     }
200 
201     /***
202      *  <p>
203      *
204      *  Allows people to set the path to the <code>velocity.properties</code> file.
205      *  </p> <p>
206      *
207      *  This file is found relative to the path where the JVM was run. For example,
208      *  if <code>build.sh</code> was executed in the <code>./build</code>
209      *  directory, then the path would be relative to this directory.</p> <p>
210      *
211      *
212      *@param  velocityPropertiesFile  a <code>File</code> with the path to the
213      *      velocity properties file
214      */
215     public void setVelocityPropertiesFile(File velocityPropertiesFile)
216     {
217         this.velocityPropertiesFile = velocityPropertiesFile;
218     }
219 
220     /***
221      *  <p>
222      *
223      *  Turns on/off last modified checking for generated files. If checking is
224      *  turned on, overwritable files are regenerated only when the model is newer
225      *  than the file to be generated. By default, it is on.</p>
226      *
227      *@param  lastmod  set the modified check, yes or no?
228      */
229     public void setLastModifiedCheck(boolean lastmod)
230     {
231         this.lastModifiedCheck = lastmod;
232     }
233 
234     /***
235      *  <p>
236      *
237      *  Add a user property specified as a nested tag in the ant build script.</p>
238      *
239      *@param  up  the UserProperty that ant already constructed for us
240      */
241     public void addUserProperty(UserProperty up)
242     {
243         userProperties.add(up);
244     }
245 
246     /***
247      *  <p>
248      *
249      *  Starts the generation of source code from an object model. 
250      * 
251      *  This is the main entry point of the application. It is called by ant whenever 
252      *  the surrounding task is executed (which could be multiple times).</p>
253      *
254      *@throws  BuildException  if something goes wrong
255      */
256     public void execute() throws BuildException
257     {
258         DirectoryScanner scanner;
259         String[] list;
260         String[] dirs;
261 
262         if (baseDir == null)
263         {
264             // We directly change the user variable, because it
265             // shouldn't lead to problems
266             baseDir = project.resolveFile(".");
267         }
268 
269         if (typeMappings == null)
270         {
271             // Not required for UML2MMbase.
272             // throw new BuildException("The typeMappings attribute of <andromda> has not been set - it is needed for class attribute to database column mapping.");
273         }
274 
275         initOutletDictionary();
276         initCartridges();
277         initVelocityPropertiesAndEngine();
278 
279         // log("Transforming into: " + destDir.getAbsolutePath(), Project.MSG_INFO);
280 
281         createRepository().createRepository().open();
282 
283         if (modelURL == null)
284         {
285             // find the files/directories
286             scanner = getDirectoryScanner(baseDir);
287 
288             // get a list of files to work on
289             list = scanner.getIncludedFiles();
290 			
291             if (list.length > 0)
292             {
293                 for (int i = 0; i < list.length; ++i)
294                 {
295                     URL modelURL = null;
296                     File inFile = new File(baseDir, list[i]);
297 
298                     try
299                     {
300                         modelURL = inFile.toURL();
301                         process(modelURL);
302                     }
303                     catch (MalformedURLException mfe)
304                     {
305                         throw new BuildException(
306                             "Malformed model file URL: " + modelURL);
307                     }
308                 }
309             }
310             else
311             {
312                 throw new BuildException("Couldn't find any input xmi.");
313             }
314         }
315         else
316         {
317             // get the model via URL
318             process(modelURL);
319         }
320 
321         createRepository().createRepository().close();
322     }
323 
324     /***
325      * Initializes the Velocity properties and the Velocity engine itself. Tells
326      * Velocity that the AndroMDA templates can be found using the classpath.
327      * 
328      * @see org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
329      * @throws BuildException
330      */
331     private void initVelocityPropertiesAndEngine() throws BuildException
332     {
333         ve = new VelocityEngine();
334 
335         boolean hasProperties = false;
336         velocityProperties = new Properties();
337 
338         if (velocityPropertiesFile == null)
339         {
340             // We directly change the user variable, because it
341             // shouldn't lead to problems
342             velocityPropertiesFile = new File("velocity.properties");
343         }
344 
345         FileInputStream fis = null;
346         try
347         {
348             // We have to reload the properties every time in the
349             // (unlikely?) case that another task has changed them.
350             fis = new FileInputStream(velocityPropertiesFile);
351             velocityProperties.load(fis);
352             hasProperties = true;
353         }
354         catch (FileNotFoundException fnfex)
355         {
356             // We ignore the exception and only complain later if we
357             // don't have a template path as well
358         }
359         catch (IOException ioex)
360         {
361             // We ignore the exception and only complain later if we
362             // don't have a template path as well
363         }
364         finally
365         {
366             if (null != fis)
367             {
368                 try
369                 {
370                     fis.close();
371                 }
372                 catch (IOException ioex)
373                 {
374                     // Not much that can be done
375                 }
376             }
377         }
378 
379         try
380         {
381             // Tell Velocity it should also use the classpath when searching for templates
382             ExtendedProperties ep =
383                 ExtendedProperties.convertProperties(velocityProperties);
384 
385             ep.addProperty(
386                 RuntimeConstants.RESOURCE_LOADER,
387                 "andromda.cartridges,file");
388 
389             ep.setProperty(
390                 "andromda.cartridges."
391                     + RuntimeConstants.RESOURCE_LOADER
392                     + ".class",
393                 ClasspathResourceLoader.class.getName());
394 
395             // This is important - Torsten Juergeleit
396             // reported that Velocity does not re-load the macros from the template
397             // file and sometimes uses a macro from one template file when
398             // processing another template file that contains a macro with the
399             // same name. This setting forces inline macros to be local, not global.
400             ep.setProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, "true");
401 
402             ve.setExtendedProperties(ep);
403             ve.init();
404         }
405         catch (Exception e)
406         {
407             log("Error: " + e.toString(), Project.MSG_INFO);
408             throw new BuildException(e);
409         }
410     }
411 
412     /***
413      * This method would normally be unnecessary. It is here because of a bug in
414      * ant. Ant calls addOutlet() before the OutletMapping javabean is fully
415      * initialized. So we kept the javabeans in an ArrayList that we have to
416      * copy into the dictionary now.
417      */
418     private void initOutletDictionary()
419     {
420         for (Iterator iter = outletMappingList.iterator(); iter.hasNext();)
421         {
422             OutletMapping om = (OutletMapping) iter.next();
423             outletDictionary.addOutletMapping(
424                 om.getCartridge(),
425                 om.getOutlet(),
426                 om.getDir());
427         }
428         outletMappingList = null;
429     }
430 
431     /***
432      * Initialize the cartridge system. Discover all installed cartridges and
433      * register them in the cartridge dictionary.
434      */
435     private void initCartridges() throws BuildException
436     {
437         CartridgeFinder.initClasspath(getClass());
438         try
439         {
440             List cartridges = CartridgeFinder.findCartridges();
441             
442             if (cartridges.size() <= 0)
443             {
444                 log("Warning: No cartridges found, check configuration!", Project.MSG_INFO);
445             }
446             else
447             {
448                 cartridgeDictionary = new CartridgeDictionary();
449                 for (Iterator cartridgeIterator = cartridges.iterator();
450                     cartridgeIterator.hasNext();
451                     )
452                 {
453                     IAndroMDACartridge cartridge =
454                         (IAndroMDACartridge) cartridgeIterator.next();
455                     List stereotypes =
456                         cartridge.getDescriptor().getSupportedStereotypes();
457                     for (Iterator stereotypeIterator = stereotypes.iterator();
458                         stereotypeIterator.hasNext();
459                         )
460                     {
461                         String stType = (String) stereotypeIterator.next();
462                         cartridgeDictionary.addCartridge(stType, cartridge);
463                     }
464                 }
465             }
466         }
467         catch (IOException e)
468         {
469             throw new BuildException(e);
470         }
471 
472     }
473 
474     private void process(URL url) throws BuildException
475     {
476         Context context = new Context();
477 
478         try
479         {
480             //-- command line status
481             log("Input:  " + url, Project.MSG_INFO);
482 
483             // configure repository
484             context.repository = createRepository().createRepository();
485             context.repository.open();
486             context.repository.readModel(url);
487 
488             // configure script helper
489             context.scriptHelper = createRepository().createTransform();
490             context.scriptHelper.setModel(context.repository.getModel());
491             context.scriptHelper.setTypeMappings(typeMappings);
492 
493         }
494         catch (FileNotFoundException fnfe)
495         {
496             throw new BuildException("Model file not found: " + modelURL);
497         }
498         catch (IOException ioe)
499         {
500             throw new BuildException(
501                 "Exception encountered while processing: " + modelURL);
502         }
503         catch (RepositoryReadException mdre)
504         {
505             throw new BuildException(mdre);
506         }
507 
508         // process all model elements
509         Collection elements = context.scriptHelper.getModelElements();
510         for (Iterator it = elements.iterator(); it.hasNext();)
511         {
512             processModelElement(context, it.next());
513         }
514         context.repository.close();
515 
516     }
517 
518     /***
519      * <p>Processes one type (e.g. class, interface or datatype) but possibly
520      * with several templates.</p>
521      *
522      *@param  mdr              Description of the Parameter
523      *@param  modelElement     Description of the Parameter
524      *@throws  BuildException  if something goes wrong
525      */
526     private void processModelElement(Context context, Object modelElement)
527         throws BuildException
528     {
529         String name = context.scriptHelper.getName(modelElement);
530         Collection stereotypeNames =
531             context.scriptHelper.getStereotypeNames(modelElement);
532 
533         for (Iterator i = stereotypeNames.iterator(); i.hasNext();)
534         {
535             String stereotypeName = (String) i.next();
536 
537             processModelElementStereotype(
538                 context,
539                 modelElement,
540                 stereotypeName);
541         }
542 
543     }
544 
545     /***
546      * Generate code from a model element, using exactly one of its stereotypes.
547      * 
548      * @param context the context for the code generation
549      * @param modelElement the model element
550      * @param stereotypeName the name of the stereotype
551      * @throws BuildException if something goes wrong
552      */
553     private void processModelElementStereotype(
554         Context context,
555         Object modelElement,
556         String stereotypeName)
557         throws BuildException
558     {
559         Collection suitableCartridges =
560             cartridgeDictionary.lookupCartridges(stereotypeName);
561         // @todo: lookup cartridges not only by stereotype 
562         // but also by properties which come from the tagged 
563         // values of the model element. This is to find those
564         // cartridges that support the proper architectural aspect.
565 
566         if (suitableCartridges == null)
567         {
568             return;
569         }
570 
571         for (Iterator iter = suitableCartridges.iterator();
572             iter.hasNext();
573             )
574         {
575             IAndroMDACartridge c = (IAndroMDACartridge) iter.next();
576 
577             processModelElementWithCartridge(
578                 context,
579                 modelElement,
580                 c,
581                 stereotypeName);
582         }
583     }
584 
585     private void processModelElementWithCartridge(
586         Context context,
587         Object modelElement,
588         IAndroMDACartridge cartridge,
589         String stereotypeName)
590         throws BuildException
591     {
592         String name = context.scriptHelper.getName(modelElement);
593         String packageName =
594             context.scriptHelper.getPackageName(modelElement);
595         long modelLastModified = context.repository.getLastModified();
596 
597         List templates =
598             cartridge.getDescriptor().getTemplateConfigurations();
599         for (Iterator it = templates.iterator(); it.hasNext();)
600         {
601             TemplateConfiguration tc = (TemplateConfiguration) it.next();
602             if (tc.getStereotype().equals(stereotypeName))
603             {
604                 ScriptHelper scriptHelper = context.scriptHelper;
605 
606                 if (tc.getTransformClass() != null)
607                 {
608                     // template has its own custom script helper
609                     try
610                     {
611                         context.scriptHelper =
612                             (ScriptHelper) tc
613                                 .getTransformClass()
614                                 .newInstance();
615                         context.scriptHelper.setModel(
616                             context.repository.getModel());
617                         context.scriptHelper.setTypeMappings(typeMappings);
618                     }
619                     catch (IllegalAccessException iae)
620                     {
621                         throw new BuildException(iae);
622                     }
623                     catch (InstantiationException ie)
624                     {
625                         throw new BuildException(ie);
626                     }
627                 }
628 
629                 File outFile =
630                     tc.getFullyQualifiedOutputFile(
631                         name,
632                         packageName,
633                         outletDictionary);
634 
635                 if (outFile != null)
636                 {
637                     try
638                     {
639                         // do not overwrite already generated file,
640                         // if that is a file that the user wants to edit.
641                         boolean writeOutputFile =
642                             !outFile.exists() || tc.isOverwrite();
643                         // only process files that have changed
644                         if (writeOutputFile
645                             && (lastModifiedCheck == false
646                                 || modelLastModified > outFile.lastModified()
647                             /*
648                         *  || styleSheetLastModified > outFile.lastModified()
649                         */
650                             ))
651                         {
652                             processModelElementWithOneTemplate(
653                                 context,
654                                 modelElement,
655                                 tc.getSheet(),
656                                 outFile,
657                                 tc.isGenerateEmptyFiles());
658                         }
659                     }
660                     catch (ClassTemplateProcessingException e)
661                     {
662                         outFile.delete();
663                         throw new BuildException(e);
664                     }
665                 }
666 
667                 // restore original script helper in case we were
668                 // using a custom template script helper
669                 context.scriptHelper = scriptHelper;
670             }
671         }
672     }
673 
674     /***
675      * <p>
676      * Processes  one type (that is class, interface or datatype) with exactly
677      * one  template script.
678      * </p>
679      *
680      * @param  context         context for code generation
681      * @param  modelElement    the model element for which code should be
682      *                         generated
683      * @param  styleSheetName  name of the Velocity style sheet
684      * @param  outFile         file to which to write the output
685      * @param  generateEmptyFile flag, tells whether to generate empty
686      *                         files or not.
687      * @throws  ClassTemplateProcessingException  if something goes wrong
688      */
689     private void processModelElementWithOneTemplate(
690         Context context,
691         Object modelElement,
692         String styleSheetName,
693         File outFile,
694         boolean generateEmptyFile)
695         throws ClassTemplateProcessingException
696     {
697         Writer writer = null;
698         ByteArrayOutputStream content = null;
699 
700         ensureDirectoryFor(outFile);
701         String encoding = getTemplateEncoding();
702         try
703         {
704             if (generateEmptyFile)
705             {
706                 writer =
707                     new BufferedWriter(
708                         new OutputStreamWriter(
709                             new FileOutputStream(outFile),
710                             encoding));
711             } else {
712                 content = new ByteArrayOutputStream();
713                 writer = new OutputStreamWriter(content, encoding);
714             }
715         }
716         catch (Exception e)
717         {
718             throw new ClassTemplateProcessingException(
719                 "Error opening output file " + outFile.getName(),
720                 e);
721         }
722 
723         try
724         {
725             VelocityContext velocityContext = new VelocityContext();
726 
727             // put some objects into the velocity context
728             velocityContext.put("model", context.scriptHelper.getModel());
729             velocityContext.put("transform", context.scriptHelper);
730             velocityContext.put("str", new StringUtilsHelper());
731             velocityContext.put("class", modelElement);
732             velocityContext.put("date", new java.util.Date());
733 
734             addUserPropertiesToContext(velocityContext);
735 
736             // Process the VSL template with the context and write out
737             // the result as the outFile.
738             // get the template to process
739             // the template name is dependent on the class's stereotype
740             // e.g. if the class is an "EntityBean", the template name
741             // is "EntityBean.vsl".
742 
743             Template template = ve.getTemplate(styleSheetName);
744             template.merge(velocityContext, writer);
745 
746             writer.flush();
747             writer.close();
748         }
749         catch (Exception e)
750         {
751             try
752             {
753                 writer.flush();
754                 writer.close();
755             }
756             catch (Exception e2)
757             {
758             }
759 
760             throw new ClassTemplateProcessingException(
761                 "Error processing velocity script on " + outFile.getName(),
762                 e);
763         }
764         
765         // Handle file generation/removal if no files should be generated for
766         // empty output.
767         if (!generateEmptyFile) 
768         {
769             byte[] result = content.toByteArray();
770             if (result.length > 0) 
771             {
772                 try
773                 {
774                     OutputStream out = new FileOutputStream(outFile);
775                     out.write(result);
776                     log("Output: " + outFile, Project.MSG_INFO);
777                 }
778                 catch (Exception e)
779                 {
780                     throw new ClassTemplateProcessingException(
781                         "Error writing output file " + outFile.getName(),
782                         e);
783                 }
784             } 
785             else 
786             {
787                 if (outFile.exists()) 
788                 {
789                     if (!outFile.delete()) 
790                     {
791                         throw new ClassTemplateProcessingException(
792                             "Error removing output file " + outFile.getName());                                
793                     }
794                     log("Remove: " + outFile, Project.MSG_INFO);
795                 }
796             }
797         }
798         else
799         {
800             log("Output: " + outFile, Project.MSG_INFO);
801         }
802     }
803 
804     /***
805      *  Takes all the UserProperty values that were defined in the ant build.xml
806      *  file and adds them to the Velocity context.
807      *
808      *@param  context  the Velocity context
809      */
810     private void addUserPropertiesToContext(VelocityContext context)
811     {
812         for (Iterator it = userProperties.iterator(); it.hasNext();)
813         {
814             UserProperty up = (UserProperty) it.next();
815             context.put(up.getName(), up.getValue());
816         }
817     }
818 
819     /***
820      *  Gets the templateEncoding attribute of the AndroMDAGenTask object
821      *
822      *@return    The templateEncoding value
823      */
824     private String getTemplateEncoding()
825     {
826         /*
827          *  get the property TEMPLATE_ENCODING
828          *  we know it's a string...
829          */
830         String encoding =
831             (String) ve.getProperty(RuntimeConstants.OUTPUT_ENCODING);
832         if (encoding == null
833             || encoding.length() == 0
834             || encoding.equals("8859-1")
835             || encoding.equals("8859_1"))
836         {
837             encoding = "ISO-8859-1";
838         }
839         return encoding;
840     }
841 
842     /***
843      * Creates and returns a repsository configuration object.  
844      * 
845      * This enables an ANT build script to use the &lt;repository&gt; ant subtask
846      * to configure the model repository used by ANDROMDA during code
847      * generation.
848      * 
849      * @return RepositoryConfiguration
850      * @throws BuildException
851      */
852     public RepositoryConfiguration createRepository() throws BuildException
853     {
854         if (repositoryConfiguration == null)
855         {
856             repositoryConfiguration = new RepositoryConfiguration();
857         }
858 
859         return repositoryConfiguration;
860     }
861 
862     /***
863      *  <p>
864      *
865      *  Creates directories as needed.</p>
866      *
867      *@param  targetFile          a <code>File</code> whose parent directories need
868      *      to exist
869      *@exception  BuildException  if the parent directories couldn't be created
870      */
871     private void ensureDirectoryFor(File targetFile) throws BuildException
872     {
873         File directory = new File(targetFile.getParent());
874         if (!directory.exists())
875         {
876             if (!directory.mkdirs())
877             {
878                 throw new BuildException(
879                     "Unable to create directory: "
880                         + directory.getAbsolutePath());
881             }
882         }
883     }
884 
885     /***
886      * Context used for doing code generation
887      */
888     private static class Context
889     {
890         RepositoryFacade repository = null;
891         ScriptHelper scriptHelper = null;
892     }
893 
894 }