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