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: DefaultModelContext.java 4469 2012-04-01 00:12:58Z schulte2005 $
029     *
030     */
031    package org.jomc.modlet;
032    
033    import java.io.BufferedReader;
034    import java.io.File;
035    import java.io.FileInputStream;
036    import java.io.IOException;
037    import java.io.InputStream;
038    import java.io.InputStreamReader;
039    import java.io.Reader;
040    import java.lang.ref.Reference;
041    import java.lang.ref.SoftReference;
042    import java.lang.reflect.InvocationTargetException;
043    import java.lang.reflect.Method;
044    import java.lang.reflect.Modifier;
045    import java.net.URI;
046    import java.net.URISyntaxException;
047    import java.net.URL;
048    import java.text.MessageFormat;
049    import java.util.ArrayList;
050    import java.util.Collection;
051    import java.util.Comparator;
052    import java.util.Enumeration;
053    import java.util.HashMap;
054    import java.util.HashSet;
055    import java.util.List;
056    import java.util.Map;
057    import java.util.ResourceBundle;
058    import java.util.Set;
059    import java.util.StringTokenizer;
060    import java.util.TreeMap;
061    import java.util.jar.Attributes;
062    import java.util.jar.Manifest;
063    import java.util.logging.Level;
064    import javax.xml.XMLConstants;
065    import javax.xml.bind.JAXBContext;
066    import javax.xml.bind.JAXBException;
067    import javax.xml.bind.Marshaller;
068    import javax.xml.bind.Unmarshaller;
069    import javax.xml.transform.Source;
070    import javax.xml.transform.sax.SAXSource;
071    import javax.xml.validation.SchemaFactory;
072    import javax.xml.validation.Validator;
073    import org.w3c.dom.ls.LSInput;
074    import org.w3c.dom.ls.LSResourceResolver;
075    import org.xml.sax.EntityResolver;
076    import org.xml.sax.ErrorHandler;
077    import org.xml.sax.InputSource;
078    import org.xml.sax.SAXException;
079    import org.xml.sax.SAXParseException;
080    import org.xml.sax.helpers.DefaultHandler;
081    
082    /**
083     * Default {@code ModelContext} implementation.
084     *
085     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
086     * @version $JOMC: DefaultModelContext.java 4469 2012-04-01 00:12:58Z schulte2005 $
087     * @see ModelContextFactory
088     */
089    public class DefaultModelContext extends ModelContext
090    {
091    
092        /**
093         * Constant for the name of the model context attribute backing property {@code providerLocation}.
094         * @see #getProviderLocation()
095         * @see ModelContext#getAttribute(java.lang.String)
096         * @since 1.2
097         */
098        public static final String PROVIDER_LOCATION_ATTRIBUTE_NAME =
099            "org.jomc.modlet.DefaultModelContext.providerLocationAttribute";
100    
101        /**
102         * Constant for the name of the model context attribute backing property {@code platformProviderLocation}.
103         * @see #getPlatformProviderLocation()
104         * @see ModelContext#getAttribute(java.lang.String)
105         * @since 1.2
106         */
107        public static final String PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME =
108            "org.jomc.modlet.DefaultModelContext.platformProviderLocationAttribute";
109    
110        /** Supported schema name extensions. */
111        private static final String[] SCHEMA_EXTENSIONS = new String[]
112        {
113            "xsd"
114        };
115    
116        /**
117         * Class path location searched for providers by default.
118         * @see #getDefaultProviderLocation()
119         */
120        private static final String DEFAULT_PROVIDER_LOCATION = "META-INF/services";
121    
122        /**
123         * Location searched for platform providers by default.
124         * @see #getDefaultPlatformProviderLocation()
125         */
126        private static final String DEFAULT_PLATFORM_PROVIDER_LOCATION =
127            new StringBuilder( 255 ).append( System.getProperty( "java.home" ) ).append( File.separator ).append( "lib" ).
128            append( File.separator ).append( "jomc.properties" ).toString();
129    
130        /**
131         * Constant for the service identifier of marshaller listener services.
132         * @since 1.2
133         */
134        private static final String MARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Marshaller.Listener";
135    
136        /**
137         * Constant for the service identifier of unmarshaller listener services.
138         * @since 1.2
139         */
140        private static final String UNMARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Unmarshaller.Listener";
141    
142        /** Default provider location. */
143        private static volatile String defaultProviderLocation;
144    
145        /** Default platform provider location. */
146        private static volatile String defaultPlatformProviderLocation;
147    
148        /** Cached schema resources. */
149        private Reference<Set<URI>> cachedSchemaResources = new SoftReference<Set<URI>>( null );
150    
151        /** Provider location of the instance. */
152        private String providerLocation;
153    
154        /** Platform provider location of the instance. */
155        private String platformProviderLocation;
156    
157        /**
158         * Creates a new {@code DefaultModelContext} instance.
159         * @since 1.2
160         */
161        public DefaultModelContext()
162        {
163            super();
164        }
165    
166        /**
167         * Creates a new {@code DefaultModelContext} instance taking a class loader.
168         *
169         * @param classLoader The class loader of the context.
170         */
171        public DefaultModelContext( final ClassLoader classLoader )
172        {
173            super( classLoader );
174        }
175    
176        /**
177         * Gets the default location searched for provider resources.
178         * <p>The default provider location is controlled by system property
179         * {@code org.jomc.modlet.DefaultModelContext.defaultProviderLocation} holding the location to search
180         * for provider resources by default. If that property is not set, the {@code META-INF/services} default is
181         * returned.</p>
182         *
183         * @return The location searched for provider resources by default.
184         *
185         * @see #setDefaultProviderLocation(java.lang.String)
186         */
187        public static String getDefaultProviderLocation()
188        {
189            if ( defaultProviderLocation == null )
190            {
191                defaultProviderLocation = System.getProperty(
192                    "org.jomc.modlet.DefaultModelContext.defaultProviderLocation", DEFAULT_PROVIDER_LOCATION );
193    
194            }
195    
196            return defaultProviderLocation;
197        }
198    
199        /**
200         * Sets the default location searched for provider resources.
201         *
202         * @param value The new default location to search for provider resources or {@code null}.
203         *
204         * @see #getDefaultProviderLocation()
205         */
206        public static void setDefaultProviderLocation( final String value )
207        {
208            defaultProviderLocation = value;
209        }
210    
211        /**
212         * Gets the location searched for provider resources.
213         *
214         * @return The location searched for provider resources.
215         *
216         * @see #getDefaultProviderLocation()
217         * @see #setProviderLocation(java.lang.String)
218         * @see #PROVIDER_LOCATION_ATTRIBUTE_NAME
219         */
220        public final String getProviderLocation()
221        {
222            if ( this.providerLocation == null )
223            {
224                this.providerLocation = getDefaultProviderLocation();
225    
226                if ( DEFAULT_PROVIDER_LOCATION.equals( this.providerLocation )
227                     && this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
228                {
229                    final String contextProviderLocation = (String) this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME );
230    
231                    if ( this.isLoggable( Level.CONFIG ) )
232                    {
233                        this.log( Level.CONFIG, getMessage( "contextProviderLocationInfo",
234                                                            contextProviderLocation ), null );
235                    }
236    
237                    this.providerLocation = null;
238                    return contextProviderLocation;
239                }
240                else if ( this.isLoggable( Level.CONFIG ) )
241                {
242                    this.log( Level.CONFIG, getMessage( "defaultProviderLocationInfo", this.providerLocation ), null );
243                }
244            }
245    
246            return this.providerLocation;
247        }
248    
249        /**
250         * Sets the location searched for provider resources.
251         *
252         * @param value The new location to search for provider resources or {@code null}.
253         *
254         * @see #getProviderLocation()
255         */
256        public final void setProviderLocation( final String value )
257        {
258            this.providerLocation = value;
259        }
260    
261        /**
262         * Gets the default location searched for platform provider resources.
263         * <p>The default platform provider location is controlled by system property
264         * {@code org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation} holding the location to
265         * search for platform provider resources by default. If that property is not set, the
266         * {@code <java-home>/lib/jomc.properties} default is returned.</p>
267         *
268         * @return The location searched for platform provider resources by default.
269         *
270         * @see #setDefaultPlatformProviderLocation(java.lang.String)
271         */
272        public static String getDefaultPlatformProviderLocation()
273        {
274            if ( defaultPlatformProviderLocation == null )
275            {
276                defaultPlatformProviderLocation = System.getProperty(
277                    "org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation",
278                    DEFAULT_PLATFORM_PROVIDER_LOCATION );
279    
280            }
281    
282            return defaultPlatformProviderLocation;
283        }
284    
285        /**
286         * Sets the default location searched for platform provider resources.
287         *
288         * @param value The new default location to search for platform provider resources or {@code null}.
289         *
290         * @see #getDefaultPlatformProviderLocation()
291         */
292        public static void setDefaultPlatformProviderLocation( final String value )
293        {
294            defaultPlatformProviderLocation = value;
295        }
296    
297        /**
298         * Gets the location searched for platform provider resources.
299         *
300         * @return The location searched for platform provider resources.
301         *
302         * @see #getDefaultPlatformProviderLocation()
303         * @see #setPlatformProviderLocation(java.lang.String)
304         * @see #PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME
305         */
306        public final String getPlatformProviderLocation()
307        {
308            if ( this.platformProviderLocation == null )
309            {
310                this.platformProviderLocation = getDefaultPlatformProviderLocation();
311    
312                if ( DEFAULT_PLATFORM_PROVIDER_LOCATION.equals( this.platformProviderLocation )
313                     && this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
314                {
315                    final String contextPlatformProviderLocation =
316                        (String) this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME );
317    
318                    if ( this.isLoggable( Level.CONFIG ) )
319                    {
320                        this.log( Level.CONFIG, getMessage( "contextPlatformProviderLocationInfo",
321                                                            contextPlatformProviderLocation ), null );
322    
323                    }
324    
325                    this.platformProviderLocation = null;
326                    return contextPlatformProviderLocation;
327                }
328                else if ( this.isLoggable( Level.CONFIG ) )
329                {
330                    this.log( Level.CONFIG,
331                              getMessage( "defaultPlatformProviderLocationInfo", this.platformProviderLocation ), null );
332    
333                }
334            }
335    
336            return this.platformProviderLocation;
337        }
338    
339        /**
340         * Sets the location searched for platform provider resources.
341         *
342         * @param value The new location to search for platform provider resources or {@code null}.
343         *
344         * @see #getPlatformProviderLocation()
345         */
346        public final void setPlatformProviderLocation( final String value )
347        {
348            this.platformProviderLocation = value;
349        }
350    
351        /**
352         * {@inheritDoc}
353         * <p>This method loads {@code ModletProvider} classes setup via the platform provider configuration file and
354         * {@code <provider-location>/org.jomc.modlet.ModletProvider} resources to return a list of {@code Modlets}.</p>
355         *
356         * @see #getProviderLocation()
357         * @see #getPlatformProviderLocation()
358         * @see ModletProvider#findModlets(org.jomc.modlet.ModelContext)
359         */
360        @Override
361        public Modlets findModlets() throws ModelException
362        {
363            final Modlets modlets = new Modlets();
364            final Collection<ModletProvider> providers = this.loadProviders( ModletProvider.class );
365    
366            for ( ModletProvider provider : providers )
367            {
368                if ( this.isLoggable( Level.FINER ) )
369                {
370                    this.log( Level.FINER, getMessage( "creatingModlets", provider.toString() ), null );
371                }
372    
373                final Modlets provided = provider.findModlets( this );
374    
375                if ( provided != null )
376                {
377                    if ( this.isLoggable( Level.FINEST ) )
378                    {
379                        for ( Modlet m : provided.getModlet() )
380                        {
381                            this.log( Level.FINEST,
382                                      getMessage( "modletInfo", m.getName(), m.getModel(),
383                                                  m.getVendor() != null
384                                                  ? m.getVendor() : getMessage( "noVendor" ),
385                                                  m.getVersion() != null
386                                                  ? m.getVersion() : getMessage( "noVersion" ) ), null );
387    
388                            if ( m.getSchemas() != null )
389                            {
390                                for ( Schema s : m.getSchemas().getSchema() )
391                                {
392                                    this.log( Level.FINEST,
393                                              getMessage( "modletSchemaInfo", m.getName(), s.getPublicId(), s.getSystemId(),
394                                                          s.getContextId() != null
395                                                          ? s.getContextId() : getMessage( "noContext" ),
396                                                          s.getClasspathId() != null
397                                                          ? s.getClasspathId() : getMessage( "noClasspathId" ) ), null );
398    
399                                }
400                            }
401    
402                            if ( m.getServices() != null )
403                            {
404                                for ( Service s : m.getServices().getService() )
405                                {
406                                    this.log( Level.FINEST, getMessage( "modletServiceInfo", m.getName(), s.getOrdinal(),
407                                                                        s.getIdentifier(), s.getClazz() ), null );
408    
409                                }
410                            }
411                        }
412                    }
413    
414                    modlets.getModlet().addAll( provided.getModlet() );
415                }
416            }
417    
418            return modlets;
419        }
420    
421        /**
422         * {@inheritDoc}
423         * <p>This method loads all {@code ModelProvider} service classes of the model identified by {@code model} to create
424         * a new {@code Model} instance.</p>
425         *
426         * @see #findModel(org.jomc.modlet.Model)
427         * @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
428         */
429        @Override
430        public Model findModel( final String model ) throws ModelException
431        {
432            if ( model == null )
433            {
434                throw new NullPointerException( "model" );
435            }
436    
437            final Model m = new Model();
438            m.setIdentifier( model );
439    
440            return this.findModel( m );
441        }
442    
443        /**
444         * {@inheritDoc}
445         * <p>This method loads all {@code ModelProvider} service classes of the given model to populate the given model
446         * instance.</p>
447         *
448         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
449         * @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
450         *
451         * @since 1.2
452         */
453        @Override
454        public Model findModel( final Model model ) throws ModelException
455        {
456            if ( model == null )
457            {
458                throw new NullPointerException( "model" );
459            }
460    
461            Model m = model.clone();
462            final Services services = this.getModlets().getServices( m.getIdentifier() );
463    
464            if ( services != null )
465            {
466                for ( Service service : services.getServices( ModelProvider.class ) )
467                {
468                    final ModelProvider modelProvider = this.createServiceObject( service, ModelProvider.class );
469    
470                    if ( this.isLoggable( Level.FINER ) )
471                    {
472                        this.log( Level.FINER, getMessage( "creatingModel", m.getIdentifier(), modelProvider.toString() ),
473                                  null );
474    
475                    }
476    
477                    final Model provided = modelProvider.findModel( this, m );
478    
479                    if ( provided != null )
480                    {
481                        m = provided;
482                    }
483                }
484            }
485    
486            return m;
487        }
488    
489        /**
490         * {@inheritDoc}
491         * @since 1.2
492         */
493        @Override
494        public <T> T createServiceObject( final Service service, final Class<T> type ) throws ModelException
495        {
496            if ( service == null )
497            {
498                throw new NullPointerException( "service" );
499            }
500            if ( type == null )
501            {
502                throw new NullPointerException( "type" );
503            }
504    
505            try
506            {
507                final Class<?> clazz = this.findClass( service.getClazz() );
508    
509                if ( clazz == null )
510                {
511                    throw new ModelException( getMessage( "serviceNotFound", service.getOrdinal(), service.getIdentifier(),
512                                                          service.getClazz() ) );
513    
514                }
515    
516                if ( !type.isAssignableFrom( clazz ) )
517                {
518                    throw new ModelException( getMessage( "illegalService", service.getOrdinal(), service.getIdentifier(),
519                                                          service.getClazz(), type.getName() ) );
520    
521                }
522    
523                final T serviceObject = clazz.asSubclass( type ).newInstance();
524    
525                for ( int i = 0, s0 = service.getProperty().size(); i < s0; i++ )
526                {
527                    final Property p = service.getProperty().get( i );
528                    this.setProperty( serviceObject, p.getName(), p.getValue() );
529                }
530    
531                return serviceObject;
532            }
533            catch ( final InstantiationException e )
534            {
535                throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
536            }
537            catch ( final IllegalAccessException e )
538            {
539                throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
540            }
541        }
542    
543        @Override
544        public EntityResolver createEntityResolver( final String model ) throws ModelException
545        {
546            if ( model == null )
547            {
548                throw new NullPointerException( "model" );
549            }
550    
551            return this.createEntityResolver( this.getModlets().getSchemas( model ) );
552        }
553    
554        @Override
555        public EntityResolver createEntityResolver( final URI publicId ) throws ModelException
556        {
557            if ( publicId == null )
558            {
559                throw new NullPointerException( "publicId" );
560            }
561    
562            return this.createEntityResolver( this.getModlets().getSchemas( publicId ) );
563        }
564    
565        @Override
566        public LSResourceResolver createResourceResolver( final String model ) throws ModelException
567        {
568            if ( model == null )
569            {
570                throw new NullPointerException( "model" );
571            }
572    
573            return this.createResourceResolver( this.createEntityResolver( model ) );
574        }
575    
576        @Override
577        public LSResourceResolver createResourceResolver( final URI publicId ) throws ModelException
578        {
579            if ( publicId == null )
580            {
581                throw new NullPointerException( "publicId" );
582            }
583    
584            return this.createResourceResolver( this.createEntityResolver( publicId ) );
585        }
586    
587        @Override
588        public javax.xml.validation.Schema createSchema( final String model ) throws ModelException
589        {
590            if ( model == null )
591            {
592                throw new NullPointerException( "model" );
593            }
594    
595            return this.createSchema( this.getModlets().getSchemas( model ), this.createEntityResolver( model ),
596                                      this.createResourceResolver( model ), model, null );
597    
598        }
599    
600        @Override
601        public javax.xml.validation.Schema createSchema( final URI publicId ) throws ModelException
602        {
603            if ( publicId == null )
604            {
605                throw new NullPointerException( "publicId" );
606            }
607    
608            return this.createSchema( this.getModlets().getSchemas( publicId ), this.createEntityResolver( publicId ),
609                                      this.createResourceResolver( publicId ), null, publicId );
610    
611        }
612    
613        @Override
614        public JAXBContext createContext( final String model ) throws ModelException
615        {
616            if ( model == null )
617            {
618                throw new NullPointerException( "model" );
619            }
620    
621            return this.createContext( this.getModlets().getSchemas( model ), model, null );
622        }
623    
624        @Override
625        public JAXBContext createContext( final URI publicId ) throws ModelException
626        {
627            if ( publicId == null )
628            {
629                throw new NullPointerException( "publicId" );
630            }
631    
632            return this.createContext( this.getModlets().getSchemas( publicId ), null, publicId );
633        }
634    
635        @Override
636        public Marshaller createMarshaller( final String model ) throws ModelException
637        {
638            if ( model == null )
639            {
640                throw new NullPointerException( "model" );
641            }
642    
643            return this.createMarshaller( this.getModlets().getSchemas( model ), this.getModlets().getServices( model ),
644                                          model, null );
645    
646        }
647    
648        @Override
649        public Marshaller createMarshaller( final URI publicId ) throws ModelException
650        {
651            if ( publicId == null )
652            {
653                throw new NullPointerException( "publicId" );
654            }
655    
656            return this.createMarshaller( this.getModlets().getSchemas( publicId ), null, null, publicId );
657        }
658    
659        @Override
660        public Unmarshaller createUnmarshaller( final String model ) throws ModelException
661        {
662            if ( model == null )
663            {
664                throw new NullPointerException( "model" );
665            }
666    
667            return this.createUnmarshaller( this.getModlets().getSchemas( model ), this.getModlets().getServices( model ),
668                                            model, null );
669    
670        }
671    
672        @Override
673        public Unmarshaller createUnmarshaller( final URI publicId ) throws ModelException
674        {
675            if ( publicId == null )
676            {
677                throw new NullPointerException( "publicId" );
678            }
679    
680            return this.createUnmarshaller( this.getModlets().getSchemas( publicId ), null, null, publicId );
681        }
682    
683        /**
684         * {@inheritDoc}
685         * <p>This method loads all {@code ModelProcessor} service classes of {@code model} to process the given
686         * {@code Model}.</p>
687         *
688         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
689         * @see ModelProcessor#processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
690         */
691        @Override
692        public Model processModel( final Model model ) throws ModelException
693        {
694            if ( model == null )
695            {
696                throw new NullPointerException( "model" );
697            }
698    
699            Model processed = model;
700            final Services services = this.getModlets().getServices( model.getIdentifier() );
701    
702            if ( services != null )
703            {
704                for ( Service service : services.getServices( ModelProcessor.class ) )
705                {
706                    final ModelProcessor modelProcessor = this.createServiceObject( service, ModelProcessor.class );
707    
708                    if ( this.isLoggable( Level.FINER ) )
709                    {
710                        this.log( Level.FINER, getMessage( "processingModel", model.getIdentifier(),
711                                                           modelProcessor.toString() ), null );
712    
713                    }
714    
715                    final Model current = modelProcessor.processModel( this, processed );
716    
717                    if ( current != null )
718                    {
719                        processed = current;
720                    }
721                }
722            }
723    
724            return processed;
725        }
726    
727        /**
728         * {@inheritDoc}
729         * <p>This method loads all {@code ModelValidator} service classes of {@code model} to validate the given
730         * {@code Model}.</p>
731         *
732         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
733         * @see ModelValidator#validateModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
734         */
735        @Override
736        public ModelValidationReport validateModel( final Model model ) throws ModelException
737        {
738            if ( model == null )
739            {
740                throw new NullPointerException( "model" );
741            }
742    
743            final Services services = this.getModlets().getServices( model.getIdentifier() );
744            final ModelValidationReport report = new ModelValidationReport();
745    
746            if ( services != null )
747            {
748                for ( Service service : services.getServices( ModelValidator.class ) )
749                {
750                    final ModelValidator modelValidator = this.createServiceObject( service, ModelValidator.class );
751    
752                    if ( this.isLoggable( Level.FINER ) )
753                    {
754                        this.log( Level.FINER, getMessage( "validatingModel", model.getIdentifier(),
755                                                           modelValidator.toString() ), null );
756    
757                    }
758    
759                    final ModelValidationReport current = modelValidator.validateModel( this, model );
760    
761                    if ( current != null )
762                    {
763                        report.getDetails().addAll( current.getDetails() );
764                    }
765                }
766            }
767    
768            return report;
769        }
770    
771        /**
772         * {@inheritDoc}
773         *
774         * @see #createSchema(java.lang.String)
775         */
776        @Override
777        public ModelValidationReport validateModel( final String model, final Source source ) throws ModelException
778        {
779            if ( model == null )
780            {
781                throw new NullPointerException( "model" );
782            }
783            if ( source == null )
784            {
785                throw new NullPointerException( "source" );
786            }
787    
788            final javax.xml.validation.Schema schema = this.createSchema( model );
789            final Validator validator = schema.newValidator();
790            final ModelErrorHandler modelErrorHandler = new ModelErrorHandler( this );
791            validator.setErrorHandler( modelErrorHandler );
792    
793            try
794            {
795                validator.validate( source );
796            }
797            catch ( final SAXException e )
798            {
799                String message = getMessage( e );
800                if ( message == null && e.getException() != null )
801                {
802                    message = getMessage( e.getException() );
803                }
804    
805                if ( this.isLoggable( Level.FINE ) )
806                {
807                    this.log( Level.FINE, message, e );
808                }
809    
810                if ( modelErrorHandler.getReport().isModelValid() )
811                {
812                    throw new ModelException( message, e );
813                }
814            }
815            catch ( final IOException e )
816            {
817                throw new ModelException( getMessage( e ), e );
818            }
819    
820            return modelErrorHandler.getReport();
821        }
822    
823        private <T> Collection<T> loadProviders( final Class<T> providerClass ) throws ModelException
824        {
825            try
826            {
827                final String providerNamePrefix = providerClass.getName() + ".";
828                final Map<String, T> providers = new TreeMap<String, T>( new Comparator<String>()
829                {
830    
831                    public int compare( final String key1, final String key2 )
832                    {
833                        return key1.compareTo( key2 );
834                    }
835    
836                } );
837    
838                final File platformProviders = new File( this.getPlatformProviderLocation() );
839    
840                if ( platformProviders.exists() )
841                {
842                    if ( this.isLoggable( Level.FINEST ) )
843                    {
844                        this.log( Level.FINEST, getMessage( "processing", platformProviders.getAbsolutePath() ), null );
845                    }
846    
847                    InputStream in = null;
848                    boolean suppressExceptionOnClose = true;
849                    final java.util.Properties p = new java.util.Properties();
850    
851                    try
852                    {
853                        in = new FileInputStream( platformProviders );
854                        p.load( in );
855                        suppressExceptionOnClose = false;
856                    }
857                    finally
858                    {
859                        try
860                        {
861                            if ( in != null )
862                            {
863                                in.close();
864                            }
865                        }
866                        catch ( final IOException e )
867                        {
868                            if ( suppressExceptionOnClose )
869                            {
870                                this.log( Level.SEVERE, getMessage( e ), e );
871                            }
872                            else
873                            {
874                                throw e;
875                            }
876                        }
877                    }
878    
879                    for ( Map.Entry<Object, Object> e : p.entrySet() )
880                    {
881                        if ( e.getKey().toString().startsWith( providerNamePrefix ) )
882                        {
883                            final String configuration = e.getValue().toString();
884    
885                            if ( this.isLoggable( Level.FINEST ) )
886                            {
887                                this.log( Level.FINEST, getMessage( "providerInfo", platformProviders.getAbsolutePath(),
888                                                                    providerClass.getName(), configuration ), null );
889    
890                            }
891    
892                            providers.put( e.getKey().toString(),
893                                           this.createProviderObject( providerClass, configuration,
894                                                                      platformProviders.toURI().toURL() ) );
895    
896                        }
897                    }
898                }
899    
900                final Enumeration<URL> classpathProviders =
901                    this.findResources( this.getProviderLocation() + '/' + providerClass.getName() );
902    
903                int count = 0;
904                final long t0 = System.currentTimeMillis();
905    
906                while ( classpathProviders.hasMoreElements() )
907                {
908                    count++;
909                    final URL url = classpathProviders.nextElement();
910    
911                    if ( this.isLoggable( Level.FINEST ) )
912                    {
913                        this.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
914                    }
915    
916                    BufferedReader reader = null;
917                    boolean suppressExceptionOnClose = true;
918    
919                    try
920                    {
921                        reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) );
922    
923                        String line = null;
924                        while ( ( line = reader.readLine() ) != null )
925                        {
926                            if ( line.contains( "#" ) )
927                            {
928                                continue;
929                            }
930    
931                            if ( this.isLoggable( Level.FINEST ) )
932                            {
933                                this.log( Level.FINEST, getMessage( "providerInfo", url.toExternalForm(),
934                                                                    providerClass.getName(), line ), null );
935    
936                            }
937    
938                            providers.put( providerNamePrefix + providers.size(),
939                                           this.createProviderObject( providerClass, line, url ) );
940    
941                        }
942    
943                        suppressExceptionOnClose = false;
944                    }
945                    finally
946                    {
947                        try
948                        {
949                            if ( reader != null )
950                            {
951                                reader.close();
952                            }
953                        }
954                        catch ( final IOException e )
955                        {
956                            if ( suppressExceptionOnClose )
957                            {
958                                this.log( Level.SEVERE, getMessage( e ), e );
959                            }
960                            else
961                            {
962                                throw new ModelException( getMessage( e ), e );
963                            }
964                        }
965                    }
966                }
967    
968                if ( this.isLoggable( Level.FINE ) )
969                {
970                    this.log( Level.FINE, getMessage( "contextReport", count,
971                                                      this.getProviderLocation() + '/' + providerClass.getName(),
972                                                      Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
973    
974                }
975    
976                return providers.values();
977            }
978            catch ( final IOException e )
979            {
980                throw new ModelException( getMessage( e ), e );
981            }
982        }
983    
984        private <T> T createProviderObject( final Class<T> providerClass, final String configuration, final URL location )
985            throws ModelException
986        {
987            String className = configuration;
988    
989            try
990            {
991                final Map<String, String> properties = new HashMap<String, String>();
992                final int i0 = configuration.indexOf( '[' );
993                final int i1 = configuration.lastIndexOf( ']' );
994    
995                if ( i0 != -1 && i1 != -1 )
996                {
997                    className = configuration.substring( 0, i0 );
998                    final StringTokenizer propertyTokens =
999                        new StringTokenizer( configuration.substring( i0 + 1, i1 ), "," );
1000    
1001                    while ( propertyTokens.hasMoreTokens() )
1002                    {
1003                        final String property = propertyTokens.nextToken();
1004                        final int d0 = property.indexOf( '=' );
1005    
1006                        String propertyName = property;
1007                        String propertyValue = null;
1008    
1009                        if ( d0 != -1 )
1010                        {
1011                            propertyName = property.substring( 0, d0 );
1012                            propertyValue = property.substring( d0 + 1, property.length() );
1013                        }
1014    
1015                        properties.put( propertyName, propertyValue );
1016                    }
1017                }
1018    
1019                final Class<?> provider = this.findClass( className );
1020    
1021                if ( provider == null )
1022                {
1023                    throw new ModelException( getMessage( "implementationNotFound", providerClass.getName(), className,
1024                                                          location.toExternalForm() ) );
1025    
1026                }
1027    
1028                if ( !providerClass.isAssignableFrom( provider ) )
1029                {
1030                    throw new ModelException( getMessage( "illegalImplementation", providerClass.getName(), className,
1031                                                          location.toExternalForm() ) );
1032    
1033                }
1034    
1035                final T o = provider.asSubclass( providerClass ).newInstance();
1036    
1037                for ( final Map.Entry<String, String> property : properties.entrySet() )
1038                {
1039                    this.setProperty( o, property.getKey(), property.getValue() );
1040                }
1041    
1042                return o;
1043            }
1044            catch ( final InstantiationException e )
1045            {
1046                throw new ModelException( getMessage( "failedCreatingObject", className ), e );
1047            }
1048            catch ( final IllegalAccessException e )
1049            {
1050                throw new ModelException( getMessage( "failedCreatingObject", className ), e );
1051            }
1052        }
1053    
1054        private <T> void setProperty( final T object, final String propertyName, final String propertyValue )
1055            throws ModelException
1056        {
1057            if ( object == null )
1058            {
1059                throw new NullPointerException( "object" );
1060            }
1061            if ( propertyName == null )
1062            {
1063                throw new NullPointerException( "propertyName" );
1064            }
1065    
1066            try
1067            {
1068                final char[] chars = propertyName.toCharArray();
1069    
1070                if ( Character.isLowerCase( chars[0] ) )
1071                {
1072                    chars[0] = Character.toUpperCase( chars[0] );
1073                }
1074    
1075                final String methodNameSuffix = String.valueOf( chars );
1076                Method getterMethod = null;
1077    
1078                try
1079                {
1080                    getterMethod = object.getClass().getMethod( "get" + methodNameSuffix );
1081                }
1082                catch ( final NoSuchMethodException e )
1083                {
1084                    if ( this.isLoggable( Level.FINEST ) )
1085                    {
1086                        this.log( Level.FINEST, null, e );
1087                    }
1088    
1089                    getterMethod = null;
1090                }
1091    
1092                if ( getterMethod == null )
1093                {
1094                    try
1095                    {
1096                        getterMethod = object.getClass().getMethod( "is" + methodNameSuffix );
1097                    }
1098                    catch ( final NoSuchMethodException e )
1099                    {
1100                        if ( this.isLoggable( Level.FINEST ) )
1101                        {
1102                            this.log( Level.FINEST, null, e );
1103                        }
1104    
1105                        getterMethod = null;
1106                    }
1107                }
1108    
1109                if ( getterMethod == null )
1110                {
1111                    throw new ModelException( getMessage( "getterMethodNotFound", object.getClass().getName(),
1112                                                          propertyName ) );
1113    
1114                }
1115    
1116                final Class<?> propertyType = getterMethod.getReturnType();
1117                Class<?> boxedPropertyType = propertyType;
1118    
1119                if ( Boolean.TYPE.equals( propertyType ) )
1120                {
1121                    boxedPropertyType = Boolean.class;
1122                }
1123                else if ( Character.TYPE.equals( propertyType ) )
1124                {
1125                    boxedPropertyType = Character.class;
1126                }
1127                else if ( Byte.TYPE.equals( propertyType ) )
1128                {
1129                    boxedPropertyType = Byte.class;
1130                }
1131                else if ( Short.TYPE.equals( propertyType ) )
1132                {
1133                    boxedPropertyType = Short.class;
1134                }
1135                else if ( Integer.TYPE.equals( propertyType ) )
1136                {
1137                    boxedPropertyType = Integer.class;
1138                }
1139                else if ( Long.TYPE.equals( propertyType ) )
1140                {
1141                    boxedPropertyType = Long.class;
1142                }
1143                else if ( Float.TYPE.equals( propertyType ) )
1144                {
1145                    boxedPropertyType = Float.class;
1146                }
1147                else if ( Double.TYPE.equals( propertyType ) )
1148                {
1149                    boxedPropertyType = Double.class;
1150                }
1151    
1152                Method setterMethod = null;
1153    
1154                try
1155                {
1156                    setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, propertyType );
1157                }
1158                catch ( final NoSuchMethodException e )
1159                {
1160                    if ( this.isLoggable( Level.FINEST ) )
1161                    {
1162                        this.log( Level.FINEST, null, e );
1163                    }
1164    
1165                    setterMethod = null;
1166                }
1167    
1168                if ( setterMethod == null )
1169                {
1170                    throw new ModelException( getMessage( "setterMethodNotFound", object.getClass().getName(),
1171                                                          propertyName ) );
1172    
1173                }
1174    
1175                if ( boxedPropertyType.equals( Character.class ) )
1176                {
1177                    if ( propertyValue == null || propertyValue.length() != 1 )
1178                    {
1179                        throw new ModelException( getMessage( "unsupportedCharacterValue", object.getClass().getName(),
1180                                                              propertyName ) );
1181    
1182                    }
1183    
1184                    setterMethod.invoke( object, Character.valueOf( propertyValue.charAt( 0 ) ) );
1185                    return;
1186                }
1187    
1188                if ( propertyValue != null )
1189                {
1190                    if ( propertyType.equals( String.class ) )
1191                    {
1192                        setterMethod.invoke( object, propertyValue );
1193                        return;
1194                    }
1195    
1196                    try
1197                    {
1198                        setterMethod.invoke(
1199                            object, boxedPropertyType.getConstructor( String.class ).newInstance( propertyValue ) );
1200    
1201                        return;
1202                    }
1203                    catch ( final NoSuchMethodException e )
1204                    {
1205                        if ( this.isLoggable( Level.FINEST ) )
1206                        {
1207                            this.log( Level.FINEST, null, e );
1208                        }
1209                    }
1210    
1211                    try
1212                    {
1213                        final Method valueOf = boxedPropertyType.getMethod( "valueOf", String.class );
1214    
1215                        if ( Modifier.isStatic( valueOf.getModifiers() )
1216                             && ( valueOf.getReturnType().equals( propertyType )
1217                                  || valueOf.getReturnType().equals( boxedPropertyType ) ) )
1218                        {
1219                            setterMethod.invoke( object, valueOf.invoke( null, propertyValue ) );
1220                            return;
1221                        }
1222                    }
1223                    catch ( final NoSuchMethodException e )
1224                    {
1225                        if ( this.isLoggable( Level.FINEST ) )
1226                        {
1227                            this.log( Level.FINEST, null, e );
1228                        }
1229                    }
1230    
1231                    throw new ModelException( getMessage( "unsupportedPropertyType", object.getClass().getName(),
1232                                                          propertyName, propertyType.getName() ) );
1233    
1234                }
1235                else
1236                {
1237                    setterMethod.invoke( object, (Object) null );
1238                }
1239            }
1240            catch ( final IllegalAccessException e )
1241            {
1242                throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1243                                                      object.getClass().getName() ), e );
1244    
1245            }
1246            catch ( final InvocationTargetException e )
1247            {
1248                throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1249                                                      object.getClass().getName() ), e );
1250    
1251            }
1252            catch ( final InstantiationException e )
1253            {
1254                throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1255                                                      object.getClass().getName() ), e );
1256    
1257            }
1258        }
1259    
1260        /**
1261         * Searches the context for {@code META-INF/MANIFEST.MF} resources and returns a set of URIs of entries whose names
1262         * end with a known schema extension.
1263         *
1264         * @return Set of URIs of any matching entries.
1265         *
1266         * @throws IOException if reading fails.
1267         * @throws URISyntaxException if parsing fails.
1268         * @throws ModelException if searching the context fails.
1269         */
1270        private Set<URI> getSchemaResources() throws IOException, URISyntaxException, ModelException
1271        {
1272            Set<URI> resources = this.cachedSchemaResources.get();
1273    
1274            if ( resources == null )
1275            {
1276                resources = new HashSet<URI>();
1277                final long t0 = System.currentTimeMillis();
1278                int count = 0;
1279    
1280                for ( final Enumeration<URL> e = this.findResources( "META-INF/MANIFEST.MF" );
1281                      e.hasMoreElements(); )
1282                {
1283                    InputStream manifestStream = null;
1284                    boolean suppressExceptionOnClose = true;
1285    
1286                    try
1287                    {
1288                        count++;
1289                        final URL manifestUrl = e.nextElement();
1290                        final String externalForm = manifestUrl.toExternalForm();
1291                        final String baseUrl = externalForm.substring( 0, externalForm.indexOf( "META-INF" ) );
1292                        manifestStream = manifestUrl.openStream();
1293                        final Manifest mf = new Manifest( manifestStream );
1294    
1295                        if ( this.isLoggable( Level.FINEST ) )
1296                        {
1297                            this.log( Level.FINEST, getMessage( "processing", externalForm ), null );
1298                        }
1299    
1300                        for ( Map.Entry<String, Attributes> entry : mf.getEntries().entrySet() )
1301                        {
1302                            for ( int i = SCHEMA_EXTENSIONS.length - 1; i >= 0; i-- )
1303                            {
1304                                if ( entry.getKey().toLowerCase().endsWith( '.' + SCHEMA_EXTENSIONS[i].toLowerCase() ) )
1305                                {
1306                                    final URL schemaUrl = new URL( baseUrl + entry.getKey() );
1307                                    resources.add( schemaUrl.toURI() );
1308    
1309                                    if ( this.isLoggable( Level.FINEST ) )
1310                                    {
1311                                        this.log( Level.FINEST, getMessage( "foundSchemaCandidate",
1312                                                                            schemaUrl.toExternalForm() ), null );
1313    
1314                                    }
1315                                }
1316                            }
1317                        }
1318    
1319                        suppressExceptionOnClose = false;
1320                    }
1321                    finally
1322                    {
1323                        try
1324                        {
1325                            if ( manifestStream != null )
1326                            {
1327                                manifestStream.close();
1328                            }
1329                        }
1330                        catch ( final IOException ex )
1331                        {
1332                            if ( suppressExceptionOnClose )
1333                            {
1334                                this.log( Level.SEVERE, getMessage( ex ), ex );
1335                            }
1336                            else
1337                            {
1338                                throw ex;
1339                            }
1340                        }
1341                    }
1342                }
1343    
1344                if ( this.isLoggable( Level.FINE ) )
1345                {
1346                    this.log( Level.FINE, getMessage( "contextReport", count, "META-INF/MANIFEST.MF",
1347                                                      Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
1348    
1349                }
1350    
1351                this.cachedSchemaResources = new SoftReference<Set<URI>>( resources );
1352            }
1353    
1354            return resources;
1355        }
1356    
1357        private EntityResolver createEntityResolver( final Schemas schemas )
1358        {
1359            return new DefaultHandler()
1360            {
1361    
1362                @Override
1363                public InputSource resolveEntity( final String publicId, final String systemId )
1364                    throws SAXException, IOException
1365                {
1366                    if ( systemId == null )
1367                    {
1368                        throw new NullPointerException( "systemId" );
1369                    }
1370    
1371                    InputSource schemaSource = null;
1372    
1373                    try
1374                    {
1375                        Schema s = null;
1376    
1377                        if ( schemas != null )
1378                        {
1379                            s = schemas.getSchemaBySystemId( systemId );
1380    
1381                            if ( s == null && publicId != null )
1382                            {
1383                                try
1384                                {
1385                                    final List<Schema> schemasByPublicId =
1386                                        schemas.getSchemasByPublicId( new URI( publicId ) );
1387    
1388                                    if ( schemasByPublicId.size() == 1 )
1389                                    {
1390                                        s = schemasByPublicId.get( 0 );
1391                                    }
1392                                }
1393                                catch ( final URISyntaxException e )
1394                                {
1395                                    if ( isLoggable( Level.WARNING ) )
1396                                    {
1397                                        log( Level.WARNING, getMessage( "unsupportedIdUri", publicId, getMessage( e ) ),
1398                                             null );
1399    
1400                                    }
1401    
1402                                    s = null;
1403                                }
1404                            }
1405                        }
1406    
1407                        if ( s != null )
1408                        {
1409                            schemaSource = new InputSource();
1410                            schemaSource.setPublicId( s.getPublicId() != null ? s.getPublicId() : publicId );
1411                            schemaSource.setSystemId( s.getSystemId() );
1412    
1413                            if ( s.getClasspathId() != null )
1414                            {
1415                                final URL resource = findResource( s.getClasspathId() );
1416    
1417                                if ( resource != null )
1418                                {
1419                                    schemaSource.setSystemId( resource.toExternalForm() );
1420                                }
1421                                else if ( isLoggable( Level.WARNING ) )
1422                                {
1423                                    log( Level.WARNING, getMessage( "resourceNotFound", s.getClasspathId() ), null );
1424                                }
1425                            }
1426    
1427                            if ( isLoggable( Level.FINEST ) )
1428                            {
1429                                log( Level.FINEST, getMessage( "resolutionInfo", publicId + ", " + systemId,
1430                                                               schemaSource.getPublicId() + ", "
1431                                                               + schemaSource.getSystemId() ), null );
1432    
1433                            }
1434                        }
1435    
1436                        if ( schemaSource == null )
1437                        {
1438                            final URI systemUri = new URI( systemId );
1439                            String schemaName = systemUri.getPath();
1440    
1441                            if ( schemaName != null )
1442                            {
1443                                final int lastIndexOfSlash = schemaName.lastIndexOf( '/' );
1444                                if ( lastIndexOfSlash != -1 && lastIndexOfSlash < schemaName.length() )
1445                                {
1446                                    schemaName = schemaName.substring( lastIndexOfSlash + 1 );
1447                                }
1448    
1449                                for ( URI uri : getSchemaResources() )
1450                                {
1451                                    if ( uri.getSchemeSpecificPart() != null
1452                                         && uri.getSchemeSpecificPart().endsWith( schemaName ) )
1453                                    {
1454                                        schemaSource = new InputSource();
1455                                        schemaSource.setPublicId( publicId );
1456                                        schemaSource.setSystemId( uri.toASCIIString() );
1457    
1458                                        if ( isLoggable( Level.FINEST ) )
1459                                        {
1460                                            log( Level.FINEST, getMessage( "resolutionInfo", systemUri.toASCIIString(),
1461                                                                           schemaSource.getSystemId() ), null );
1462    
1463                                        }
1464    
1465                                        break;
1466                                    }
1467                                }
1468                            }
1469                            else
1470                            {
1471                                if ( isLoggable( Level.WARNING ) )
1472                                {
1473                                    log( Level.WARNING, getMessage( "unsupportedIdUri", systemId,
1474                                                                    systemUri.toASCIIString() ), null );
1475    
1476                                }
1477    
1478                                schemaSource = null;
1479                            }
1480                        }
1481                    }
1482                    catch ( final URISyntaxException e )
1483                    {
1484                        if ( isLoggable( Level.WARNING ) )
1485                        {
1486                            log( Level.WARNING, getMessage( "unsupportedIdUri", systemId, getMessage( e ) ), null );
1487                        }
1488    
1489                        schemaSource = null;
1490                    }
1491                    catch ( final ModelException e )
1492                    {
1493                        String message = getMessage( e );
1494                        if ( message == null )
1495                        {
1496                            message = "";
1497                        }
1498                        else if ( message.length() > 0 )
1499                        {
1500                            message = " " + message;
1501                        }
1502    
1503                        String resource = "";
1504                        if ( publicId != null )
1505                        {
1506                            resource = publicId + ", ";
1507                        }
1508                        resource += systemId;
1509    
1510                        // JDK: As of JDK 6, "new IOException( message, cause )".
1511                        throw (IOException) new IOException( getMessage(
1512                            "failedResolving", resource, message ) ).initCause( e );
1513    
1514                    }
1515    
1516                    return schemaSource;
1517                }
1518    
1519            };
1520        }
1521    
1522        private LSResourceResolver createResourceResolver( final EntityResolver entityResolver )
1523        {
1524            if ( entityResolver == null )
1525            {
1526                throw new NullPointerException( "entityResolver" );
1527            }
1528    
1529            return new LSResourceResolver()
1530            {
1531    
1532                public LSInput resolveResource( final String type, final String namespaceURI, final String publicId,
1533                                                final String systemId, final String baseURI )
1534                {
1535                    final String resolvePublicId = namespaceURI == null ? publicId : namespaceURI;
1536                    final String resolveSystemId = systemId == null ? "" : systemId;
1537    
1538                    try
1539                    {
1540                        if ( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) )
1541                        {
1542                            final InputSource schemaSource =
1543                                entityResolver.resolveEntity( resolvePublicId, resolveSystemId );
1544    
1545                            if ( schemaSource != null )
1546                            {
1547                                return new LSInput()
1548                                {
1549    
1550                                    public Reader getCharacterStream()
1551                                    {
1552                                        return schemaSource.getCharacterStream();
1553                                    }
1554    
1555                                    public void setCharacterStream( final Reader characterStream )
1556                                    {
1557                                        if ( isLoggable( Level.WARNING ) )
1558                                        {
1559                                            log( Level.WARNING, getMessage(
1560                                                "unsupportedOperation", "setCharacterStream",
1561                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1562    
1563                                        }
1564                                    }
1565    
1566                                    public InputStream getByteStream()
1567                                    {
1568                                        return schemaSource.getByteStream();
1569                                    }
1570    
1571                                    public void setByteStream( final InputStream byteStream )
1572                                    {
1573                                        if ( isLoggable( Level.WARNING ) )
1574                                        {
1575                                            log( Level.WARNING, getMessage(
1576                                                "unsupportedOperation", "setByteStream",
1577                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1578    
1579                                        }
1580                                    }
1581    
1582                                    public String getStringData()
1583                                    {
1584                                        return null;
1585                                    }
1586    
1587                                    public void setStringData( final String stringData )
1588                                    {
1589                                        if ( isLoggable( Level.WARNING ) )
1590                                        {
1591                                            log( Level.WARNING, getMessage(
1592                                                "unsupportedOperation", "setStringData",
1593                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1594    
1595                                        }
1596                                    }
1597    
1598                                    public String getSystemId()
1599                                    {
1600                                        return schemaSource.getSystemId();
1601                                    }
1602    
1603                                    public void setSystemId( final String systemId )
1604                                    {
1605                                        if ( isLoggable( Level.WARNING ) )
1606                                        {
1607                                            log( Level.WARNING, getMessage(
1608                                                "unsupportedOperation", "setSystemId",
1609                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1610    
1611                                        }
1612                                    }
1613    
1614                                    public String getPublicId()
1615                                    {
1616                                        return schemaSource.getPublicId();
1617                                    }
1618    
1619                                    public void setPublicId( final String publicId )
1620                                    {
1621                                        if ( isLoggable( Level.WARNING ) )
1622                                        {
1623                                            log( Level.WARNING, getMessage(
1624                                                "unsupportedOperation", "setPublicId",
1625                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1626    
1627                                        }
1628                                    }
1629    
1630                                    public String getBaseURI()
1631                                    {
1632                                        return baseURI;
1633                                    }
1634    
1635                                    public void setBaseURI( final String baseURI )
1636                                    {
1637                                        if ( isLoggable( Level.WARNING ) )
1638                                        {
1639                                            log( Level.WARNING, getMessage(
1640                                                "unsupportedOperation", "setBaseURI",
1641                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1642    
1643                                        }
1644                                    }
1645    
1646                                    public String getEncoding()
1647                                    {
1648                                        return schemaSource.getEncoding();
1649                                    }
1650    
1651                                    public void setEncoding( final String encoding )
1652                                    {
1653                                        if ( isLoggable( Level.WARNING ) )
1654                                        {
1655                                            log( Level.WARNING, getMessage(
1656                                                "unsupportedOperation", "setEncoding",
1657                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1658    
1659                                        }
1660                                    }
1661    
1662                                    public boolean getCertifiedText()
1663                                    {
1664                                        return false;
1665                                    }
1666    
1667                                    public void setCertifiedText( final boolean certifiedText )
1668                                    {
1669                                        if ( isLoggable( Level.WARNING ) )
1670                                        {
1671                                            log( Level.WARNING, getMessage(
1672                                                "unsupportedOperation", "setCertifiedText",
1673                                                DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1674    
1675                                        }
1676                                    }
1677    
1678                                };
1679                            }
1680    
1681                        }
1682                        else if ( isLoggable( Level.WARNING ) )
1683                        {
1684                            log( Level.WARNING, getMessage( "unsupportedResourceType", type ), null );
1685                        }
1686                    }
1687                    catch ( final SAXException e )
1688                    {
1689                        String message = getMessage( e );
1690                        if ( message == null && e.getException() != null )
1691                        {
1692                            message = getMessage( e.getException() );
1693                        }
1694                        if ( message == null )
1695                        {
1696                            message = "";
1697                        }
1698                        else if ( message.length() > 0 )
1699                        {
1700                            message = " " + message;
1701                        }
1702    
1703                        String resource = "";
1704                        if ( resolvePublicId != null )
1705                        {
1706                            resource = resolvePublicId + ", ";
1707                        }
1708                        resource += resolveSystemId;
1709    
1710                        if ( isLoggable( Level.SEVERE ) )
1711                        {
1712                            log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1713                        }
1714                    }
1715                    catch ( final IOException e )
1716                    {
1717                        String message = getMessage( e );
1718                        if ( message == null )
1719                        {
1720                            message = "";
1721                        }
1722                        else if ( message.length() > 0 )
1723                        {
1724                            message = " " + message;
1725                        }
1726    
1727                        String resource = "";
1728                        if ( resolvePublicId != null )
1729                        {
1730                            resource = resolvePublicId + ", ";
1731                        }
1732                        resource += resolveSystemId;
1733    
1734                        if ( isLoggable( Level.SEVERE ) )
1735                        {
1736                            log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1737                        }
1738                    }
1739    
1740                    return null;
1741                }
1742    
1743            };
1744        }
1745    
1746        private javax.xml.validation.Schema createSchema( final Schemas schemas, final EntityResolver entityResolver,
1747                                                          final LSResourceResolver resourceResolver, final String model,
1748                                                          final URI publicId ) throws ModelException
1749        {
1750            if ( entityResolver == null )
1751            {
1752                throw new NullPointerException( "entityResolver" );
1753            }
1754            if ( model != null && publicId != null )
1755            {
1756                throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1757            }
1758    
1759            try
1760            {
1761                final SchemaFactory f = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
1762                final List<Source> sources = new ArrayList<Source>( schemas != null ? schemas.getSchema().size() : 0 );
1763    
1764                if ( schemas != null )
1765                {
1766                    for ( Schema s : schemas.getSchema() )
1767                    {
1768                        final InputSource inputSource = entityResolver.resolveEntity( s.getPublicId(), s.getSystemId() );
1769    
1770                        if ( inputSource != null )
1771                        {
1772                            sources.add( new SAXSource( inputSource ) );
1773                        }
1774                    }
1775                }
1776    
1777                if ( sources.isEmpty() )
1778                {
1779                    if ( model != null )
1780                    {
1781                        throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1782                    }
1783                    if ( publicId != null )
1784                    {
1785                        throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1786                    }
1787                }
1788    
1789                f.setResourceResolver( resourceResolver );
1790                f.setErrorHandler( new ErrorHandler()
1791                {
1792                    // See http://java.net/jira/browse/JAXP-66
1793    
1794                    public void warning( final SAXParseException e ) throws SAXException
1795                    {
1796                        String message = getMessage( e );
1797                        if ( message == null && e.getException() != null )
1798                        {
1799                            message = getMessage( e.getException() );
1800                        }
1801    
1802                        if ( isLoggable( Level.WARNING ) )
1803                        {
1804                            log( Level.WARNING, message, e );
1805                        }
1806                    }
1807    
1808                    public void error( final SAXParseException e ) throws SAXException
1809                    {
1810                        throw e;
1811                    }
1812    
1813                    public void fatalError( final SAXParseException e ) throws SAXException
1814                    {
1815                        throw e;
1816                    }
1817    
1818                } );
1819    
1820                if ( this.isLoggable( Level.FINEST ) )
1821                {
1822                    final StringBuilder schemaInfo = new StringBuilder( sources.size() * 50 );
1823    
1824                    for ( Source s : sources )
1825                    {
1826                        schemaInfo.append( ", " ).append( s.getSystemId() );
1827                    }
1828    
1829                    this.log( Level.FINEST, getMessage( "creatingSchema", schemaInfo.substring( 2 ) ), null );
1830                }
1831    
1832                return f.newSchema( sources.toArray( new Source[ sources.size() ] ) );
1833            }
1834            catch ( final IOException e )
1835            {
1836                throw new ModelException( getMessage( e ), e );
1837            }
1838            catch ( final SAXException e )
1839            {
1840                String message = getMessage( e );
1841                if ( message == null && e.getException() != null )
1842                {
1843                    message = getMessage( e.getException() );
1844                }
1845    
1846                throw new ModelException( message, e );
1847            }
1848        }
1849    
1850        private JAXBContext createContext( final Schemas schemas, final String model, final URI publicId )
1851            throws ModelException
1852        {
1853            if ( model != null && publicId != null )
1854            {
1855                throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1856            }
1857    
1858            try
1859            {
1860                StringBuilder packageNames = null;
1861    
1862                if ( schemas != null )
1863                {
1864                    packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1865    
1866                    for ( Schema schema : schemas.getSchema() )
1867                    {
1868                        if ( schema.getContextId() != null )
1869                        {
1870                            packageNames.append( ':' ).append( schema.getContextId() );
1871                        }
1872                    }
1873                }
1874    
1875                if ( packageNames == null || packageNames.length() == 0 )
1876                {
1877                    if ( model != null )
1878                    {
1879                        throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1880                    }
1881                    if ( publicId != null )
1882                    {
1883                        throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1884                    }
1885                }
1886    
1887                if ( this.isLoggable( Level.FINEST ) )
1888                {
1889                    this.log( Level.FINEST, getMessage( "creatingContext", packageNames.substring( 1 ) ), null );
1890                }
1891    
1892                return JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() );
1893            }
1894            catch ( final JAXBException e )
1895            {
1896                String message = getMessage( e );
1897                if ( message == null && e.getLinkedException() != null )
1898                {
1899                    message = getMessage( e.getLinkedException() );
1900                }
1901    
1902                throw new ModelException( message, e );
1903            }
1904        }
1905    
1906        private Marshaller createMarshaller( final Schemas schemas, final Services services, final String model,
1907                                             final URI publicId ) throws ModelException
1908        {
1909            if ( model != null && publicId != null )
1910            {
1911                throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1912            }
1913    
1914            try
1915            {
1916                StringBuilder packageNames = null;
1917                StringBuilder schemaLocation = null;
1918    
1919                if ( schemas != null )
1920                {
1921                    packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1922                    schemaLocation = new StringBuilder( schemas.getSchema().size() * 50 );
1923    
1924                    for ( Schema schema : schemas.getSchema() )
1925                    {
1926                        if ( schema.getContextId() != null )
1927                        {
1928                            packageNames.append( ':' ).append( schema.getContextId() );
1929                        }
1930                        if ( schema.getPublicId() != null && schema.getSystemId() != null )
1931                        {
1932                            schemaLocation.append( ' ' ).append( schema.getPublicId() ).append( ' ' ).
1933                                append( schema.getSystemId() );
1934    
1935                        }
1936                    }
1937                }
1938    
1939                if ( packageNames == null || packageNames.length() == 0 )
1940                {
1941                    if ( model != null )
1942                    {
1943                        throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1944                    }
1945                    if ( publicId != null )
1946                    {
1947                        throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1948                    }
1949                }
1950    
1951                final Marshaller m =
1952                    JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createMarshaller();
1953    
1954                if ( schemaLocation != null && schemaLocation.length() != 0 )
1955                {
1956                    m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation.substring( 1 ) );
1957                }
1958    
1959                MarshallerListenerList listenerList = null;
1960    
1961                if ( services != null )
1962                {
1963                    for ( Service service : services.getServices( MARSHALLER_LISTENER_SERVICE ) )
1964                    {
1965                        if ( listenerList == null )
1966                        {
1967                            listenerList = new MarshallerListenerList();
1968                        }
1969    
1970                        listenerList.getListeners().add( this.createServiceObject( service, Marshaller.Listener.class ) );
1971                    }
1972                }
1973    
1974                if ( listenerList != null )
1975                {
1976                    m.setListener( listenerList );
1977                }
1978    
1979                if ( this.isLoggable( Level.FINEST ) )
1980                {
1981                    if ( listenerList == null )
1982                    {
1983                        this.log( Level.FINEST, getMessage( "creatingMarshaller", packageNames.substring( 1 ),
1984                                                            schemaLocation.substring( 1 ) ), null );
1985    
1986                    }
1987                    else
1988                    {
1989                        final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
1990    
1991                        for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
1992                        {
1993                            b.append( ',' ).append( listenerList.getListeners().get( i ) );
1994                        }
1995    
1996                        this.log( Level.FINEST, getMessage( "creatingMarshallerWithListeners", packageNames.substring( 1 ),
1997                                                            schemaLocation.substring( 1 ), b.substring( 1 ) ), null );
1998    
1999                    }
2000                }
2001    
2002                return m;
2003            }
2004            catch ( final JAXBException e )
2005            {
2006                String message = getMessage( e );
2007                if ( message == null && e.getLinkedException() != null )
2008                {
2009                    message = getMessage( e.getLinkedException() );
2010                }
2011    
2012                throw new ModelException( message, e );
2013            }
2014        }
2015    
2016        private Unmarshaller createUnmarshaller( final Schemas schemas, final Services services, final String model,
2017                                                 final URI publicId ) throws ModelException
2018        {
2019            if ( model != null && publicId != null )
2020            {
2021                throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
2022            }
2023    
2024            try
2025            {
2026                StringBuilder packageNames = null;
2027    
2028                if ( schemas != null )
2029                {
2030                    packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
2031    
2032                    for ( Schema schema : schemas.getSchema() )
2033                    {
2034                        if ( schema.getContextId() != null )
2035                        {
2036                            packageNames.append( ':' ).append( schema.getContextId() );
2037                        }
2038                    }
2039                }
2040    
2041                if ( packageNames == null || packageNames.length() == 0 )
2042                {
2043                    if ( model != null )
2044                    {
2045                        throw new ModelException( getMessage( "missingSchemasForModel", model ) );
2046                    }
2047                    if ( publicId != null )
2048                    {
2049                        throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
2050                    }
2051                }
2052    
2053                final Unmarshaller u =
2054                    JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createUnmarshaller();
2055    
2056                UnmarshallerListenerList listenerList = null;
2057    
2058                if ( services != null )
2059                {
2060                    for ( Service service : services.getServices( UNMARSHALLER_LISTENER_SERVICE ) )
2061                    {
2062                        if ( listenerList == null )
2063                        {
2064                            listenerList = new UnmarshallerListenerList();
2065                        }
2066    
2067                        listenerList.getListeners().add( this.createServiceObject( service, Unmarshaller.Listener.class ) );
2068                    }
2069                }
2070    
2071                if ( listenerList != null )
2072                {
2073                    u.setListener( listenerList );
2074                }
2075    
2076                if ( this.isLoggable( Level.FINEST ) )
2077                {
2078                    if ( listenerList == null )
2079                    {
2080                        this.log( Level.FINEST,
2081                                  getMessage( "creatingUnmarshaller", packageNames.substring( 1 ) ), null );
2082    
2083                    }
2084                    else
2085                    {
2086                        final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
2087    
2088                        for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
2089                        {
2090                            b.append( ',' ).append( listenerList.getListeners().get( i ) );
2091                        }
2092    
2093                        this.log( Level.FINEST, getMessage( "creatingUnmarshallerWithListeners",
2094                                                            packageNames.substring( 1 ), b.substring( 1 ) ), null );
2095    
2096                    }
2097                }
2098    
2099                return u;
2100            }
2101            catch ( final JAXBException e )
2102            {
2103                String message = getMessage( e );
2104                if ( message == null && e.getLinkedException() != null )
2105                {
2106                    message = getMessage( e.getLinkedException() );
2107                }
2108    
2109                throw new ModelException( message, e );
2110            }
2111        }
2112    
2113        private static String getMessage( final String key, final Object... arguments )
2114        {
2115            return MessageFormat.format( ResourceBundle.getBundle(
2116                DefaultModelContext.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2117    
2118        }
2119    
2120        private static String getMessage( final Throwable t )
2121        {
2122            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2123        }
2124    
2125    }
2126    
2127    /**
2128     * {@code ErrorHandler} collecting {@code ModelValidationReport} details.
2129     *
2130     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
2131     * @version $JOMC: DefaultModelContext.java 4469 2012-04-01 00:12:58Z schulte2005 $
2132     */
2133    class ModelErrorHandler extends DefaultHandler
2134    {
2135    
2136        /** The context of the instance. */
2137        private ModelContext context;
2138    
2139        /** The report of the instance. */
2140        private ModelValidationReport report;
2141    
2142        /**
2143         * Creates a new {@code ModelErrorHandler} instance taking a context.
2144         *
2145         * @param context The context of the instance.
2146         */
2147        ModelErrorHandler( final ModelContext context )
2148        {
2149            this( context, null );
2150        }
2151    
2152        /**
2153         * Creates a new {@code ModelErrorHandler} instance taking a report to use for collecting validation events.
2154         *
2155         * @param context The context of the instance.
2156         * @param report A report to use for collecting validation events.
2157         */
2158        ModelErrorHandler( final ModelContext context, final ModelValidationReport report )
2159        {
2160            super();
2161            this.context = context;
2162            this.report = report;
2163        }
2164    
2165        /**
2166         * Gets the report of the instance.
2167         *
2168         * @return The report of the instance.
2169         */
2170        public ModelValidationReport getReport()
2171        {
2172            if ( this.report == null )
2173            {
2174                this.report = new ModelValidationReport();
2175            }
2176    
2177            return this.report;
2178        }
2179    
2180        @Override
2181        public void warning( final SAXParseException exception ) throws SAXException
2182        {
2183            String message = getMessage( exception );
2184            if ( message == null && exception.getException() != null )
2185            {
2186                message = getMessage( exception.getException() );
2187            }
2188    
2189            if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2190            {
2191                this.context.log( Level.FINE, message, exception );
2192            }
2193    
2194            this.getReport().getDetails().add( new ModelValidationReport.Detail(
2195                "W3C XML 1.0 Recommendation - Warning condition", Level.WARNING, message, null ) );
2196    
2197        }
2198    
2199        @Override
2200        public void error( final SAXParseException exception ) throws SAXException
2201        {
2202            String message = getMessage( exception );
2203            if ( message == null && exception.getException() != null )
2204            {
2205                message = getMessage( exception.getException() );
2206            }
2207    
2208            if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2209            {
2210                this.context.log( Level.FINE, message, exception );
2211            }
2212    
2213            this.getReport().getDetails().add( new ModelValidationReport.Detail(
2214                "W3C XML 1.0 Recommendation - Section 1.2 - Error", Level.SEVERE, message, null ) );
2215    
2216        }
2217    
2218        @Override
2219        public void fatalError( final SAXParseException exception ) throws SAXException
2220        {
2221            String message = getMessage( exception );
2222            if ( message == null && exception.getException() != null )
2223            {
2224                message = getMessage( exception.getException() );
2225            }
2226    
2227            if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2228            {
2229                this.context.log( Level.FINE, message, exception );
2230            }
2231    
2232            this.getReport().getDetails().add( new ModelValidationReport.Detail(
2233                "W3C XML 1.0 Recommendation - Section 1.2 - Fatal Error", Level.SEVERE, message, null ) );
2234    
2235        }
2236    
2237        private static String getMessage( final Throwable t )
2238        {
2239            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2240        }
2241    
2242    }
2243    
2244    /**
2245     * List of {@code Marshaller.Listener}s.
2246     *
2247     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
2248     * @version $JOMC: DefaultModelContext.java 4469 2012-04-01 00:12:58Z schulte2005 $
2249     * @since 1.2
2250     */
2251    class MarshallerListenerList extends Marshaller.Listener
2252    {
2253    
2254        /** The {@code Marshaller.Listener}s of the instance. */
2255        private List<Marshaller.Listener> listeners;
2256    
2257        /** Creates a new {@code MarshallerListenerList} instance. */
2258        MarshallerListenerList()
2259        {
2260            super();
2261        }
2262    
2263        /**
2264         * Gets the listeners of the instance.
2265         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2266         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2267         * listeners property.</p>
2268         *
2269         * @return The list of listeners of the instance.
2270         */
2271        List<Marshaller.Listener> getListeners()
2272        {
2273            if ( this.listeners == null )
2274            {
2275                this.listeners = new ArrayList<Marshaller.Listener>();
2276            }
2277    
2278            return this.listeners;
2279        }
2280    
2281        @Override
2282        public void beforeMarshal( final Object source )
2283        {
2284            for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2285            {
2286                this.getListeners().get( i ).beforeMarshal( source );
2287            }
2288        }
2289    
2290        @Override
2291        public void afterMarshal( final Object source )
2292        {
2293            for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2294            {
2295                this.getListeners().get( i ).afterMarshal( source );
2296            }
2297        }
2298    
2299    }
2300    
2301    /**
2302     * List of {@code Unmarshaller.Listener}s.
2303     *
2304     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
2305     * @version $JOMC: DefaultModelContext.java 4469 2012-04-01 00:12:58Z schulte2005 $
2306     * @since 1.2
2307     */
2308    class UnmarshallerListenerList extends Unmarshaller.Listener
2309    {
2310    
2311        /** The {@code Unmarshaller.Listener}s of the instance. */
2312        private List<Unmarshaller.Listener> listeners;
2313    
2314        /** Creates a new {@code UnmarshallerListenerList} instance. */
2315        UnmarshallerListenerList()
2316        {
2317            super();
2318        }
2319    
2320        /**
2321         * Gets the listeners of the instance.
2322         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2323         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2324         * listeners property.</p>
2325         *
2326         * @return The list of listeners of the instance.
2327         */
2328        List<Unmarshaller.Listener> getListeners()
2329        {
2330            if ( this.listeners == null )
2331            {
2332                this.listeners = new ArrayList<Unmarshaller.Listener>();
2333            }
2334    
2335            return this.listeners;
2336        }
2337    
2338        @Override
2339        public void beforeUnmarshal( final Object target, final Object parent )
2340        {
2341            for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2342            {
2343                this.getListeners().get( i ).beforeUnmarshal( target, parent );
2344            }
2345        }
2346    
2347        @Override
2348        public void afterUnmarshal( final Object target, final Object parent )
2349        {
2350            for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2351            {
2352                this.getListeners().get( i ).afterUnmarshal( target, parent );
2353            }
2354        }
2355    
2356    }