001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
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: ProjectClassLoader.java 5303 2016-08-30 02:31:20Z schulte $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.BufferedReader;
034import java.io.BufferedWriter;
035import java.io.Closeable;
036import java.io.File;
037import java.io.FileOutputStream;
038import java.io.IOException;
039import java.io.InputStream;
040import java.io.InputStreamReader;
041import java.io.OutputStream;
042import java.io.OutputStreamWriter;
043import java.net.MalformedURLException;
044import java.net.URI;
045import java.net.URISyntaxException;
046import java.net.URL;
047import java.net.URLClassLoader;
048import java.util.Collections;
049import java.util.Enumeration;
050import java.util.HashSet;
051import java.util.Iterator;
052import java.util.LinkedList;
053import java.util.List;
054import java.util.Set;
055import javax.xml.bind.JAXBElement;
056import javax.xml.bind.JAXBException;
057import org.apache.tools.ant.Project;
058import org.apache.tools.ant.types.Path;
059import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
060import org.jomc.modlet.ModelContext;
061import org.jomc.modlet.ModelContextFactory;
062import org.jomc.modlet.ModelException;
063import org.jomc.modlet.Modlet;
064import org.jomc.modlet.ModletObject;
065import org.jomc.modlet.Modlets;
066import org.jomc.modlet.ObjectFactory;
067import org.jomc.modlet.Schema;
068import org.jomc.modlet.Schemas;
069import org.jomc.modlet.Service;
070import org.jomc.modlet.Services;
071import org.jomc.util.ParseException;
072import org.jomc.util.TokenMgrError;
073import org.jomc.util.VersionParser;
074
075/**
076 * Class loader supporting JOMC resources backed by a project.
077 *
078 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
079 * @version $JOMC: ProjectClassLoader.java 5303 2016-08-30 02:31:20Z schulte $
080 */
081public class ProjectClassLoader extends URLClassLoader
082{
083
084    /**
085     * Constant to prefix relative resource names with.
086     */
087    private static final String ABSOLUTE_RESOURCE_NAME_PREFIX =
088        "/" + ProjectClassLoader.class.getPackage().getName().replace( '.', '/' ) + "/";
089
090    /**
091     * Empty URL array.
092     */
093    private static final URL[] NO_URLS =
094    {
095    };
096
097    /**
098     * Set of modlet names to exclude.
099     */
100    private Set<String> modletExcludes;
101
102    /**
103     * Excluded modlets.
104     */
105    private final Modlets excludedModlets = new Modlets();
106
107    /**
108     * Set of service class names to exclude.
109     */
110    private Set<String> serviceExcludes;
111
112    /**
113     * Excluded services.
114     */
115    private final Services excludedServices = new Services();
116
117    /**
118     * Set of schema public ids to exclude.
119     */
120    private Set<String> schemaExcludes;
121
122    /**
123     * Excluded schemas.
124     */
125    private final Schemas excludedSchemas = new Schemas();
126
127    /**
128     * Set of providers to exclude.
129     */
130    private Set<String> providerExcludes;
131
132    /**
133     * Set of excluded providers.
134     */
135    private final Set<String> excludedProviders = Collections.synchronizedSet( new HashSet<String>( 128 ) );
136
137    /**
138     * The project the class loader is associated with.
139     */
140    private final Project project;
141
142    /**
143     * Set of modlet resource locations to filter.
144     */
145    private Set<String> modletResourceLocations;
146
147    /**
148     * Set of provider resource locations to filter.
149     */
150    private Set<String> providerResourceLocations;
151
152    /**
153     * Set of temporary resources.
154     */
155    private final Set<File> temporaryResources = Collections.synchronizedSet( new HashSet<File>( 128 ) );
156
157    /**
158     * Creates a new {@code ProjectClassLoader} instance taking a project and a class path.
159     *
160     * @param project The project to which this class loader is to belong.
161     * @param classpath The class path to use for loading.
162     *
163     * @throws MalformedURLException if {@code classpath} contains unsupported elements.
164     * @throws IOException if reading configuration resources fails.
165     */
166    public ProjectClassLoader( final Project project, final Path classpath ) throws MalformedURLException, IOException
167    {
168        super( NO_URLS, ProjectClassLoader.class.getClassLoader() );
169
170        for ( final String name : classpath.list() )
171        {
172            final File resolved = project.resolveFile( name );
173            this.addURL( resolved.toURI().toURL() );
174        }
175
176        this.project = project;
177    }
178
179    /**
180     * Gets the project of the instance.
181     *
182     * @return The project of the instance.
183     */
184    public final Project getProject()
185    {
186        return this.project;
187    }
188
189    /**
190     * Finds a resource with a given name.
191     *
192     * @param name The name of the resource to search.
193     *
194     * @return An {@code URL} object for reading the resource or {@code null}, if no resource matching {@code name} is
195     * found.
196     */
197    @Override
198    public URL findResource( final String name ) //JDK: As of JDK 23 throws IOException
199    {
200        try
201        {
202            URL resource = super.findResource( name );
203
204            if ( resource != null )
205            {
206                if ( this.getProviderResourceLocations().contains( name ) )
207                {
208                    resource = this.filterProviders( resource );
209                }
210                else if ( this.getModletResourceLocations().contains( name ) )
211                {
212                    resource = this.filterModlets( resource );
213                }
214            }
215
216            return resource;
217        }
218        catch ( final IOException e )
219        {
220            this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
221            return null;
222        }
223        catch ( final JAXBException e )
224        {
225            String message = Messages.getMessage( e );
226            if ( message == null && e.getLinkedException() != null )
227            {
228                message = Messages.getMessage( e.getLinkedException() );
229            }
230
231            this.getProject().log( message, Project.MSG_ERR );
232            return null;
233        }
234        catch ( final ModelException e )
235        {
236            this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
237            return null;
238        }
239    }
240
241    /**
242     * Finds all resources matching a given name.
243     *
244     * @param name The name of the resources to search.
245     *
246     * @return An enumeration of {@code URL} objects of resources matching name.
247     *
248     * @throws IOException if getting resources fails.
249     */
250    @Override
251    public Enumeration<URL> findResources( final String name ) throws IOException
252    {
253        try
254        {
255            Enumeration<URL> resources = super.findResources( name );
256
257            if ( this.getProviderResourceLocations().contains( name )
258                     || this.getModletResourceLocations().contains( name ) )
259            {
260                final List<URI> filtered = new LinkedList<URI>();
261
262                while ( resources.hasMoreElements() )
263                {
264                    final URL resource = resources.nextElement();
265
266                    if ( this.getProviderResourceLocations().contains( name ) )
267                    {
268                        filtered.add( this.filterProviders( resource ).toURI() );
269                    }
270                    else if ( this.getModletResourceLocations().contains( name ) )
271                    {
272                        filtered.add( this.filterModlets( resource ).toURI() );
273                    }
274                }
275
276                final Iterator<URI> it = filtered.iterator();
277
278                resources = new Enumeration<URL>()
279                {
280
281                    public boolean hasMoreElements()
282                    {
283                        return it.hasNext();
284                    }
285
286                    public URL nextElement()
287                    {
288                        try
289                        {
290                            return it.next().toURL();
291                        }
292                        catch ( final MalformedURLException e )
293                        {
294                            throw new AssertionError( e );
295                        }
296                    }
297
298                };
299            }
300
301            return resources;
302        }
303        catch ( final URISyntaxException e )
304        {
305            // JDK: As of JDK 6, new IOException( message, e );
306            throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
307        }
308        catch ( final JAXBException e )
309        {
310            String message = Messages.getMessage( e );
311            if ( message == null && e.getLinkedException() != null )
312            {
313                message = Messages.getMessage( e.getLinkedException() );
314            }
315
316            // JDK: As of JDK 6, new IOException( message, e );
317            throw (IOException) new IOException( message ).initCause( e );
318        }
319        catch ( final ModelException e )
320        {
321            // JDK: As of JDK 6, new IOException( message, e );
322            throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
323        }
324    }
325
326    /**
327     * Gets a set of modlet resource locations to filter.
328     * <p>
329     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
330     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
331     * modlet resource locations property.
332     * </p>
333     *
334     * @return A set of modlet resource locations to filter.
335     */
336    public final Set<String> getModletResourceLocations()
337    {
338        if ( this.modletResourceLocations == null )
339        {
340            this.modletResourceLocations = new HashSet<String>( 128 );
341        }
342
343        return this.modletResourceLocations;
344    }
345
346    /**
347     * Gets a set of provider resource locations to filter.
348     * <p>
349     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
350     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
351     * provider resource locations property.
352     * </p>
353     *
354     * @return A set of provider resource locations to filter.
355     */
356    public final Set<String> getProviderResourceLocations()
357    {
358        if ( this.providerResourceLocations == null )
359        {
360            this.providerResourceLocations = new HashSet<String>( 128 );
361        }
362
363        return this.providerResourceLocations;
364    }
365
366    /**
367     * Gets a set of modlet names to exclude.
368     * <p>
369     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
370     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
371     * modlet excludes property.
372     * </p>
373     *
374     * @return A set of modlet names to exclude.
375     */
376    public final Set<String> getModletExcludes()
377    {
378        if ( this.modletExcludes == null )
379        {
380            this.modletExcludes = new HashSet<String>( 128 );
381        }
382
383        return this.modletExcludes;
384    }
385
386    /**
387     * Gets a set of modlet names excluded by default.
388     *
389     * @return An unmodifiable set of modlet names excluded by default.
390     *
391     * @throws IOException if reading configuration resources fails.
392     */
393    public static Set<String> getDefaultModletExcludes() throws IOException
394    {
395        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultModletExcludes" );
396    }
397
398    /**
399     * Gets a set of modlets excluded during resource loading.
400     * <p>
401     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
402     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
403     * excluded modlets property.
404     * </p>
405     *
406     * @return A set of modlets excluded during resource loading.
407     */
408    public final Modlets getExcludedModlets()
409    {
410        return this.excludedModlets;
411    }
412
413    /**
414     * Gets a set of provider names to exclude.
415     * <p>
416     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
417     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
418     * provider excludes property.
419     * </p>
420     *
421     * @return A set of providers to exclude.
422     */
423    public final Set<String> getProviderExcludes()
424    {
425        if ( this.providerExcludes == null )
426        {
427            this.providerExcludes = new HashSet<String>( 128 );
428        }
429
430        return this.providerExcludes;
431    }
432
433    /**
434     * Gets a set of provider names excluded by default.
435     *
436     * @return An unmodifiable set of provider names excluded by default.
437     *
438     * @throws IOException if reading configuration resources fails.
439     */
440    public static Set<String> getDefaultProviderExcludes() throws IOException
441    {
442        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultProviderExcludes" );
443    }
444
445    /**
446     * Gets a set of providers excluded during resource loading.
447     * <p>
448     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
449     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
450     * excluded providers property.
451     * </p>
452     *
453     * @return A set of providers excluded during resource loading.
454     */
455    public final Set<String> getExcludedProviders()
456    {
457        return this.excludedProviders;
458    }
459
460    /**
461     * Gets a set of service class names to exclude.
462     * <p>
463     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
464     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
465     * service excludes property.
466     * </p>
467     *
468     * @return A set of service class names to exclude.
469     */
470    public final Set<String> getServiceExcludes()
471    {
472        if ( this.serviceExcludes == null )
473        {
474            this.serviceExcludes = new HashSet<String>( 128 );
475        }
476
477        return this.serviceExcludes;
478    }
479
480    /**
481     * Gets a set of service class names excluded by default.
482     *
483     * @return An unmodifiable set of service class names excluded by default.
484     *
485     * @throws IOException if reading configuration resources fails.
486     */
487    public static Set<String> getDefaultServiceExcludes() throws IOException
488    {
489        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultServiceExcludes" );
490    }
491
492    /**
493     * Gets a set of services excluded during resource loading.
494     * <p>
495     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
496     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
497     * excluded services property.
498     * </p>
499     *
500     * @return Services excluded during resource loading.
501     */
502    public final Services getExcludedServices()
503    {
504        return this.excludedServices;
505    }
506
507    /**
508     * Gets a set of schema public identifiers to exclude.
509     * <p>
510     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
511     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
512     * schema excludes property.
513     * </p>
514     *
515     * @return A set of schema public identifiers to exclude.
516     */
517    public final Set<String> getSchemaExcludes()
518    {
519        if ( this.schemaExcludes == null )
520        {
521            this.schemaExcludes = new HashSet<String>( 128 );
522        }
523
524        return this.schemaExcludes;
525    }
526
527    /**
528     * Gets a set of schema public identifiers excluded by default.
529     *
530     * @return An unmodifiable set of schema public identifiers excluded by default.
531     *
532     * @throws IOException if reading configuration resources fails.
533     */
534    public static Set<String> getDefaultSchemaExcludes() throws IOException
535    {
536        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultSchemaExcludes" );
537    }
538
539    /**
540     * Gets a set of schemas excluded during resource loading.
541     * <p>
542     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
543     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
544     * excluded schemas property.
545     * </p>
546     *
547     * @return Schemas excluded during resource loading.
548     */
549    public final Schemas getExcludedSchemas()
550    {
551        return this.excludedSchemas;
552    }
553
554    /**
555     * Closes the class loader.
556     *
557     * @throws IOException if closing the class loader fails.
558     */
559    @Override
560    @IgnoreJRERequirement
561    public void close() throws IOException
562    {
563        for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
564        {
565            final File temporaryResource = it.next();
566
567            if ( temporaryResource.exists() && temporaryResource.delete() )
568            {
569                it.remove();
570            }
571        }
572
573        if ( Closeable.class.isAssignableFrom( ProjectClassLoader.class ) )
574        {
575            super.close();
576        }
577    }
578
579    /**
580     * Removes temporary resources.
581     *
582     * @throws Throwable if finalization fails.
583     */
584    @Override
585    protected void finalize() throws Throwable
586    {
587        for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
588        {
589            final File temporaryResource = it.next();
590
591            if ( temporaryResource.exists() && !temporaryResource.delete() )
592            {
593                temporaryResource.deleteOnExit();
594            }
595
596            it.remove();
597        }
598
599        super.finalize();
600    }
601
602    private URL filterProviders( final URL resource ) throws IOException
603    {
604        InputStream in = null;
605        BufferedReader reader = null;
606        OutputStream out = null;
607        BufferedWriter writer = null;
608        URL filteredResource = resource;
609        final List<String> filteredLines = new LinkedList<String>();
610
611        try
612        {
613            boolean filtered = false;
614            in = resource.openStream();
615            reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
616
617            for ( String line = reader.readLine(); line != null; line = reader.readLine() )
618            {
619                String normalized = line.trim();
620
621                if ( !this.getProviderExcludes().contains( normalized ) )
622                {
623                    filteredLines.add( normalized );
624                }
625                else
626                {
627                    filtered = true;
628                    this.getExcludedProviders().add( normalized );
629                    this.getProject().log( Messages.getMessage( "providerExclusion", resource.toExternalForm(),
630                                                                line.trim() ), Project.MSG_DEBUG );
631
632                }
633            }
634
635            reader.close();
636            reader = null;
637            in = null;
638
639            if ( filtered )
640            {
641                final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
642                this.temporaryResources.add( tmpResource );
643
644                out = new FileOutputStream( tmpResource );
645                writer = new BufferedWriter( new OutputStreamWriter( out, "UTF-8" ) );
646
647                for ( final String line : filteredLines )
648                {
649                    writer.write( line );
650                    writer.newLine();
651                }
652
653                writer.close();
654                writer = null;
655                out = null;
656
657                filteredResource = tmpResource.toURI().toURL();
658            }
659
660            return filteredResource;
661        }
662        finally
663        {
664            try
665            {
666                if ( reader != null )
667                {
668                    reader.close();
669                }
670            }
671            catch ( final IOException e )
672            {
673                this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
674            }
675            finally
676            {
677                try
678                {
679                    if ( in != null )
680                    {
681                        in.close();
682                    }
683                }
684                catch ( final IOException e )
685                {
686                    this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
687                }
688                finally
689                {
690                    try
691                    {
692                        if ( writer != null )
693                        {
694                            writer.close();
695                        }
696                    }
697                    catch ( final IOException e )
698                    {
699                        this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
700                    }
701                    finally
702                    {
703                        try
704                        {
705                            if ( out != null )
706                            {
707                                out.close();
708                            }
709                        }
710                        catch ( final IOException e )
711                        {
712                            this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
713                        }
714                    }
715                }
716            }
717        }
718    }
719
720    private URL filterModlets( final URL resource ) throws ModelException, IOException, JAXBException
721    {
722        InputStream in = null;
723
724        try
725        {
726            URL filteredResource = resource;
727            in = resource.openStream();
728
729            final ModelContext modelContext = ModelContextFactory.newInstance().newModelContext();
730            final JAXBElement<?> e =
731                (JAXBElement<?>) modelContext.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID ).unmarshal( in );
732
733            in.close();
734            in = null;
735
736            final Object o = e.getValue();
737            Modlets modlets = null;
738            boolean filtered = false;
739
740            if ( o instanceof Modlets )
741            {
742                modlets = (Modlets) o;
743            }
744            else if ( o instanceof Modlet )
745            {
746                modlets = new Modlets();
747                modlets.getModlet().add( (Modlet) o );
748            }
749
750            if ( modlets != null )
751            {
752                for ( final Iterator<Modlet> it = modlets.getModlet().iterator(); it.hasNext(); )
753                {
754                    final Modlet m = it.next();
755
756                    if ( this.getModletExcludes().contains( m.getName() ) )
757                    {
758                        it.remove();
759                        filtered = true;
760                        this.addExcludedModlet( m );
761                        this.getProject().log( Messages.getMessage( "modletExclusion", resource.toExternalForm(),
762                                                                    m.getName() ), Project.MSG_DEBUG );
763
764                        continue;
765                    }
766
767                    if ( this.filterModlet( m, resource.toExternalForm() ) )
768                    {
769                        filtered = true;
770                    }
771                }
772
773                if ( filtered )
774                {
775                    final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
776                    this.temporaryResources.add( tmpResource );
777                    modelContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID ).marshal(
778                        new ObjectFactory().createModlets( modlets ), tmpResource );
779
780                    filteredResource = tmpResource.toURI().toURL();
781                }
782            }
783
784            return filteredResource;
785        }
786        finally
787        {
788            try
789            {
790                if ( in != null )
791                {
792                    in.close();
793                }
794            }
795            catch ( final IOException e )
796            {
797                this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
798            }
799        }
800    }
801
802    private boolean filterModlet( final Modlet modlet, final String resourceInfo )
803    {
804        boolean filteredSchemas = false;
805        boolean filteredServices = false;
806
807        if ( modlet.getSchemas() != null )
808        {
809            final Schemas schemas = new Schemas();
810
811            for ( final Schema s : modlet.getSchemas().getSchema() )
812            {
813                if ( !this.getSchemaExcludes().contains( s.getPublicId() ) )
814                {
815                    schemas.getSchema().add( s );
816                }
817                else
818                {
819                    this.getProject().log( Messages.getMessage( "schemaExclusion", resourceInfo, s.getPublicId() ),
820                                           Project.MSG_DEBUG );
821
822                    this.addExcludedSchema( s );
823                    filteredSchemas = true;
824                }
825            }
826
827            if ( filteredSchemas )
828            {
829                modlet.setSchemas( schemas );
830            }
831        }
832
833        if ( modlet.getServices() != null )
834        {
835            final Services services = new Services();
836
837            for ( final Service s : modlet.getServices().getService() )
838            {
839                if ( !this.getServiceExcludes().contains( s.getClazz() ) )
840                {
841                    services.getService().add( s );
842                }
843                else
844                {
845                    this.getProject().log( Messages.getMessage( "serviceExclusion", resourceInfo, s.getClazz() ),
846                                           Project.MSG_DEBUG );
847
848                    this.addExcludedService( s );
849                    filteredServices = true;
850                }
851            }
852
853            if ( filteredServices )
854            {
855                modlet.setServices( services );
856            }
857        }
858
859        return filteredSchemas || filteredServices;
860    }
861
862    private synchronized void addExcludedModlet( final Modlet modlet )
863    {
864        try
865        {
866            final Modlet m = this.getExcludedModlets().getModlet( modlet.getName() );
867
868            if ( m != null )
869            {
870                if ( m.getVersion() != null && modlet.getVersion() != null
871                         && VersionParser.compare( m.getVersion(), modlet.getVersion() ) < 0 )
872                {
873                    this.getExcludedModlets().getModlet().remove( m );
874                    this.getExcludedModlets().getModlet().add( modlet );
875                }
876            }
877            else
878            {
879                this.getExcludedModlets().getModlet().add( modlet );
880            }
881        }
882        catch ( final ParseException e )
883        {
884            this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
885        }
886        catch ( final TokenMgrError e )
887        {
888            this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
889        }
890    }
891
892    private synchronized void addExcludedSchema( final Schema schema )
893    {
894        if ( this.getExcludedSchemas().getSchemaBySystemId( schema.getSystemId() ) == null )
895        {
896            this.getExcludedSchemas().getSchema().add( schema );
897        }
898    }
899
900    private synchronized void addExcludedService( final Service service )
901    {
902        for ( int i = 0, s0 = this.getExcludedServices().getService().size(); i < s0; i++ )
903        {
904            final Service s = this.getExcludedServices().getService().get( i );
905
906            if ( s.getIdentifier().equals( service.getIdentifier() ) && s.getClazz().equals( service.getClazz() ) )
907            {
908                return;
909            }
910        }
911
912        this.getExcludedServices().getService().add( service );
913    }
914
915    private static Set<String> readDefaultExcludes( final String location ) throws IOException
916    {
917        InputStream in = null;
918        BufferedReader reader = null;
919        final Set<String> defaultExcludes = new HashSet<String>();
920
921        try
922        {
923            in = ProjectClassLoader.class.getResourceAsStream( location );
924            assert in != null : "Expected resource '" + location + "' not found.";
925            reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
926
927            for ( String line = reader.readLine(); line != null; line = reader.readLine() )
928            {
929                final String normalized = line.trim();
930
931                if ( normalized.length() > 0 && !normalized.contains( "#" ) )
932                {
933                    defaultExcludes.add( line.trim() );
934                }
935            }
936
937            reader.close();
938            reader = null;
939            in = null;
940
941            return Collections.unmodifiableSet( defaultExcludes );
942        }
943        finally
944        {
945            try
946            {
947                if ( reader != null )
948                {
949                    reader.close();
950                }
951            }
952            catch ( final IOException e )
953            {
954                // Suppressed.
955            }
956            finally
957            {
958                try
959                {
960                    if ( in != null )
961                    {
962                        in.close();
963                    }
964                }
965                catch ( final IOException e )
966                {
967                    // Suppressed.
968                }
969            }
970        }
971    }
972
973}