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