001    /*
002     *   Copyright (C) Christian Schulte, 2005-206
003     *   All rights reserved.
004     *
005     *   Redistribution and use in source and binary forms, with or without
006     *   modification, are permitted provided that the following conditions
007     *   are met:
008     *
009     *     o Redistributions of source code must retain the above copyright
010     *       notice, this list of conditions and the following disclaimer.
011     *
012     *     o Redistributions in binary form must reproduce the above copyright
013     *       notice, this list of conditions and the following disclaimer in
014     *       the documentation and/or other materials provided with the
015     *       distribution.
016     *
017     *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018     *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019     *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020     *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021     *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022     *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023     *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024     *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026     *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     *
028     *   $JOMC: JomcTask.java 4204 2012-01-26 08:33:42Z schulte2005 $
029     *
030     */
031    package org.jomc.ant;
032    
033    import java.io.BufferedReader;
034    import java.io.File;
035    import java.io.IOException;
036    import java.io.InputStream;
037    import java.io.StringReader;
038    import java.io.StringWriter;
039    import java.net.MalformedURLException;
040    import java.net.SocketTimeoutException;
041    import java.net.URI;
042    import java.net.URISyntaxException;
043    import java.net.URL;
044    import java.net.URLConnection;
045    import java.util.ArrayList;
046    import java.util.Collection;
047    import java.util.Enumeration;
048    import java.util.HashSet;
049    import java.util.Iterator;
050    import java.util.LinkedList;
051    import java.util.List;
052    import java.util.Map;
053    import java.util.Properties;
054    import java.util.Set;
055    import java.util.logging.Level;
056    import javax.xml.bind.JAXBException;
057    import javax.xml.bind.Marshaller;
058    import javax.xml.transform.ErrorListener;
059    import javax.xml.transform.Transformer;
060    import javax.xml.transform.TransformerConfigurationException;
061    import javax.xml.transform.TransformerException;
062    import javax.xml.transform.TransformerFactory;
063    import javax.xml.transform.stream.StreamSource;
064    import org.apache.tools.ant.BuildException;
065    import org.apache.tools.ant.Project;
066    import org.apache.tools.ant.PropertyHelper;
067    import org.apache.tools.ant.Task;
068    import org.apache.tools.ant.types.Path;
069    import org.apache.tools.ant.types.Reference;
070    import org.jomc.ant.types.KeyValueType;
071    import org.jomc.ant.types.NameType;
072    import org.jomc.ant.types.PropertiesFormatType;
073    import org.jomc.ant.types.PropertiesResourceType;
074    import org.jomc.ant.types.ResourceType;
075    import org.jomc.ant.types.TransformerResourceType;
076    import org.jomc.model.ModelObject;
077    import org.jomc.modlet.DefaultModelContext;
078    import org.jomc.modlet.DefaultModletProvider;
079    import org.jomc.modlet.Model;
080    import org.jomc.modlet.ModelContext;
081    import org.jomc.modlet.ModelContextFactory;
082    import org.jomc.modlet.ModelException;
083    import org.jomc.modlet.ModelValidationReport;
084    import org.jomc.modlet.ModletProvider;
085    
086    /**
087     * Base class for executing tasks.
088     *
089     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
090     * @version $JOMC: JomcTask.java 4204 2012-01-26 08:33:42Z schulte2005 $
091     * @see #execute()
092     */
093    public class JomcTask extends Task
094    {
095    
096        /** The class path to process. */
097        private Path classpath;
098    
099        /** The identifier of the model to process. */
100        private String model;
101    
102        /** {@code ModelContext} attributes to apply. */
103        private List<KeyValueType> modelContextAttributes;
104    
105        /** The name of the {@code ModelContextFactory} implementation class backing the task. */
106        private String modelContextFactoryClassName;
107    
108        /** Controls processing of models. */
109        private boolean modelProcessingEnabled = true;
110    
111        /** The location to search for modlets. */
112        private String modletLocation;
113    
114        /** The {@code http://jomc.org/modlet} namespace schema system id of the context backing the task. */
115        private String modletSchemaSystemId;
116    
117        /** The location to search for providers. */
118        private String providerLocation;
119    
120        /** The location to search for platform providers. */
121        private String platformProviderLocation;
122    
123        /** The global transformation parameters to apply. */
124        private List<KeyValueType> transformationParameters;
125    
126        /** The global transformation parameter resources to apply. */
127        private List<PropertiesResourceType> transformationParameterResources;
128    
129        /** The global transformation output properties to apply. */
130        private List<KeyValueType> transformationOutputProperties;
131    
132        /** The flag indicating JAXP schema validation of modlet resources is enabled. */
133        private boolean modletResourceValidationEnabled = true;
134    
135        /** Property controlling the execution of the task. */
136        private Object _if;
137    
138        /** Property controlling the execution of the task. */
139        private Object unless;
140    
141        /** Creates a new {@code JomcTask} instance. */
142        public JomcTask()
143        {
144            super();
145        }
146    
147        /**
148         * Gets an object controlling the execution of the task.
149         *
150         * @return An object controlling the execution of the task or {@code null}.
151         *
152         * @see #setIf(java.lang.Object)
153         */
154        public final Object getIf()
155        {
156            return this._if;
157        }
158    
159        /**
160         * Sets an object controlling the execution of the task.
161         *
162         * @param value The new object controlling the execution of the task or {@code null}.
163         *
164         * @see #getIf()
165         */
166        public final void setIf( final Object value )
167        {
168            this._if = value;
169        }
170    
171        /**
172         * Gets an object controlling the execution of the task.
173         *
174         * @return An object controlling the execution of the task or {@code null}.
175         *
176         * @see #setUnless(java.lang.Object)
177         */
178        public final Object getUnless()
179        {
180            if ( this.unless == null )
181            {
182                this.unless = Boolean.TRUE;
183            }
184    
185            return this.unless;
186        }
187    
188        /**
189         * Sets an object controlling the execution of the task.
190         *
191         * @param value The new object controlling the execution of the task or {@code null}.
192         *
193         * @see #getUnless()
194         */
195        public final void setUnless( final Object value )
196        {
197            this.unless = value;
198        }
199    
200        /**
201         * Creates a new {@code classpath} element instance.
202         *
203         * @return A new {@code classpath} element instance.
204         */
205        public final Path createClasspath()
206        {
207            return this.getClasspath().createPath();
208        }
209    
210        /**
211         * Gets the class path to process.
212         *
213         * @return The class path to process.
214         *
215         * @see #setClasspath(org.apache.tools.ant.types.Path)
216         */
217        public final Path getClasspath()
218        {
219            if ( this.classpath == null )
220            {
221                this.classpath = new Path( this.getProject() );
222            }
223    
224            return this.classpath;
225        }
226    
227        /**
228         * Adds to the class path to process.
229         *
230         * @param value The path to add to the list of class path elements.
231         *
232         * @see #getClasspath()
233         */
234        public final void setClasspath( final Path value )
235        {
236            this.getClasspath().add( value );
237        }
238    
239        /**
240         * Adds a reference to a class path defined elsewhere.
241         *
242         * @param value A reference to a class path.
243         *
244         * @see #getClasspath()
245         */
246        public final void setClasspathRef( final Reference value )
247        {
248            this.getClasspath().setRefid( value );
249        }
250    
251        /**
252         * Gets the identifier of the model to process.
253         *
254         * @return The identifier of the model to process.
255         *
256         * @see #setModel(java.lang.String)
257         */
258        public final String getModel()
259        {
260            if ( this.model == null )
261            {
262                this.model = ModelObject.MODEL_PUBLIC_ID;
263            }
264    
265            return this.model;
266        }
267    
268        /**
269         * Sets the identifier of the model to process.
270         *
271         * @param value The new identifier of the model to process or {@code null}.
272         *
273         * @see #getModel()
274         */
275        public final void setModel( final String value )
276        {
277            this.model = value;
278        }
279    
280        /**
281         * Gets the {@code ModelContext} attributes to apply.
282         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
283         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
284         * model context attributes property.</p>
285         *
286         * @return The  {@code ModelContext} attributes to apply.
287         *
288         * @see #createModelContextAttribute()
289         * @see #newModelContext(java.lang.ClassLoader)
290         */
291        public final List<KeyValueType> getModelContextAttributes()
292        {
293            if ( this.modelContextAttributes == null )
294            {
295                this.modelContextAttributes = new LinkedList<KeyValueType>();
296            }
297    
298            return this.modelContextAttributes;
299        }
300    
301        /**
302         * Creates a new {@code modelContextAttribute} element instance.
303         *
304         * @return A new {@code modelContextAttribute} element instance.
305         *
306         * @see #getModelContextAttributes()
307         */
308        public KeyValueType createModelContextAttribute()
309        {
310            final KeyValueType modelContextAttribute = new KeyValueType();
311            this.getModelContextAttributes().add( modelContextAttribute );
312            return modelContextAttribute;
313        }
314    
315        /**
316         * Gets the name of the {@code ModelContextFactory} implementation class backing the task.
317         *
318         * @return The name of the {@code ModelContextFactory} implementation class backing the task or {@code null}.
319         *
320         * @see #setModelContextFactoryClassName(java.lang.String)
321         */
322        public final String getModelContextFactoryClassName()
323        {
324            return this.modelContextFactoryClassName;
325        }
326    
327        /**
328         * Sets the name of the {@code ModelContextFactory} implementation class backing the task.
329         *
330         * @param value The new name of the {@code ModelContextFactory} implementation class backing the task or
331         * {@code null}.
332         *
333         * @see #getModelContextFactoryClassName()
334         */
335        public final void setModelContextFactoryClassName( final String value )
336        {
337            this.modelContextFactoryClassName = value;
338        }
339    
340        /**
341         * Gets a flag indicating the processing of models is enabled.
342         *
343         * @return {@code true}, if processing of models is enabled; {@code false}, else.
344         *
345         * @see #setModelProcessingEnabled(boolean)
346         */
347        public final boolean isModelProcessingEnabled()
348        {
349            return this.modelProcessingEnabled;
350        }
351    
352        /**
353         * Sets the flag indicating the processing of models is enabled.
354         *
355         * @param value {@code true}, to enable processing of models; {@code false}, to disable processing of models.
356         *
357         * @see #isModelProcessingEnabled()
358         */
359        public final void setModelProcessingEnabled( final boolean value )
360        {
361            this.modelProcessingEnabled = value;
362        }
363    
364        /**
365         * Gets the location searched for modlets.
366         *
367         * @return The location searched for modlets or {@code null}.
368         *
369         * @see #setModletLocation(java.lang.String)
370         */
371        public final String getModletLocation()
372        {
373            return this.modletLocation;
374        }
375    
376        /**
377         * Sets the location to search for modlets.
378         *
379         * @param value The new location to search for modlets or {@code null}.
380         *
381         * @see #getModletLocation()
382         */
383        public final void setModletLocation( final String value )
384        {
385            this.modletLocation = value;
386        }
387    
388        /**
389         * Gets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
390         *
391         * @return The {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
392         * {@code null}.
393         *
394         * @see #setModletSchemaSystemId(java.lang.String)
395         */
396        public final String getModletSchemaSystemId()
397        {
398            return this.modletSchemaSystemId;
399        }
400    
401        /**
402         * Sets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
403         *
404         * @param value The new {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
405         * {@code null}.
406         *
407         * @see #getModletSchemaSystemId()
408         */
409        public final void setModletSchemaSystemId( final String value )
410        {
411            this.modletSchemaSystemId = value;
412        }
413    
414        /**
415         * Gets the location searched for providers.
416         *
417         * @return The location searched for providers or {@code null}.
418         *
419         * @see #setProviderLocation(java.lang.String)
420         */
421        public final String getProviderLocation()
422        {
423            return this.providerLocation;
424        }
425    
426        /**
427         * Sets the location to search for providers.
428         *
429         * @param value The new location to search for providers or {@code null}.
430         *
431         * @see #getProviderLocation()
432         */
433        public final void setProviderLocation( final String value )
434        {
435            this.providerLocation = value;
436        }
437    
438        /**
439         * Gets the location searched for platform provider resources.
440         *
441         * @return The location searched for platform provider resources or {@code null}.
442         *
443         * @see #setPlatformProviderLocation(java.lang.String)
444         */
445        public final String getPlatformProviderLocation()
446        {
447            return this.platformProviderLocation;
448        }
449    
450        /**
451         * Sets the location to search for platform provider resources.
452         *
453         * @param value The new location to search for platform provider resources or {@code null}.
454         *
455         * @see #getPlatformProviderLocation()
456         */
457        public final void setPlatformProviderLocation( final String value )
458        {
459            this.platformProviderLocation = value;
460        }
461    
462        /**
463         * Gets the global transformation parameters to apply.
464         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
465         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
466         * transformation parameters property.</p>
467         *
468         * @return The global transformation parameters to apply.
469         *
470         * @see #createTransformationParameter()
471         * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
472         */
473        public final List<KeyValueType> getTransformationParameters()
474        {
475            if ( this.transformationParameters == null )
476            {
477                this.transformationParameters = new LinkedList<KeyValueType>();
478            }
479    
480            return this.transformationParameters;
481        }
482    
483        /**
484         * Creates a new {@code transformationParameter} element instance.
485         *
486         * @return A new {@code transformationParameter} element instance.
487         *
488         * @see #getTransformationParameters()
489         */
490        public KeyValueType createTransformationParameter()
491        {
492            final KeyValueType transformationParameter = new KeyValueType();
493            this.getTransformationParameters().add( transformationParameter );
494            return transformationParameter;
495        }
496    
497        /**
498         * Gets the global transformation parameter resources to apply.
499         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
500         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
501         * transformation parameter resources property.</p>
502         *
503         * @return The global transformation parameter resources to apply.
504         *
505         * @see #createTransformationParameterResource()
506         * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
507         */
508        public final List<PropertiesResourceType> getTransformationParameterResources()
509        {
510            if ( this.transformationParameterResources == null )
511            {
512                this.transformationParameterResources = new LinkedList<PropertiesResourceType>();
513            }
514    
515            return this.transformationParameterResources;
516        }
517    
518        /**
519         * Creates a new {@code transformationParameterResource} element instance.
520         *
521         * @return A new {@code transformationParameterResource} element instance.
522         *
523         * @see #getTransformationParameterResources()
524         */
525        public PropertiesResourceType createTransformationParameterResource()
526        {
527            final PropertiesResourceType transformationParameterResource = new PropertiesResourceType();
528            this.getTransformationParameterResources().add( transformationParameterResource );
529            return transformationParameterResource;
530        }
531    
532        /**
533         * Gets the global transformation output properties to apply.
534         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
535         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
536         * transformation output properties property.</p>
537         *
538         * @return The global transformation output properties to apply.
539         *
540         * @see #createTransformationOutputProperty()
541         */
542        public final List<KeyValueType> getTransformationOutputProperties()
543        {
544            if ( this.transformationOutputProperties == null )
545            {
546                this.transformationOutputProperties = new LinkedList<KeyValueType>();
547            }
548    
549            return this.transformationOutputProperties;
550        }
551    
552        /**
553         * Creates a new {@code transformationOutputProperty} element instance.
554         *
555         * @return A new {@code transformationOutputProperty} element instance.
556         *
557         * @see #getTransformationOutputProperties()
558         */
559        public KeyValueType createTransformationOutputProperty()
560        {
561            final KeyValueType transformationOutputProperty = new KeyValueType();
562            this.getTransformationOutputProperties().add( transformationOutputProperty );
563            return transformationOutputProperty;
564        }
565    
566        /**
567         * Gets a flag indicating JAXP schema validation of modlet resources is enabled.
568         *
569         * @return {@code true}, if JAXP schema validation of modlet resources is enabled; {@code false}, else.
570         *
571         * @see #setModletResourceValidationEnabled(boolean)
572         */
573        public final boolean isModletResourceValidationEnabled()
574        {
575            return this.modletResourceValidationEnabled;
576        }
577    
578        /**
579         * Sets the flag indicating JAXP schema validation of modlet resources is enabled.
580         *
581         * @param value {@code true}, to enable JAXP schema validation of modlet resources; {@code false}, to disable JAXP
582         * schema validation of modlet resources.
583         *
584         * @see #isModletResourceValidationEnabled()
585         */
586        public final void setModletResourceValidationEnabled( final boolean value )
587        {
588            this.modletResourceValidationEnabled = value;
589        }
590    
591        /**
592         * Called by the project to let the task do its work.
593         *
594         * @throws BuildException if execution fails.
595         *
596         * @see #getIf()
597         * @see #getUnless()
598         * @see #preExecuteTask()
599         * @see #executeTask()
600         * @see #postExecuteTask()
601         */
602        @Override
603        public final void execute() throws BuildException
604        {
605            final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper( this.getProject() );
606    
607            if ( propertyHelper.testIfCondition( this.getIf() ) && !propertyHelper.testUnlessCondition( this.getUnless() ) )
608            {
609                try
610                {
611                    this.preExecuteTask();
612                    this.executeTask();
613                }
614                finally
615                {
616                    this.postExecuteTask();
617                }
618            }
619        }
620    
621        /**
622         * Called by the {@code execute} method prior to the {@code executeTask} method.
623         *
624         * @throws BuildException if execution fails.
625         *
626         * @see #execute()
627         */
628        public void preExecuteTask() throws BuildException
629        {
630            this.logSeparator();
631            this.log( Messages.getMessage( "title" ) );
632            this.logSeparator();
633    
634            this.assertNotNull( "model", this.getModel() );
635            this.assertKeysNotNull( this.getModelContextAttributes() );
636            this.assertKeysNotNull( this.getTransformationParameters() );
637            this.assertKeysNotNull( this.getTransformationOutputProperties() );
638            this.assertLocationsNotNull( this.getTransformationParameterResources() );
639        }
640    
641        /**
642         * Called by the {@code execute} method prior to the {@code postExecuteTask} method.
643         *
644         * @throws BuildException if execution fails.
645         *
646         * @see #execute()
647         */
648        public void executeTask() throws BuildException
649        {
650            this.getProject().log( Messages.getMessage( "unimplementedTask", this.getClass().getName(), "executeTask" ),
651                                   Project.MSG_WARN );
652    
653        }
654    
655        /**
656         * Called by the {@code execute} method after the {@code preExecuteTask}/{@code executeTask} methods even if those
657         * methods threw an exception.
658         *
659         * @throws BuildException if execution fails.
660         *
661         * @see #execute()
662         */
663        public void postExecuteTask() throws BuildException
664        {
665            this.logSeparator();
666        }
667    
668        /**
669         * Gets a {@code Model} from a given {@code ModelContext}.
670         *
671         * @param context The context to get a {@code Model} from.
672         *
673         * @return The {@code Model} from {@code context}.
674         *
675         * @throws NullPointerException if {@code contexŧ} is {@code null}.
676         * @throws ModelException if getting the model fails.
677         *
678         * @see #getModel()
679         * @see #isModelProcessingEnabled()
680         */
681        public Model getModel( final ModelContext context ) throws ModelException
682        {
683            if ( context == null )
684            {
685                throw new NullPointerException( "context" );
686            }
687    
688            Model foundModel = context.findModel( this.getModel() );
689    
690            if ( foundModel != null && this.isModelProcessingEnabled() )
691            {
692                foundModel = context.processModel( foundModel );
693            }
694    
695            return foundModel;
696        }
697    
698        /**
699         * Creates an {@code URL} for a given resource location.
700         * <p>This method first searches the class path of the task for a single resource matching {@code location}. If
701         * such a resource is found, the URL of that resource is returned. If no such resource is found, an attempt is made
702         * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
703         * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
704         * of that file is returned. Otherwise {@code null} is returned.</p>
705         *
706         * @param location The resource location to create an {@code URL} from.
707         *
708         * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
709         * {@code location} points to a non-existent resource.
710         *
711         * @throws NullPointerException if {@code location} is {@code null}.
712         * @throws BuildException if creating an URL fails.
713         */
714        public URL getResource( final String location ) throws BuildException
715        {
716            if ( location == null )
717            {
718                throw new NullPointerException( "location" );
719            }
720    
721            try
722            {
723                String absolute = location;
724                if ( !absolute.startsWith( "/" ) )
725                {
726                    absolute = "/" + absolute;
727                }
728    
729                URL resource = this.getClass().getResource( absolute );
730                if ( resource == null )
731                {
732                    try
733                    {
734                        resource = new URL( location );
735                    }
736                    catch ( final MalformedURLException e )
737                    {
738                        this.log( e, Project.MSG_DEBUG );
739                        resource = null;
740                    }
741                }
742    
743                if ( resource == null )
744                {
745                    final File f = this.getProject().resolveFile( location );
746    
747                    if ( f.isFile() )
748                    {
749                        resource = f.toURI().toURL();
750                    }
751                }
752    
753                return resource;
754            }
755            catch ( final MalformedURLException e )
756            {
757                String m = Messages.getMessage( e );
758                m = m == null ? "" : " " + m;
759    
760                throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
761            }
762        }
763    
764        /**
765         * Creates an array of {@code URL}s for a given resource location.
766         * <p>This method first searches the given context for resources matching {@code location}. If such resources are
767         * found, an array of URLs of those resources is returned. If no such resources are found, an attempt is made
768         * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
769         * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
770         * of that file is returned. Otherwise an empty array is returned.</p>
771         *
772         * @param context The context to search for resources.
773         * @param location The resource location to create an array of {@code URL}s from.
774         *
775         * @return An array of {@code URL}s for {@code location} or an empty array if parsing {@code location} to an URL
776         * fails and {@code location} points to non-existent resources.
777         *
778         * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
779         * @throws BuildException if creating an URL array fails.
780         */
781        public URL[] getResources( final ModelContext context, final String location ) throws BuildException
782        {
783            if ( context == null )
784            {
785                throw new NullPointerException( "context" );
786            }
787            if ( location == null )
788            {
789                throw new NullPointerException( "location" );
790            }
791    
792            final Set<URI> uris = new HashSet<URI>();
793    
794            try
795            {
796                for ( final Enumeration<URL> e = context.findResources( location ); e.hasMoreElements(); )
797                {
798                    uris.add( e.nextElement().toURI() );
799                }
800            }
801            catch ( final URISyntaxException e )
802            {
803                this.log( e, Project.MSG_DEBUG );
804            }
805            catch ( final ModelException e )
806            {
807                this.log( e, Project.MSG_DEBUG );
808            }
809    
810            if ( uris.isEmpty() )
811            {
812                try
813                {
814                    uris.add( new URL( location ).toURI() );
815                }
816                catch ( final MalformedURLException e )
817                {
818                    this.log( e, Project.MSG_DEBUG );
819                }
820                catch ( final URISyntaxException e )
821                {
822                    this.log( e, Project.MSG_DEBUG );
823                }
824            }
825    
826            if ( uris.isEmpty() )
827            {
828                final File f = this.getProject().resolveFile( location );
829    
830                if ( f.isFile() )
831                {
832                    uris.add( f.toURI() );
833                }
834            }
835    
836            int i = 0;
837            final URL[] urls = new URL[ uris.size() ];
838    
839            for ( URI uri : uris )
840            {
841                try
842                {
843                    urls[i++] = uri.toURL();
844                }
845                catch ( final MalformedURLException e )
846                {
847                    String m = Messages.getMessage( e );
848                    m = m == null ? "" : " " + m;
849    
850                    throw new BuildException( Messages.getMessage( "malformedLocation", uri.toASCIIString(), m ), e,
851                                              this.getLocation() );
852    
853                }
854            }
855    
856            return urls;
857        }
858    
859        /**
860         * Creates an {@code URL} for a given directory location.
861         * <p>This method first attempts to parse the given location to an URL. On successful parsing, that URL is returned.
862         * Failing that, the given location is interpreted as a directory name relative to the project's base directory. If
863         * that directory is found, the URL of that directory is returned. Otherwise {@code null} is returned.</p>
864         *
865         * @param location The directory location to create an {@code URL} from.
866         *
867         * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
868         * {@code location} points to a non-existent directory.
869         *
870         * @throws NullPointerException if {@code location} is {@code null}.
871         * @throws BuildException if creating an URL fails.
872         */
873        public URL getDirectory( final String location ) throws BuildException
874        {
875            if ( location == null )
876            {
877                throw new NullPointerException( "location" );
878            }
879    
880            try
881            {
882                URL resource = null;
883    
884                try
885                {
886                    resource = new URL( location );
887                }
888                catch ( final MalformedURLException e )
889                {
890                    this.log( e, Project.MSG_DEBUG );
891                    resource = null;
892                }
893    
894                if ( resource == null )
895                {
896                    final File f = this.getProject().resolveFile( location );
897    
898                    if ( f.isDirectory() )
899                    {
900                        resource = f.toURI().toURL();
901                    }
902                }
903    
904                return resource;
905            }
906            catch ( final MalformedURLException e )
907            {
908                String m = Messages.getMessage( e );
909                m = m == null ? "" : " " + m;
910    
911                throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
912            }
913        }
914    
915        /**
916         * Creates a new {@code Transformer} for a given {@code TransformerResourceType}.
917         *
918         * @param resource The resource to create a {@code Transformer} of.
919         *
920         * @return A new {@code Transformer} for {@code resource} or {@code null}, if {@code resource} is not found and
921         * flagged optional.
922         *
923         * @throws TransformerConfigurationException if creating a new {@code Transformer} fails.
924         *
925         * @see #getTransformationParameterResources()
926         * @see #getTransformationParameters()
927         * @see #getResource(java.lang.String)
928         */
929        public Transformer getTransformer( final TransformerResourceType resource ) throws TransformerConfigurationException
930        {
931            if ( resource == null )
932            {
933                throw new NullPointerException( "resource" );
934            }
935    
936            InputStream in = null;
937            boolean suppressExceptionOnClose = true;
938            final URL url = this.getResource( resource.getLocation() );
939    
940            try
941            {
942                if ( url != null )
943                {
944                    final ErrorListener errorListener = new ErrorListener()
945                    {
946    
947                        public void warning( final TransformerException exception ) throws TransformerException
948                        {
949                            if ( getProject() != null )
950                            {
951                                getProject().log( Messages.getMessage( exception ), exception, Project.MSG_WARN );
952                            }
953                        }
954    
955                        public void error( final TransformerException exception ) throws TransformerException
956                        {
957                            throw exception;
958                        }
959    
960                        public void fatalError( final TransformerException exception ) throws TransformerException
961                        {
962                            throw exception;
963                        }
964    
965                    };
966    
967                    final URLConnection con = url.openConnection();
968                    con.setConnectTimeout( resource.getConnectTimeout() );
969                    con.setReadTimeout( resource.getReadTimeout() );
970                    con.connect();
971                    in = con.getInputStream();
972    
973                    final TransformerFactory f = TransformerFactory.newInstance();
974                    f.setErrorListener( errorListener );
975                    final Transformer transformer = f.newTransformer( new StreamSource( in, url.toURI().toASCIIString() ) );
976                    transformer.setErrorListener( errorListener );
977    
978                    for ( Map.Entry<Object, Object> e : System.getProperties().entrySet() )
979                    {
980                        transformer.setParameter( e.getKey().toString(), e.getValue() );
981                    }
982    
983                    for ( final Iterator<Map.Entry<?, ?>> it = this.getProject().getProperties().entrySet().iterator();
984                          it.hasNext(); )
985                    {
986                        final Map.Entry<?, ?> e = it.next();
987                        transformer.setParameter( e.getKey().toString(), e.getValue() );
988                    }
989    
990                    for ( int i = 0, s0 = this.getTransformationParameterResources().size(); i < s0; i++ )
991                    {
992                        for ( Map.Entry<Object, Object> e :
993                              this.getProperties( this.getTransformationParameterResources().get( i ) ).entrySet() )
994                        {
995                            transformer.setParameter( e.getKey().toString(), e.getValue() );
996                        }
997                    }
998    
999                    for ( int i = 0, s0 = this.getTransformationParameters().size(); i < s0; i++ )
1000                    {
1001                        final KeyValueType p = this.getTransformationParameters().get( i );
1002                        transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1003                    }
1004    
1005                    for ( int i = 0, s0 = this.getTransformationOutputProperties().size(); i < s0; i++ )
1006                    {
1007                        final KeyValueType p = this.getTransformationOutputProperties().get( i );
1008                        transformer.setOutputProperty( p.getKey(), p.getValue() );
1009                    }
1010    
1011                    for ( int i = 0, s0 = resource.getTransformationParameterResources().size(); i < s0; i++ )
1012                    {
1013                        for ( Map.Entry<Object, Object> e :
1014                              this.getProperties( resource.getTransformationParameterResources().get( i ) ).entrySet() )
1015                        {
1016                            transformer.setParameter( e.getKey().toString(), e.getValue() );
1017                        }
1018                    }
1019    
1020                    for ( int i = 0, s0 = resource.getTransformationParameters().size(); i < s0; i++ )
1021                    {
1022                        final KeyValueType p = resource.getTransformationParameters().get( i );
1023                        transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1024                    }
1025    
1026                    for ( int i = 0, s0 = resource.getTransformationOutputProperties().size(); i < s0; i++ )
1027                    {
1028                        final KeyValueType p = resource.getTransformationOutputProperties().get( i );
1029                        transformer.setOutputProperty( p.getKey(), p.getValue() );
1030                    }
1031    
1032                    suppressExceptionOnClose = false;
1033                    return transformer;
1034                }
1035                else if ( resource.isOptional() )
1036                {
1037                    this.log( Messages.getMessage( "transformerNotFound", resource.getLocation() ), Project.MSG_WARN );
1038                }
1039                else
1040                {
1041                    throw new BuildException( Messages.getMessage( "transformerNotFound", resource.getLocation() ),
1042                                              this.getLocation() );
1043    
1044                }
1045            }
1046            catch ( final URISyntaxException e )
1047            {
1048                throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1049            }
1050            catch ( final SocketTimeoutException e )
1051            {
1052                final String message = Messages.getMessage( e );
1053    
1054                if ( resource.isOptional() )
1055                {
1056                    this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1057                                           e, Project.MSG_WARN );
1058    
1059                }
1060                else
1061                {
1062                    throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1063                                              e, this.getLocation() );
1064    
1065                }
1066            }
1067            catch ( final IOException e )
1068            {
1069                final String message = Messages.getMessage( e );
1070    
1071                if ( resource.isOptional() )
1072                {
1073                    this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1074                                           e, Project.MSG_WARN );
1075    
1076                }
1077                else
1078                {
1079                    throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1080                                              e, this.getLocation() );
1081    
1082                }
1083            }
1084            finally
1085            {
1086                try
1087                {
1088                    if ( in != null )
1089                    {
1090                        in.close();
1091                    }
1092                }
1093                catch ( final IOException e )
1094                {
1095                    if ( suppressExceptionOnClose )
1096                    {
1097                        this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1098                    }
1099                    else
1100                    {
1101                        throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1102                    }
1103                }
1104            }
1105    
1106            return null;
1107        }
1108    
1109        /**
1110         * Creates a new {@code Properties} instance from a {@code PropertiesResourceType}.
1111         *
1112         * @param propertiesResourceType The {@code PropertiesResourceType} specifying the properties to create.
1113         *
1114         * @return The properties for {@code propertiesResourceType}.
1115         *
1116         * @throws NullPointerException if {@code propertiesResourceType} is {@code null}.
1117         * @throws BuildException if loading properties fails.
1118         */
1119        public Properties getProperties( final PropertiesResourceType propertiesResourceType ) throws BuildException
1120        {
1121            if ( propertiesResourceType == null )
1122            {
1123                throw new NullPointerException( "propertiesResourceType" );
1124            }
1125    
1126            InputStream in = null;
1127            boolean suppressExceptionOnClose = true;
1128            final Properties properties = new Properties();
1129            final URL url = this.getResource( propertiesResourceType.getLocation() );
1130    
1131            try
1132            {
1133                if ( url != null )
1134                {
1135                    final URLConnection con = url.openConnection();
1136                    con.setConnectTimeout( propertiesResourceType.getConnectTimeout() );
1137                    con.setReadTimeout( propertiesResourceType.getReadTimeout() );
1138                    con.connect();
1139    
1140                    in = con.getInputStream();
1141    
1142                    if ( propertiesResourceType.getFormat() == PropertiesFormatType.PLAIN )
1143                    {
1144                        properties.load( in );
1145                    }
1146                    else if ( propertiesResourceType.getFormat() == PropertiesFormatType.XML )
1147                    {
1148                        properties.loadFromXML( in );
1149                    }
1150                }
1151                else if ( propertiesResourceType.isOptional() )
1152                {
1153                    this.log( Messages.getMessage( "propertiesNotFound", propertiesResourceType.getLocation() ),
1154                              Project.MSG_WARN );
1155    
1156                }
1157                else
1158                {
1159                    throw new BuildException( Messages.getMessage(
1160                        "propertiesNotFound", propertiesResourceType.getLocation() ), this.getLocation() );
1161    
1162                }
1163    
1164                suppressExceptionOnClose = false;
1165            }
1166            catch ( final SocketTimeoutException e )
1167            {
1168                final String message = Messages.getMessage( e );
1169    
1170                if ( propertiesResourceType.isOptional() )
1171                {
1172                    this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1173                                           e, Project.MSG_WARN );
1174    
1175                }
1176                else
1177                {
1178                    throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1179                                              e, this.getLocation() );
1180    
1181                }
1182            }
1183            catch ( final IOException e )
1184            {
1185                final String message = Messages.getMessage( e );
1186    
1187                if ( propertiesResourceType.isOptional() )
1188                {
1189                    this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1190                                           e, Project.MSG_WARN );
1191    
1192                }
1193                else
1194                {
1195                    throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1196                                              e, this.getLocation() );
1197    
1198                }
1199            }
1200            finally
1201            {
1202                try
1203                {
1204                    if ( in != null )
1205                    {
1206                        in.close();
1207                    }
1208                }
1209                catch ( final IOException e )
1210                {
1211                    if ( suppressExceptionOnClose )
1212                    {
1213                        this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1214                    }
1215                    else
1216                    {
1217                        throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1218                    }
1219                }
1220            }
1221    
1222            return properties;
1223        }
1224    
1225        /**
1226         * Creates a new {@code ProjectClassLoader} instance.
1227         *
1228         * @return A new {@code ProjectClassLoader} instance.
1229         *
1230         * @throws BuildException if creating a new class loader instance fails.
1231         */
1232        public ProjectClassLoader newProjectClassLoader() throws BuildException
1233        {
1234            try
1235            {
1236                final ProjectClassLoader classLoader = new ProjectClassLoader( this.getProject(), this.getClasspath() );
1237                classLoader.getModletExcludes().addAll( ProjectClassLoader.getDefaultModletExcludes() );
1238                classLoader.getProviderExcludes().addAll( ProjectClassLoader.getDefaultProviderExcludes() );
1239                classLoader.getSchemaExcludes().addAll( ProjectClassLoader.getDefaultSchemaExcludes() );
1240                classLoader.getServiceExcludes().addAll( ProjectClassLoader.getDefaultServiceExcludes() );
1241    
1242                if ( this.getModletLocation() != null )
1243                {
1244                    classLoader.getModletResourceLocations().add( this.getModletLocation() );
1245                }
1246                else
1247                {
1248                    classLoader.getModletResourceLocations().add( DefaultModletProvider.getDefaultModletLocation() );
1249                }
1250    
1251                if ( this.getProviderLocation() != null )
1252                {
1253                    classLoader.getProviderResourceLocations().add(
1254                        this.getProviderLocation() + "/" + ModletProvider.class.getName() );
1255    
1256                }
1257                else
1258                {
1259                    classLoader.getProviderResourceLocations().add(
1260                        DefaultModelContext.getDefaultProviderLocation() + "/" + ModletProvider.class.getName() );
1261    
1262                }
1263    
1264                return classLoader;
1265            }
1266            catch ( final IOException e )
1267            {
1268                throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1269            }
1270        }
1271    
1272        /**
1273         * Creates a new {@code ModelContext} instance using a given class loader.
1274         *
1275         * @param classLoader The class loader to create a new {@code ModelContext} instance with.
1276         *
1277         * @return A new {@code ModelContext} instance backed by {@code classLoader}.
1278         *
1279         * @throws ModelException if creating a new {@code ModelContext} instance fails.
1280         */
1281        public ModelContext newModelContext( final ClassLoader classLoader ) throws ModelException
1282        {
1283            final ModelContextFactory modelContextFactory;
1284            if ( this.modelContextFactoryClassName != null )
1285            {
1286                modelContextFactory = ModelContextFactory.newInstance( this.getModelContextFactoryClassName() );
1287            }
1288            else
1289            {
1290                modelContextFactory = ModelContextFactory.newInstance();
1291            }
1292    
1293            final ModelContext modelContext = modelContextFactory.newModelContext( classLoader );
1294            modelContext.setLogLevel( Level.ALL );
1295            modelContext.setModletSchemaSystemId( this.getModletSchemaSystemId() );
1296    
1297            modelContext.getListeners().add( new ModelContext.Listener()
1298            {
1299    
1300                @Override
1301                public void onLog( final Level level, final String message, final Throwable t )
1302                {
1303                    super.onLog( level, message, t );
1304                    logMessage( level, message, t );
1305                }
1306    
1307            } );
1308    
1309            if ( this.getProviderLocation() != null )
1310            {
1311                modelContext.setAttribute( DefaultModelContext.PROVIDER_LOCATION_ATTRIBUTE_NAME,
1312                                           this.getProviderLocation() );
1313    
1314            }
1315    
1316            if ( this.getPlatformProviderLocation() != null )
1317            {
1318                modelContext.setAttribute( DefaultModelContext.PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME,
1319                                           this.getPlatformProviderLocation() );
1320    
1321            }
1322    
1323            if ( this.getModletLocation() != null )
1324            {
1325                modelContext.setAttribute( DefaultModletProvider.MODLET_LOCATION_ATTRIBUTE_NAME, this.getModletLocation() );
1326            }
1327    
1328            modelContext.setAttribute( DefaultModletProvider.VALIDATING_ATTRIBUTE_NAME,
1329                                       this.isModletResourceValidationEnabled() );
1330    
1331            for ( int i = 0, s0 = this.getModelContextAttributes().size(); i < s0; i++ )
1332            {
1333                final KeyValueType kv = this.getModelContextAttributes().get( i );
1334                final Object object = kv.getObject( this.getLocation() );
1335    
1336                if ( object != null )
1337                {
1338                    modelContext.setAttribute( kv.getKey(), object );
1339                }
1340                else
1341                {
1342                    modelContext.clearAttribute( kv.getKey() );
1343                }
1344            }
1345    
1346            return modelContext;
1347        }
1348    
1349        /**
1350         * Throws a {@code BuildException} on a given {@code null} value.
1351         *
1352         * @param attributeName The name of a mandatory attribute.
1353         * @param value The value of that attribute.
1354         *
1355         * @throws NullPointerException if {@code attributeName} is {@code null}.
1356         * @throws BuildException if {@code value} is {@code null}.
1357         */
1358        public final void assertNotNull( final String attributeName, final Object value ) throws BuildException
1359        {
1360            if ( attributeName == null )
1361            {
1362                throw new NullPointerException( "attributeName" );
1363            }
1364    
1365            if ( value == null )
1366            {
1367                throw new BuildException( Messages.getMessage( "mandatoryAttribute", attributeName ), this.getLocation() );
1368            }
1369        }
1370    
1371        /**
1372         * Throws a {@code BuildException} on a {@code null} value of a {@code name} property of a given {@code NameType}
1373         * collection.
1374         *
1375         * @param names The collection holding the  {@code NameType} instances to test.
1376         *
1377         * @throws NullPointerException if {@code names} is {@code null}.
1378         * @throws BuildException if a {@code name} property of a given {@code NameType} from the {@code names} collection
1379         * holds a {@code null} value.
1380         */
1381        public final void assertNamesNotNull( final Collection<? extends NameType> names ) throws BuildException
1382        {
1383            if ( names == null )
1384            {
1385                throw new NullPointerException( "names" );
1386            }
1387    
1388            for ( NameType n : names )
1389            {
1390                this.assertNotNull( "name", n.getName() );
1391            }
1392        }
1393    
1394        /**
1395         * Throws a {@code BuildException} on a {@code null} value of a {@code key} property of a given {@code KeyValueType}
1396         * collection.
1397         *
1398         * @param keys The collection holding the  {@code KeyValueType} instances to test.
1399         *
1400         * @throws NullPointerException if {@code keys} is {@code null}.
1401         * @throws BuildException if a {@code key} property of a given {@code KeyValueType} from the {@code keys} collection
1402         * holds a {@code null} value.
1403         */
1404        public final void assertKeysNotNull( final Collection<? extends KeyValueType> keys ) throws BuildException
1405        {
1406            if ( keys == null )
1407            {
1408                throw new NullPointerException( "keys" );
1409            }
1410    
1411            for ( KeyValueType k : keys )
1412            {
1413                this.assertNotNull( "key", k.getKey() );
1414            }
1415        }
1416    
1417        /**
1418         * Throws a {@code BuildException} on a {@code null} value of a {@code location} property of a given
1419         * {@code ResourceType} collection.
1420         *
1421         * @param locations The collection holding the {@code ResourceType} instances to test.
1422         *
1423         * @throws NullPointerException if {@code locations} is {@code null}.
1424         * @throws BuildException if a {@code location} property of a given {@code ResourceType} from the {@code locations}
1425         * collection holds a {@code null} value.
1426         */
1427        public final void assertLocationsNotNull( final Collection<? extends ResourceType> locations )
1428            throws BuildException
1429        {
1430            if ( locations == null )
1431            {
1432                throw new NullPointerException( "locations" );
1433            }
1434    
1435            for ( ResourceType r : locations )
1436            {
1437                assertNotNull( "location", r.getLocation() );
1438    
1439                if ( r instanceof TransformerResourceType )
1440                {
1441                    assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationParameters() );
1442                    assertLocationsNotNull( ( (TransformerResourceType) r ).getTransformationParameterResources() );
1443                    assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationOutputProperties() );
1444                }
1445            }
1446        }
1447    
1448        /** Logs a separator string. */
1449        public final void logSeparator()
1450        {
1451            this.log( Messages.getMessage( "separator" ) );
1452        }
1453    
1454        /**
1455         * Logs a message at a given level.
1456         *
1457         * @param level The level to log at.
1458         * @param message The message to log.
1459         *
1460         * @throws BuildException if logging fails.
1461         */
1462        public final void logMessage( final Level level, final String message ) throws BuildException
1463        {
1464            BufferedReader reader = null;
1465            boolean suppressExceptionOnClose = true;
1466    
1467            try
1468            {
1469                String line = null;
1470                reader = new BufferedReader( new StringReader( message ) );
1471    
1472                while ( ( line = reader.readLine() ) != null )
1473                {
1474                    if ( level.intValue() >= Level.SEVERE.intValue() )
1475                    {
1476                        log( line, Project.MSG_ERR );
1477                    }
1478                    else if ( level.intValue() >= Level.WARNING.intValue() )
1479                    {
1480                        log( line, Project.MSG_WARN );
1481                    }
1482                    else if ( level.intValue() >= Level.INFO.intValue() )
1483                    {
1484                        log( line, Project.MSG_INFO );
1485                    }
1486                    else
1487                    {
1488                        log( line, Project.MSG_DEBUG );
1489                    }
1490                }
1491    
1492                suppressExceptionOnClose = false;
1493            }
1494            catch ( final IOException e )
1495            {
1496                throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1497            }
1498            finally
1499            {
1500                try
1501                {
1502                    if ( reader != null )
1503                    {
1504                        reader.close();
1505                    }
1506                }
1507                catch ( final IOException e )
1508                {
1509                    if ( suppressExceptionOnClose )
1510                    {
1511                        this.log( e, Project.MSG_ERR );
1512                    }
1513                    else
1514                    {
1515                        throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1516                    }
1517                }
1518            }
1519        }
1520    
1521        /**
1522         * Logs a message at a given level.
1523         *
1524         * @param level The level to log at.
1525         * @param message The message to log.
1526         * @param throwable The throwable to log.
1527         *
1528         * @throws BuildException if logging fails.
1529         */
1530        public final void logMessage( final Level level, final String message, final Throwable throwable )
1531            throws BuildException
1532        {
1533            this.logMessage( level, message );
1534    
1535            if ( level.intValue() >= Level.SEVERE.intValue() )
1536            {
1537                log( throwable, Project.MSG_ERR );
1538            }
1539            else if ( level.intValue() >= Level.WARNING.intValue() )
1540            {
1541                log( throwable, Project.MSG_WARN );
1542            }
1543            else if ( level.intValue() >= Level.INFO.intValue() )
1544            {
1545                log( throwable, Project.MSG_INFO );
1546            }
1547            else
1548            {
1549                log( throwable, Project.MSG_DEBUG );
1550            }
1551        }
1552    
1553        /**
1554         * Logs a validation report.
1555         *
1556         * @param context The context to use for logging the report.
1557         * @param report The report to log.
1558         *
1559         * @throws NullPointerException if {@code context} or {@code report} is {@code null}.
1560         * @throws BuildException if logging fails.
1561         */
1562        public final void logValidationReport( final ModelContext context, final ModelValidationReport report )
1563        {
1564            try
1565            {
1566                if ( !report.getDetails().isEmpty() )
1567                {
1568                    this.logSeparator();
1569                    Marshaller marshaller = null;
1570    
1571                    for ( ModelValidationReport.Detail detail : report.getDetails() )
1572                    {
1573                        this.logMessage( detail.getLevel(), "o " + detail.getMessage() );
1574    
1575                        if ( detail.getElement() != null )
1576                        {
1577                            if ( marshaller == null )
1578                            {
1579                                marshaller = context.createMarshaller( this.getModel() );
1580                                marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
1581                            }
1582    
1583                            final StringWriter stringWriter = new StringWriter();
1584                            marshaller.marshal( detail.getElement(), stringWriter );
1585                            this.logMessage( Level.FINEST, stringWriter.toString() );
1586                        }
1587                    }
1588                }
1589            }
1590            catch ( final ModelException e )
1591            {
1592                throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1593            }
1594            catch ( final JAXBException e )
1595            {
1596                String message = Messages.getMessage( e );
1597                if ( message == null && e.getLinkedException() != null )
1598                {
1599                    message = Messages.getMessage( e.getLinkedException() );
1600                }
1601    
1602                throw new BuildException( message, e, this.getLocation() );
1603            }
1604        }
1605    
1606        /**
1607         * Creates and returns a copy of this object.
1608         *
1609         * @return A copy of this object.
1610         */
1611        @Override
1612        public JomcTask clone()
1613        {
1614            try
1615            {
1616                final JomcTask clone = (JomcTask) super.clone();
1617                clone.classpath = (Path) ( this.classpath != null ? this.classpath.clone() : null );
1618    
1619                if ( this.modelContextAttributes != null )
1620                {
1621                    clone.modelContextAttributes = new ArrayList<KeyValueType>( this.modelContextAttributes.size() );
1622    
1623                    for ( KeyValueType e : this.modelContextAttributes )
1624                    {
1625                        clone.modelContextAttributes.add( e.clone() );
1626                    }
1627                }
1628    
1629                if ( this.transformationParameters != null )
1630                {
1631                    clone.transformationParameters =
1632                        new ArrayList<KeyValueType>( this.transformationParameters.size() );
1633    
1634                    for ( KeyValueType e : this.transformationParameters )
1635                    {
1636                        clone.transformationParameters.add( e.clone() );
1637                    }
1638                }
1639    
1640                if ( this.transformationParameterResources != null )
1641                {
1642                    clone.transformationParameterResources =
1643                        new ArrayList<PropertiesResourceType>( this.transformationParameterResources.size() );
1644    
1645                    for ( PropertiesResourceType e : this.transformationParameterResources )
1646                    {
1647                        clone.transformationParameterResources.add( e.clone() );
1648                    }
1649                }
1650    
1651                if ( this.transformationOutputProperties != null )
1652                {
1653                    clone.transformationOutputProperties =
1654                        new ArrayList<KeyValueType>( this.transformationOutputProperties.size() );
1655    
1656                    for ( KeyValueType e : this.transformationOutputProperties )
1657                    {
1658                        clone.transformationOutputProperties.add( e.clone() );
1659                    }
1660                }
1661    
1662                return clone;
1663            }
1664            catch ( final CloneNotSupportedException e )
1665            {
1666                throw new AssertionError( e );
1667            }
1668        }
1669    
1670    }