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: ClassFileProcessor.java 4200 2012-01-25 09:46:13Z schulte2005 $
029     *
030     */
031    package org.jomc.tools;
032    
033    import java.io.ByteArrayInputStream;
034    import java.io.ByteArrayOutputStream;
035    import java.io.File;
036    import java.io.IOException;
037    import java.io.InputStream;
038    import java.net.URL;
039    import java.text.MessageFormat;
040    import java.util.List;
041    import java.util.ResourceBundle;
042    import java.util.logging.Level;
043    import java.util.zip.GZIPInputStream;
044    import java.util.zip.GZIPOutputStream;
045    import javax.xml.bind.JAXBElement;
046    import javax.xml.bind.JAXBException;
047    import javax.xml.bind.Marshaller;
048    import javax.xml.bind.Unmarshaller;
049    import javax.xml.bind.util.JAXBResult;
050    import javax.xml.bind.util.JAXBSource;
051    import javax.xml.transform.Transformer;
052    import javax.xml.transform.TransformerException;
053    import javax.xml.validation.Schema;
054    import org.apache.bcel.classfile.Attribute;
055    import org.apache.bcel.classfile.ClassParser;
056    import org.apache.bcel.classfile.Constant;
057    import org.apache.bcel.classfile.ConstantPool;
058    import org.apache.bcel.classfile.ConstantUtf8;
059    import org.apache.bcel.classfile.JavaClass;
060    import org.apache.bcel.classfile.Unknown;
061    import org.jomc.model.Dependencies;
062    import org.jomc.model.Dependency;
063    import org.jomc.model.Implementation;
064    import org.jomc.model.Implementations;
065    import org.jomc.model.Message;
066    import org.jomc.model.Messages;
067    import org.jomc.model.ModelObject;
068    import org.jomc.model.Module;
069    import org.jomc.model.ObjectFactory;
070    import org.jomc.model.Properties;
071    import org.jomc.model.Property;
072    import org.jomc.model.Specification;
073    import org.jomc.model.SpecificationReference;
074    import org.jomc.model.Specifications;
075    import org.jomc.modlet.ModelContext;
076    import org.jomc.modlet.ModelException;
077    import org.jomc.modlet.ModelValidationReport;
078    import org.jomc.util.ParseException;
079    import org.jomc.util.TokenMgrError;
080    import org.jomc.util.VersionParser;
081    
082    /**
083     * Processes class files.
084     *
085     * <p><b>Use Cases:</b><br/><ul>
086     * <li>{@link #commitModelObjects(org.jomc.modlet.ModelContext, java.io.File) }</li>
087     * <li>{@link #commitModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File) }</li>
088     * <li>{@link #commitModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File) }</li>
089     * <li>{@link #commitModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File) }</li>
090     * <li>{@link #validateModelObjects(org.jomc.modlet.ModelContext) }</li>
091     * <li>{@link #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext) }</li>
092     * <li>{@link #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext) }</li>
093     * <li>{@link #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext) }</li>
094     * <li>{@link #validateModelObjects(org.jomc.modlet.ModelContext, java.io.File) }</li>
095     * <li>{@link #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File) }</li>
096     * <li>{@link #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File) }</li>
097     * <li>{@link #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File) }</li>
098     * <li>{@link #transformModelObjects(org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
099     * <li>{@link #transformModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
100     * <li>{@link #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
101     * <li>{@link #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
102     * </ul></p>
103     *
104     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
105     * @version $JOMC: ClassFileProcessor.java 4200 2012-01-25 09:46:13Z schulte2005 $
106     *
107     * @see #getModules()
108     */
109    public class ClassFileProcessor extends JomcTool
110    {
111    
112        /** Empty byte array. */
113        private static final byte[] NO_BYTES =
114        {
115        };
116    
117        /** Creates a new {@code ClassFileProcessor} instance. */
118        public ClassFileProcessor()
119        {
120            super();
121        }
122    
123        /**
124         * Creates a new {@code ClassFileProcessor} instance taking a {@code ClassFileProcessor} instance to initialize the
125         * instance with.
126         *
127         * @param tool The instance to initialize the new instance with.
128         *
129         * @throws NullPointerException if {@code tool} is {@code null}.
130         * @throws IOException if copying {@code tool} fails.
131         */
132        public ClassFileProcessor( final ClassFileProcessor tool ) throws IOException
133        {
134            super( tool );
135        }
136    
137        /**
138         * Commits model objects of the modules of the instance to class files.
139         *
140         * @param context The model context to use for committing the model objects.
141         * @param classesDirectory The directory holding the class files.
142         *
143         * @throws NullPointerException if {@code context} or {@code classesDirectory} is {@code null}.
144         * @throws IOException if committing model objects fails.
145         *
146         * @see #commitModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File)
147         */
148        public final void commitModelObjects( final ModelContext context, final File classesDirectory ) throws IOException
149        {
150            if ( context == null )
151            {
152                throw new NullPointerException( "context" );
153            }
154            if ( classesDirectory == null )
155            {
156                throw new NullPointerException( "classesDirectory" );
157            }
158    
159            try
160            {
161                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
162                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
163    
164                this.commitModelObjects( this.getModules().getSpecifications(), this.getModules().getImplementations(), m,
165                                         classesDirectory );
166    
167            }
168            catch ( final ModelException e )
169            {
170                // JDK: As of JDK 6, "new IOException( message, cause )".
171                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
172            }
173        }
174    
175        /**
176         * Commits model objects of a given module of the modules of the instance to class files.
177         *
178         * @param module The module to process.
179         * @param context The model context to use for committing the model objects.
180         * @param classesDirectory The directory holding the class files.
181         *
182         * @throws NullPointerException if {@code module}, {@code context} or {@code classesDirectory} is {@code null}.
183         * @throws IOException if committing model objects fails.
184         *
185         * @see #commitModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File)
186         * @see #commitModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File)
187         */
188        public final void commitModelObjects( final Module module, final ModelContext context, final File classesDirectory )
189            throws IOException
190        {
191            if ( module == null )
192            {
193                throw new NullPointerException( "module" );
194            }
195            if ( context == null )
196            {
197                throw new NullPointerException( "context" );
198            }
199            if ( classesDirectory == null )
200            {
201                throw new NullPointerException( "classesDirectory" );
202            }
203    
204            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
205    
206            try
207            {
208                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
209                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
210    
211                this.commitModelObjects( module.getSpecifications(), module.getImplementations(), m, classesDirectory );
212            }
213            catch ( final ModelException e )
214            {
215                // JDK: As of JDK 6, "new IOException( message, cause )".
216                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
217            }
218        }
219    
220        /**
221         * Commits model objects of a given specification of the modules of the instance to class files.
222         *
223         * @param specification The specification to process.
224         * @param context The model context to use for committing the model objects.
225         * @param classesDirectory The directory holding the class files.
226         *
227         * @throws NullPointerException if {@code specification}, {@code context} or {@code classesDirectory} is
228         * {@code null}.
229         * @throws IOException if committing model objects fails.
230         *
231         * @see #commitModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, org.apache.bcel.classfile.JavaClass)
232         */
233        public final void commitModelObjects( final Specification specification, final ModelContext context,
234                                              final File classesDirectory ) throws IOException
235        {
236            if ( specification == null )
237            {
238                throw new NullPointerException( "specification" );
239            }
240            if ( context == null )
241            {
242                throw new NullPointerException( "context" );
243            }
244            if ( classesDirectory == null )
245            {
246                throw new NullPointerException( "classesDirectory" );
247            }
248    
249            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
250                "Specification '" + specification.getIdentifier() + "' not found.";
251    
252            try
253            {
254                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
255                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
256    
257                this.commitModelObjects( specification, m, classesDirectory );
258            }
259            catch ( final ModelException e )
260            {
261                // JDK: As of JDK 6, "new IOException( message, cause )".
262                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
263            }
264        }
265    
266        /**
267         * Commits model objects of a given implementation of the modules of the instance to class files.
268         *
269         * @param implementation The implementation to process.
270         * @param context The model context to use for committing the model objects.
271         * @param classesDirectory The directory holding the class files.
272         *
273         * @throws NullPointerException if {@code implementation}, {@code context} or {@code classesDirectory} is
274         * {@code null}.
275         * @throws IOException if committing model objects fails.
276         *
277         * @see #commitModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, org.apache.bcel.classfile.JavaClass)
278         */
279        public final void commitModelObjects( final Implementation implementation, final ModelContext context,
280                                              final File classesDirectory ) throws IOException
281        {
282            if ( implementation == null )
283            {
284                throw new NullPointerException( "implementation" );
285            }
286            if ( context == null )
287            {
288                throw new NullPointerException( "context" );
289            }
290            if ( classesDirectory == null )
291            {
292                throw new NullPointerException( "classesDirectory" );
293            }
294    
295            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
296                "Implementation '" + implementation.getIdentifier() + "' not found.";
297    
298            try
299            {
300                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
301                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
302    
303                this.commitModelObjects( implementation, m, classesDirectory );
304            }
305            catch ( final ModelException e )
306            {
307                // JDK: As of JDK 6, "new IOException( message, cause )".
308                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
309            }
310        }
311    
312        /**
313         * Commits model objects of a given specification of the modules of the instance to a given class file.
314         *
315         * @param specification The specification to process.
316         * @param marshaller The marshaller to use for committing the model objects.
317         * @param javaClass The java class to commit to.
318         *
319         * @throws NullPointerException if {@code specification}, {@code marshaller} or {@code javaClass} is {@code null}.
320         * @throws IOException if committing model objects fails.
321         */
322        public void commitModelObjects( final Specification specification, final Marshaller marshaller,
323                                        final JavaClass javaClass ) throws IOException
324        {
325            if ( specification == null )
326            {
327                throw new NullPointerException( "specification" );
328            }
329            if ( marshaller == null )
330            {
331                throw new NullPointerException( "marshaller" );
332            }
333            if ( javaClass == null )
334            {
335                throw new NullPointerException( "javaClass" );
336            }
337    
338            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
339                "Specification '" + specification.getIdentifier() + "' not found.";
340    
341            this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
342                marshaller, new ObjectFactory().createSpecification( specification ) ) );
343    
344        }
345    
346        /**
347         * Commits model objects of a given implementation of the modules of the instance to a given class file.
348         *
349         * @param implementation The implementation to process.
350         * @param marshaller The marshaller to use for committing the model objects.
351         * @param javaClass The java class to commit to.
352         *
353         * @throws NullPointerException if {@code implementation}, {@code marshaller} or {@code javaClass} is {@code null}.
354         * @throws IOException if committing model objects fails.
355         */
356        public void commitModelObjects( final Implementation implementation, final Marshaller marshaller,
357                                        final JavaClass javaClass ) throws IOException
358        {
359            if ( implementation == null )
360            {
361                throw new NullPointerException( "implementation" );
362            }
363            if ( marshaller == null )
364            {
365                throw new NullPointerException( "marshaller" );
366            }
367            if ( javaClass == null )
368            {
369                throw new NullPointerException( "javaClass" );
370            }
371    
372            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
373                "Implementation '" + implementation.getIdentifier() + "' not found.";
374    
375            final ObjectFactory of = new ObjectFactory();
376    
377            Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
378            if ( dependencies == null )
379            {
380                dependencies = new Dependencies();
381            }
382    
383            Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
384            if ( properties == null )
385            {
386                properties = new Properties();
387            }
388    
389            Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
390            if ( messages == null )
391            {
392                messages = new Messages();
393            }
394    
395            Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
396            if ( specifications == null )
397            {
398                specifications = new Specifications();
399            }
400    
401            for ( int i = 0, s0 = specifications.getReference().size(); i < s0; i++ )
402            {
403                final SpecificationReference r = specifications.getReference().get( i );
404    
405                if ( specifications.getSpecification( r.getIdentifier() ) == null && this.isLoggable( Level.WARNING ) )
406                {
407                    this.log( Level.WARNING, getMessage( "unresolvedSpecification", r.getIdentifier(),
408                                                         implementation.getIdentifier() ), null );
409    
410                }
411            }
412    
413            for ( int i = 0, s0 = dependencies.getDependency().size(); i < s0; i++ )
414            {
415                final Dependency d = dependencies.getDependency().get( i );
416                final Specification s = this.getModules().getSpecification( d.getIdentifier() );
417    
418                if ( s != null )
419                {
420                    if ( specifications.getSpecification( s.getIdentifier() ) == null )
421                    {
422                        specifications.getSpecification().add( s );
423                    }
424                }
425                else if ( this.isLoggable( Level.WARNING ) )
426                {
427                    this.log( Level.WARNING, getMessage( "unresolvedDependencySpecification", d.getIdentifier(),
428                                                         d.getName(), implementation.getIdentifier() ), null );
429    
430                }
431            }
432    
433            this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
434                marshaller, of.createDependencies( dependencies ) ) );
435    
436            this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
437                marshaller, of.createProperties( properties ) ) );
438    
439            this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
440                marshaller, of.createMessages( messages ) ) );
441    
442            this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
443                marshaller, of.createSpecifications( specifications ) ) );
444    
445        }
446    
447        /**
448         * Validates model objects of class files of the modules of the instance.
449         *
450         * @param context The model context to use for validating model objects.
451         *
452         * @return The report of the validation.
453         *
454         * @throws NullPointerException if {@code context} is {@code null}.
455         * @throws IOException if validating model objects fails.
456         *
457         * @see #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext)
458         */
459        public final ModelValidationReport validateModelObjects( final ModelContext context ) throws IOException
460        {
461            if ( context == null )
462            {
463                throw new NullPointerException( "context" );
464            }
465    
466            try
467            {
468                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
469                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
470                return this.validateModelObjects(
471                    this.getModules().getSpecifications(), this.getModules().getImplementations(), u, context );
472    
473            }
474            catch ( final ModelException e )
475            {
476                // JDK: As of JDK 6, "new IOException( message, cause )".
477                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
478            }
479        }
480    
481        /**
482         * Validates model objects of class files of a given module of the modules of the instance.
483         *
484         * @param module The module to process.
485         * @param context The model context to use for validating model objects.
486         *
487         * @return The report of the validation.
488         *
489         * @throws NullPointerException if {@code module} or {@code context} is {@code null}.
490         * @throws IOException if validating model objects fails.
491         *
492         * @see #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext)
493         * @see #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext)
494         */
495        public final ModelValidationReport validateModelObjects( final Module module, final ModelContext context )
496            throws IOException
497        {
498            if ( module == null )
499            {
500                throw new NullPointerException( "module" );
501            }
502            if ( context == null )
503            {
504                throw new NullPointerException( "context" );
505            }
506    
507            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
508    
509            try
510            {
511                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
512                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
513                return this.validateModelObjects( module.getSpecifications(), module.getImplementations(), u, context );
514            }
515            catch ( final ModelException e )
516            {
517                // JDK: As of JDK 6, "new IOException( message, cause )".
518                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
519            }
520        }
521    
522        /**
523         * Validates model objects of class files of a given specification of the modules of the instance.
524         *
525         * @param specification The specification to process.
526         * @param context The model context to use for validating model objects.
527         *
528         * @return The report of the validation.
529         *
530         * @throws NullPointerException if {@code specification} or {@code context} is {@code null}.
531         *
532         * @throws IOException if validating model objects fails.
533         *
534         * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
535         */
536        public final ModelValidationReport validateModelObjects( final Specification specification,
537                                                                 final ModelContext context ) throws IOException
538        {
539            if ( specification == null )
540            {
541                throw new NullPointerException( "specification" );
542            }
543            if ( context == null )
544            {
545                throw new NullPointerException( "context" );
546            }
547    
548            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
549                "Specification '" + specification.getIdentifier() + "' not found.";
550    
551            try
552            {
553                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
554                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
555                return this.validateModelObjects( specification, u, context );
556            }
557            catch ( final ModelException e )
558            {
559                // JDK: As of JDK 6, "new IOException( message, cause )".
560                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
561            }
562        }
563    
564        /**
565         * Validates model objects of class files of a given implementation of the modules of the instance.
566         *
567         * @param implementation The implementation to process.
568         * @param context The model context to use for validating model objects.
569         *
570         * @return The report of the validation.
571         *
572         * @throws NullPointerException if {@code implementation} or {@code context} is {@code null}.
573         *
574         * @throws IOException if validating model objects fails.
575         *
576         * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
577         */
578        public final ModelValidationReport validateModelObjects( final Implementation implementation,
579                                                                 final ModelContext context ) throws IOException
580        {
581            if ( implementation == null )
582            {
583                throw new NullPointerException( "implementation" );
584            }
585            if ( context == null )
586            {
587                throw new NullPointerException( "context" );
588            }
589    
590            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
591                "Implementation '" + implementation.getIdentifier() + "' not found.";
592    
593            try
594            {
595                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
596                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
597                return this.validateModelObjects( implementation, u, context );
598            }
599            catch ( final ModelException e )
600            {
601                // JDK: As of JDK 6, "new IOException( message, cause )".
602                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
603            }
604        }
605    
606        /**
607         * Validates model objects of class files of the modules of the instance.
608         *
609         * @param context The model context to use for validating model objects.
610         * @param classesDirectory The directory holding the class files.
611         *
612         * @return The report of the validation.
613         *
614         * @throws NullPointerException if {@code context} or {@code classesDirectory} is {@code null}.
615         * @throws IOException if validating model objects fails.
616         *
617         * @see #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File)
618         */
619        public final ModelValidationReport validateModelObjects( final ModelContext context, final File classesDirectory )
620            throws IOException
621        {
622            if ( context == null )
623            {
624                throw new NullPointerException( "context" );
625            }
626            if ( classesDirectory == null )
627            {
628                throw new NullPointerException( "classesDirectory" );
629            }
630    
631            try
632            {
633                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
634                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
635                return this.validateModelObjects(
636                    this.getModules().getSpecifications(), this.getModules().getImplementations(), u, classesDirectory );
637    
638            }
639            catch ( final ModelException e )
640            {
641                // JDK: As of JDK 6, "new IOException( message, cause )".
642                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
643            }
644        }
645    
646        /**
647         * Validates model objects of class files of a given module of the modules of the instance.
648         *
649         * @param module The module to process.
650         * @param context The model context to use for validating model objects.
651         * @param classesDirectory The directory holding the class files.
652         *
653         * @return The report of the validation.
654         *
655         * @throws NullPointerException if {@code module}, {@code context} or {@code classesDirectory} is {@code null}.
656         * @throws IOException if validating model objects fails.
657         *
658         * @see #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File)
659         * @see #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File)
660         */
661        public final ModelValidationReport validateModelObjects( final Module module, final ModelContext context,
662                                                                 final File classesDirectory ) throws IOException
663        {
664            if ( module == null )
665            {
666                throw new NullPointerException( "module" );
667            }
668            if ( context == null )
669            {
670                throw new NullPointerException( "context" );
671            }
672            if ( classesDirectory == null )
673            {
674                throw new NullPointerException( "classesDirectory" );
675            }
676    
677            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
678    
679            try
680            {
681                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
682                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
683                return this.validateModelObjects( module.getSpecifications(), module.getImplementations(), u,
684                                                  classesDirectory );
685    
686            }
687            catch ( final ModelException e )
688            {
689                // JDK: As of JDK 6, "new IOException( message, cause )".
690                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
691            }
692        }
693    
694        /**
695         * Validates model objects of class files of a given specification of the modules of the instance.
696         *
697         * @param specification The specification to process.
698         * @param context The model context to use for validating model objects.
699         * @param classesDirectory The directory holding the class files.
700         *
701         * @return The report of the validation.
702         *
703         * @throws NullPointerException if {@code specification}, {@code context} or {@code classesDirectory} is
704         * {@code null}.
705         *
706         * @throws IOException if validating model objects fails.
707         *
708         * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
709         */
710        public final ModelValidationReport validateModelObjects( final Specification specification,
711                                                                 final ModelContext context, final File classesDirectory )
712            throws IOException
713        {
714            if ( specification == null )
715            {
716                throw new NullPointerException( "specification" );
717            }
718            if ( context == null )
719            {
720                throw new NullPointerException( "context" );
721            }
722            if ( classesDirectory == null )
723            {
724                throw new NullPointerException( "classesDirectory" );
725            }
726    
727            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
728                "Specification '" + specification.getIdentifier() + "' not found.";
729    
730            try
731            {
732                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
733                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
734                return this.validateModelObjects( specification, u, classesDirectory );
735            }
736            catch ( final ModelException e )
737            {
738                // JDK: As of JDK 6, "new IOException( message, cause )".
739                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
740            }
741        }
742    
743        /**
744         * Validates model objects of class files of a given implementation of the modules of the instance.
745         *
746         * @param implementation The implementation to process.
747         * @param context The model context to use for validating model objects.
748         * @param classesDirectory The directory holding the class files.
749         *
750         * @return The report of the validation.
751         *
752         * @throws NullPointerException if {@code implementation}, {@code context} or {@code classesDirectory} is
753         * {@code null}.
754         *
755         * @throws IOException if validating model objects fails.
756         *
757         * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
758         */
759        public final ModelValidationReport validateModelObjects( final Implementation implementation,
760                                                                 final ModelContext context, final File classesDirectory )
761            throws IOException
762        {
763            if ( implementation == null )
764            {
765                throw new NullPointerException( "implementation" );
766            }
767            if ( context == null )
768            {
769                throw new NullPointerException( "context" );
770            }
771            if ( classesDirectory == null )
772            {
773                throw new NullPointerException( "classesDirectory" );
774            }
775    
776            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
777                "Implementation '" + implementation.getIdentifier() + "' not found.";
778    
779            try
780            {
781                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
782                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
783                return this.validateModelObjects( implementation, u, classesDirectory );
784            }
785            catch ( final ModelException e )
786            {
787                // JDK: As of JDK 6, "new IOException( message, cause )".
788                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
789            }
790        }
791    
792        /**
793         * Validates model objects of a given specification of the modules of the instance.
794         *
795         * @param specification The specification to process.
796         * @param unmarshaller The unmarshaller to use for validating model objects.
797         * @param javaClass The java class to validate.
798         *
799         * @return The report of the validation.
800         *
801         * @throws NullPointerException if {@code specification}, {@code unmarshaller} or {@code javaClass} is {@code null}.
802         * @throws IOException if validating model objects fails.
803         */
804        public ModelValidationReport validateModelObjects( final Specification specification,
805                                                           final Unmarshaller unmarshaller, final JavaClass javaClass )
806            throws IOException
807        {
808            if ( specification == null )
809            {
810                throw new NullPointerException( "specification" );
811            }
812            if ( unmarshaller == null )
813            {
814                throw new NullPointerException( "unmarshaller" );
815            }
816            if ( javaClass == null )
817            {
818                throw new NullPointerException( "javaClass" );
819            }
820    
821            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
822                "Specification '" + specification.getIdentifier() + "' not found.";
823    
824            final ModelValidationReport report = new ModelValidationReport();
825    
826            Specification decoded = null;
827            final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
828            if ( bytes != null )
829            {
830                decoded = this.decodeModelObject( unmarshaller, bytes, Specification.class );
831            }
832    
833            if ( decoded != null )
834            {
835                if ( decoded.getMultiplicity() != specification.getMultiplicity() )
836                {
837                    report.getDetails().add( new ModelValidationReport.Detail(
838                        "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
839                        "illegalMultiplicity", specification.getIdentifier(), specification.getMultiplicity().value(),
840                        decoded.getMultiplicity().value() ), new ObjectFactory().createSpecification( specification ) ) );
841    
842                }
843    
844                if ( decoded.getScope() == null
845                     ? specification.getScope() != null
846                     : !decoded.getScope().equals( specification.getScope() ) )
847                {
848                    report.getDetails().add( new ModelValidationReport.Detail(
849                        "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
850                        "illegalScope", specification.getIdentifier(),
851                        specification.getScope() == null ? "Multiton" : specification.getScope(),
852                        decoded.getScope() == null ? "Multiton" : decoded.getScope() ),
853                        new ObjectFactory().createSpecification( specification ) ) );
854    
855                }
856    
857                if ( decoded.getClazz() == null
858                     ? specification.getClazz() != null
859                     : !decoded.getClazz().equals( specification.getClazz() ) )
860                {
861                    report.getDetails().add( new ModelValidationReport.Detail(
862                        "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
863                        "illegalSpecificationClass", decoded.getIdentifier(),
864                        specification.getClazz(), decoded.getClazz() ),
865                        new ObjectFactory().createSpecification( specification ) ) );
866    
867                }
868            }
869            else if ( this.isLoggable( Level.WARNING ) )
870            {
871                this.log( Level.WARNING, getMessage( "cannotValidateSpecification", specification.getIdentifier(),
872                                                     Specification.class.getName() ), null );
873    
874            }
875    
876            return report;
877        }
878    
879        /**
880         * Validates model objects of a given implementation of the modules of the instance.
881         *
882         * @param implementation The implementation to process.
883         * @param unmarshaller The unmarshaller to use for validating model objects.
884         * @param javaClass The java class to validate.
885         *
886         * @return The report of the validation.
887         *
888         * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code javaClass} is {@code null}.
889         * @throws IOException if validating model objects fails.
890         */
891        public ModelValidationReport validateModelObjects( final Implementation implementation,
892                                                           final Unmarshaller unmarshaller, final JavaClass javaClass )
893            throws IOException
894        {
895            if ( implementation == null )
896            {
897                throw new NullPointerException( "implementation" );
898            }
899            if ( unmarshaller == null )
900            {
901                throw new NullPointerException( "unmarshaller" );
902            }
903            if ( javaClass == null )
904            {
905                throw new NullPointerException( "javaClass" );
906            }
907    
908            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
909                "Implementation '" + implementation.getIdentifier() + "' not found.";
910    
911            try
912            {
913                final ModelValidationReport report = new ModelValidationReport();
914                Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
915                if ( dependencies == null )
916                {
917                    dependencies = new Dependencies();
918                }
919    
920                Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
921                if ( properties == null )
922                {
923                    properties = new Properties();
924                }
925    
926                Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
927                if ( messages == null )
928                {
929                    messages = new Messages();
930                }
931    
932                Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
933                if ( specifications == null )
934                {
935                    specifications = new Specifications();
936                }
937    
938                Dependencies decodedDependencies = null;
939                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
940                if ( bytes != null )
941                {
942                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
943                }
944    
945                Properties decodedProperties = null;
946                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
947                if ( bytes != null )
948                {
949                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
950                }
951    
952                Messages decodedMessages = null;
953                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
954                if ( bytes != null )
955                {
956                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
957                }
958    
959                Specifications decodedSpecifications = null;
960                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
961                if ( bytes != null )
962                {
963                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
964                }
965    
966                if ( decodedDependencies != null )
967                {
968                    for ( int i = 0, s0 = decodedDependencies.getDependency().size(); i < s0; i++ )
969                    {
970                        final Dependency decodedDependency = decodedDependencies.getDependency().get( i );
971                        final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
972                        final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );
973    
974                        if ( dependency == null )
975                        {
976                            report.getDetails().add( new ModelValidationReport.Detail(
977                                "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
978                                "missingDependency", implementation.getIdentifier(), decodedDependency.getName() ),
979                                new ObjectFactory().createImplementation( implementation ) ) );
980    
981                        }
982                        else if ( decodedDependency.getImplementationName() != null
983                                  && dependency.getImplementationName() == null )
984                        {
985                            report.getDetails().add( new ModelValidationReport.Detail(
986                                "CLASS_MISSING_DEPENDENCY_IMPLEMENTATION_NAME", Level.SEVERE, getMessage(
987                                "missingDependencyImplementationName", implementation.getIdentifier(),
988                                decodedDependency.getName() ),
989                                new ObjectFactory().createImplementation( implementation ) ) );
990    
991                        }
992    
993                        if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null
994                             && VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
995                        {
996                            final Module moduleOfSpecification =
997                                this.getModules().getModuleOfSpecification( s.getIdentifier() );
998    
999                            final Module moduleOfImplementation =
1000                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
1001    
1002                            report.getDetails().add( new ModelValidationReport.Detail(
1003                                "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
1004                                "incompatibleDependency", javaClass.getClassName(),
1005                                moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
1006                                s.getIdentifier(),
1007                                moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
1008                                decodedDependency.getVersion(), s.getVersion() ),
1009                                new ObjectFactory().createImplementation( implementation ) ) );
1010    
1011                        }
1012                    }
1013                }
1014                else if ( this.isLoggable( Level.WARNING ) )
1015                {
1016                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
1017                                                         Dependencies.class.getName() ), null );
1018    
1019                }
1020    
1021                if ( decodedProperties != null )
1022                {
1023                    for ( int i = 0, s0 = decodedProperties.getProperty().size(); i < s0; i++ )
1024                    {
1025                        final Property decodedProperty = decodedProperties.getProperty().get( i );
1026                        final Property property = properties.getProperty( decodedProperty.getName() );
1027    
1028                        if ( property == null )
1029                        {
1030                            report.getDetails().add( new ModelValidationReport.Detail(
1031                                "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
1032                                "missingProperty", implementation.getIdentifier(), decodedProperty.getName() ),
1033                                new ObjectFactory().createImplementation( implementation ) ) );
1034    
1035                        }
1036                        else if ( decodedProperty.getType() == null
1037                                  ? property.getType() != null
1038                                  : !decodedProperty.getType().equals( property.getType() ) )
1039                        {
1040                            report.getDetails().add( new ModelValidationReport.Detail(
1041                                "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
1042                                "illegalPropertyType", implementation.getIdentifier(), decodedProperty.getName(),
1043                                property.getType() == null ? "<>" : property.getType(),
1044                                decodedProperty.getType() == null ? "<>" : decodedProperty.getType() ),
1045                                new ObjectFactory().createImplementation( implementation ) ) );
1046    
1047                        }
1048                    }
1049                }
1050                else if ( this.isLoggable( Level.WARNING ) )
1051                {
1052                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
1053                                                         Properties.class.getName() ), null );
1054    
1055                }
1056    
1057                if ( decodedMessages != null )
1058                {
1059                    for ( int i = 0, s0 = decodedMessages.getMessage().size(); i < s0; i++ )
1060                    {
1061                        final Message decodedMessage = decodedMessages.getMessage().get( i );
1062                        final Message message = messages.getMessage( decodedMessage.getName() );
1063    
1064                        if ( message == null )
1065                        {
1066                            report.getDetails().add( new ModelValidationReport.Detail(
1067                                "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE, getMessage(
1068                                "missingMessage", implementation.getIdentifier(), decodedMessage.getName() ),
1069                                new ObjectFactory().createImplementation( implementation ) ) );
1070    
1071                        }
1072                    }
1073                }
1074                else if ( this.isLoggable( Level.WARNING ) )
1075                {
1076                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation",
1077                                                         implementation.getIdentifier(), Messages.class.getName() ), null );
1078    
1079                }
1080    
1081                if ( decodedSpecifications != null )
1082                {
1083                    for ( int i = 0, s0 = decodedSpecifications.getSpecification().size(); i < s0; i++ )
1084                    {
1085                        final Specification decodedSpecification = decodedSpecifications.getSpecification().get( i );
1086                        final Specification specification =
1087                            this.getModules().getSpecification( decodedSpecification.getIdentifier() );
1088    
1089                        if ( specification == null )
1090                        {
1091                            report.getDetails().add( new ModelValidationReport.Detail(
1092                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
1093                                "missingSpecification", implementation.getIdentifier(),
1094                                decodedSpecification.getIdentifier() ),
1095                                new ObjectFactory().createImplementation( implementation ) ) );
1096    
1097                        }
1098                        else
1099                        {
1100                            if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
1101                            {
1102                                report.getDetails().add( new ModelValidationReport.Detail(
1103                                    "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
1104                                    "illegalMultiplicity", specification.getIdentifier(),
1105                                    specification.getMultiplicity().value(),
1106                                    decodedSpecification.getMultiplicity().value() ),
1107                                    new ObjectFactory().createImplementation( implementation ) ) );
1108    
1109                            }
1110    
1111                            if ( decodedSpecification.getScope() == null
1112                                 ? specification.getScope() != null
1113                                 : !decodedSpecification.getScope().equals( specification.getScope() ) )
1114                            {
1115                                report.getDetails().add( new ModelValidationReport.Detail(
1116                                    "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
1117                                    "illegalScope", decodedSpecification.getIdentifier(),
1118                                    specification.getScope() == null ? "Multiton" : specification.getScope(),
1119                                    decodedSpecification.getScope() == null ? "Multiton" : decodedSpecification.getScope() ),
1120                                    new ObjectFactory().createImplementation( implementation ) ) );
1121    
1122                            }
1123    
1124                            if ( decodedSpecification.getClazz() == null
1125                                 ? specification.getClazz() != null
1126                                 : !decodedSpecification.getClazz().equals( specification.getClazz() ) )
1127                            {
1128                                report.getDetails().add( new ModelValidationReport.Detail(
1129                                    "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
1130                                    "illegalSpecificationClass", decodedSpecification.getIdentifier(),
1131                                    specification.getClazz(), decodedSpecification.getClazz() ),
1132                                    new ObjectFactory().createImplementation( implementation ) ) );
1133    
1134                            }
1135                        }
1136                    }
1137    
1138                    for ( int i = 0, s0 = decodedSpecifications.getReference().size(); i < s0; i++ )
1139                    {
1140                        final SpecificationReference decodedReference = decodedSpecifications.getReference().get( i );
1141                        final Specification specification =
1142                            specifications.getSpecification( decodedReference.getIdentifier() );
1143    
1144                        if ( specification == null )
1145                        {
1146                            report.getDetails().add( new ModelValidationReport.Detail(
1147                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
1148                                "missingSpecification", implementation.getIdentifier(), decodedReference.getIdentifier() ),
1149                                new ObjectFactory().createImplementation( implementation ) ) );
1150    
1151                        }
1152                        else if ( decodedReference.getVersion() != null && specification.getVersion() != null
1153                                  && VersionParser.compare( decodedReference.getVersion(),
1154                                                            specification.getVersion() ) != 0 )
1155                        {
1156                            final Module moduleOfSpecification =
1157                                this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );
1158    
1159                            final Module moduleOfImplementation =
1160                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
1161    
1162                            report.getDetails().add( new ModelValidationReport.Detail(
1163                                "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE, getMessage(
1164                                "incompatibleImplementation", javaClass.getClassName(),
1165                                moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
1166                                specification.getIdentifier(),
1167                                moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
1168                                decodedReference.getVersion(), specification.getVersion() ),
1169                                new ObjectFactory().createImplementation( implementation ) ) );
1170    
1171                        }
1172                    }
1173                }
1174                else if ( this.isLoggable( Level.WARNING ) )
1175                {
1176                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
1177                                                         Specifications.class.getName() ), null );
1178    
1179                }
1180    
1181                return report;
1182            }
1183            catch ( final ParseException e )
1184            {
1185                // JDK: As of JDK 6, "new IOException( message, cause )".
1186                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1187            }
1188            catch ( final TokenMgrError e )
1189            {
1190                // JDK: As of JDK 6, "new IOException( message, cause )".
1191                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1192            }
1193        }
1194    
1195        /**
1196         * Transforms model objects of class files of the modules of the instance.
1197         *
1198         * @param context The model context to use for transforming model objects.
1199         * @param classesDirectory The directory holding the class files.
1200         * @param transformers The transformers to use for transforming model objects.
1201         *
1202         * @throws NullPointerException if {@code context}, {@code classesDirectory} or {@code transformers} is
1203         * {@code null}.
1204         * @throws IOException if transforming model objects fails.
1205         *
1206         * @see #transformModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
1207         */
1208        public final void transformModelObjects( final ModelContext context, final File classesDirectory,
1209                                                 final List<Transformer> transformers ) throws IOException
1210        {
1211            if ( context == null )
1212            {
1213                throw new NullPointerException( "context" );
1214            }
1215            if ( classesDirectory == null )
1216            {
1217                throw new NullPointerException( "classesDirectory" );
1218            }
1219            if ( transformers == null )
1220            {
1221                throw new NullPointerException( "transformers" );
1222            }
1223            if ( !classesDirectory.isDirectory() )
1224            {
1225                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
1226            }
1227    
1228            try
1229            {
1230                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
1231                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
1232                final Schema s = context.createSchema( this.getModel().getIdentifier() );
1233                u.setSchema( s );
1234                m.setSchema( s );
1235    
1236                this.transformModelObjects( this.getModules().getSpecifications(), this.getModules().getImplementations(),
1237                                            u, m, classesDirectory, transformers );
1238    
1239            }
1240            catch ( final ModelException e )
1241            {
1242                // JDK: As of JDK 6, "new IOException( message, cause )".
1243                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1244            }
1245        }
1246    
1247        /**
1248         * Transforms model objects of class files of a given module of the modules of the instance.
1249         *
1250         * @param module The module to process.
1251         * @param context The model context to use for transforming model objects.
1252         * @param classesDirectory The directory holding the class files.
1253         * @param transformers The transformers to use for transforming the model objects.
1254         *
1255         * @throws NullPointerException if {@code module}, {@code context}, {@code classesDirectory} or {@code transformers}
1256         * is {@code null}.
1257         * @throws IOException if transforming model objects fails.
1258         *
1259         * @see #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
1260         * @see #transformModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
1261         */
1262        public final void transformModelObjects( final Module module, final ModelContext context,
1263                                                 final File classesDirectory, final List<Transformer> transformers )
1264            throws IOException
1265        {
1266            if ( module == null )
1267            {
1268                throw new NullPointerException( "module" );
1269            }
1270            if ( context == null )
1271            {
1272                throw new NullPointerException( "context" );
1273            }
1274            if ( classesDirectory == null )
1275            {
1276                throw new NullPointerException( "classesDirectory" );
1277            }
1278            if ( transformers == null )
1279            {
1280                throw new NullPointerException( "transformers" );
1281            }
1282            if ( !classesDirectory.isDirectory() )
1283            {
1284                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
1285            }
1286    
1287            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
1288    
1289            try
1290            {
1291                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
1292                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
1293                final Schema s = context.createSchema( this.getModel().getIdentifier() );
1294                u.setSchema( s );
1295                m.setSchema( s );
1296    
1297                this.transformModelObjects( module.getSpecifications(), module.getImplementations(), u, m, classesDirectory,
1298                                            transformers );
1299    
1300            }
1301            catch ( final ModelException e )
1302            {
1303                // JDK: As of JDK 6, "new IOException( message, cause )".
1304                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1305            }
1306        }
1307    
1308        /**
1309         * Transforms model objects of class files of a given specification of the modules of the instance.
1310         *
1311         * @param specification The specification to process.
1312         * @param context The model context to use for transforming model objects.
1313         * @param classesDirectory The directory holding the class files.
1314         * @param transformers The transformers to use for transforming the model objects.
1315         *
1316         * @throws NullPointerException if {@code specification}, {@code context}, {@code classesDirectory} or
1317         * {@code transformers} is {@code null}.
1318         * @throws IOException if transforming model objects fails.
1319         *
1320         * @see #transformModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1321         */
1322        public final void transformModelObjects( final Specification specification, final ModelContext context,
1323                                                 final File classesDirectory, final List<Transformer> transformers )
1324            throws IOException
1325        {
1326            if ( specification == null )
1327            {
1328                throw new NullPointerException( "specification" );
1329            }
1330            if ( context == null )
1331            {
1332                throw new NullPointerException( "context" );
1333            }
1334            if ( classesDirectory == null )
1335            {
1336                throw new NullPointerException( "classesDirectory" );
1337            }
1338            if ( transformers == null )
1339            {
1340                throw new NullPointerException( "transformers" );
1341            }
1342            if ( !classesDirectory.isDirectory() )
1343            {
1344                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
1345            }
1346    
1347            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
1348                "Specification '" + specification.getIdentifier() + "' not found.";
1349    
1350            try
1351            {
1352                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
1353                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
1354                final Schema s = context.createSchema( this.getModel().getIdentifier() );
1355                u.setSchema( s );
1356                m.setSchema( s );
1357    
1358                this.transformModelObjects( specification, m, u, classesDirectory, transformers );
1359            }
1360            catch ( final ModelException e )
1361            {
1362                // JDK: As of JDK 6, "new IOException( message, cause )".
1363                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1364            }
1365        }
1366    
1367        /**
1368         * Transforms model objects of class files of a given implementation of the modules of the instance.
1369         *
1370         * @param implementation The implementation to process.
1371         * @param context The model context to use for transforming model objects.
1372         * @param classesDirectory The directory holding the class files.
1373         * @param transformers The transformers to use for transforming the model objects.
1374         *
1375         * @throws NullPointerException if {@code implementation}, {@code context}, {@code classesDirectory} or
1376         * {@code transformers} is {@code null}.
1377         * @throws IOException if transforming model objects fails.
1378         *
1379         * @see #transformModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1380         */
1381        public final void transformModelObjects( final Implementation implementation, final ModelContext context,
1382                                                 final File classesDirectory, final List<Transformer> transformers )
1383            throws IOException
1384        {
1385            if ( implementation == null )
1386            {
1387                throw new NullPointerException( "implementation" );
1388            }
1389            if ( context == null )
1390            {
1391                throw new NullPointerException( "context" );
1392            }
1393            if ( classesDirectory == null )
1394            {
1395                throw new NullPointerException( "classesDirectory" );
1396            }
1397            if ( transformers == null )
1398            {
1399                throw new NullPointerException( "transformers" );
1400            }
1401            if ( !classesDirectory.isDirectory() )
1402            {
1403                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
1404            }
1405    
1406            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
1407                "Implementation '" + implementation.getIdentifier() + "' not found.";
1408    
1409            try
1410            {
1411                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
1412                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
1413                final Schema s = context.createSchema( this.getModel().getIdentifier() );
1414                u.setSchema( s );
1415                m.setSchema( s );
1416    
1417                this.transformModelObjects( implementation, m, u, classesDirectory, transformers );
1418            }
1419            catch ( final ModelException e )
1420            {
1421                // JDK: As of JDK 6, "new IOException( message, cause )".
1422                throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1423            }
1424        }
1425    
1426        /**
1427         * Transforms model objects of a given specification of the modules of the instance.
1428         *
1429         * @param specification The specification to process.
1430         * @param marshaller The marshaller to use for transforming model objects.
1431         * @param unmarshaller The unmarshaller to use for transforming model objects.
1432         * @param javaClass The java class to transform model objects of.
1433         * @param transformers The transformers to use for transforming the model objects.
1434         *
1435         * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
1436         * {@code javaClass} or {@code transformers} is {@code null}.
1437         * @throws IOException if transforming model objects fails.
1438         */
1439        public void transformModelObjects( final Specification specification, final Marshaller marshaller,
1440                                           final Unmarshaller unmarshaller, final JavaClass javaClass,
1441                                           final List<Transformer> transformers ) throws IOException
1442        {
1443            if ( specification == null )
1444            {
1445                throw new NullPointerException( "specification" );
1446            }
1447            if ( marshaller == null )
1448            {
1449                throw new NullPointerException( "marshaller" );
1450            }
1451            if ( unmarshaller == null )
1452            {
1453                throw new NullPointerException( "unmarshaller" );
1454            }
1455            if ( javaClass == null )
1456            {
1457                throw new NullPointerException( "javaClass" );
1458            }
1459            if ( transformers == null )
1460            {
1461                throw new NullPointerException( "transformers" );
1462            }
1463    
1464            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
1465                "Specification '" + specification.getIdentifier() + "' not found.";
1466    
1467            try
1468            {
1469                Specification decodedSpecification = null;
1470                final ObjectFactory objectFactory = new ObjectFactory();
1471                final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
1472                if ( bytes != null )
1473                {
1474                    decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
1475                }
1476    
1477                if ( decodedSpecification != null )
1478                {
1479                    for ( int i = 0, l = transformers.size(); i < l; i++ )
1480                    {
1481                        final JAXBSource source =
1482                            new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );
1483    
1484                        final JAXBResult result = new JAXBResult( unmarshaller );
1485                        transformers.get( i ).transform( source, result );
1486    
1487                        if ( result.getResult() instanceof JAXBElement<?>
1488                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Specification )
1489                        {
1490                            decodedSpecification = (Specification) ( (JAXBElement<?>) result.getResult() ).getValue();
1491                        }
1492                        else
1493                        {
1494                            throw new IOException( getMessage(
1495                                "illegalSpecificationTransformationResult", specification.getIdentifier() ) );
1496    
1497                        }
1498                    }
1499    
1500                    this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
1501                        marshaller, objectFactory.createSpecification( decodedSpecification ) ) );
1502    
1503                }
1504            }
1505            catch ( final JAXBException e )
1506            {
1507                String message = getMessage( e );
1508                if ( message == null && e.getLinkedException() != null )
1509                {
1510                    message = getMessage( e.getLinkedException() );
1511                }
1512    
1513                // JDK: As of JDK 6, "new IOException( message, cause )".
1514                throw (IOException) new IOException( message ).initCause( e );
1515            }
1516            catch ( final TransformerException e )
1517            {
1518                String message = getMessage( e );
1519                if ( message == null && e.getException() != null )
1520                {
1521                    message = getMessage( e.getException() );
1522                }
1523    
1524                // JDK: As of JDK 6, "new IOException( message, cause )".
1525                throw (IOException) new IOException( message ).initCause( e );
1526            }
1527        }
1528    
1529        /**
1530         * Transforms model objects of a given implementation of the modules of the instance.
1531         *
1532         * @param implementation The implementation to process.
1533         * @param marshaller The marshaller to use for transforming model objects.
1534         * @param unmarshaller The unmarshaller to use for transforming model objects.
1535         * @param javaClass The java class to transform model object of.
1536         * @param transformers The transformers to use for transforming the model objects.
1537         *
1538         * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
1539         * {@code javaClass} or {@code transformers} is {@code null}.
1540         * @throws IOException if transforming model objects fails.
1541         */
1542        public void transformModelObjects( final Implementation implementation, final Marshaller marshaller,
1543                                           final Unmarshaller unmarshaller, final JavaClass javaClass,
1544                                           final List<Transformer> transformers ) throws IOException
1545        {
1546            if ( implementation == null )
1547            {
1548                throw new NullPointerException( "implementation" );
1549            }
1550            if ( marshaller == null )
1551            {
1552                throw new NullPointerException( "marshaller" );
1553            }
1554            if ( unmarshaller == null )
1555            {
1556                throw new NullPointerException( "unmarshaller" );
1557            }
1558            if ( javaClass == null )
1559            {
1560                throw new NullPointerException( "javaClass" );
1561            }
1562            if ( transformers == null )
1563            {
1564                throw new NullPointerException( "transformers" );
1565            }
1566    
1567            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
1568                "Implementation '" + implementation.getIdentifier() + "' not found.";
1569    
1570            try
1571            {
1572                Dependencies decodedDependencies = null;
1573                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
1574                if ( bytes != null )
1575                {
1576                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
1577                }
1578    
1579                Messages decodedMessages = null;
1580                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
1581                if ( bytes != null )
1582                {
1583                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
1584                }
1585    
1586                Properties decodedProperties = null;
1587                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
1588                if ( bytes != null )
1589                {
1590                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
1591                }
1592    
1593                Specifications decodedSpecifications = null;
1594                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
1595                if ( bytes != null )
1596                {
1597                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
1598                }
1599    
1600                final ObjectFactory of = new ObjectFactory();
1601                for ( int i = 0, l = transformers.size(); i < l; i++ )
1602                {
1603                    final Transformer transformer = transformers.get( i );
1604    
1605                    if ( decodedDependencies != null )
1606                    {
1607                        final JAXBSource source =
1608                            new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );
1609    
1610                        final JAXBResult result = new JAXBResult( unmarshaller );
1611                        transformer.transform( source, result );
1612    
1613                        if ( result.getResult() instanceof JAXBElement<?>
1614                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Dependencies )
1615                        {
1616                            decodedDependencies = (Dependencies) ( (JAXBElement<?>) result.getResult() ).getValue();
1617                        }
1618                        else
1619                        {
1620                            throw new IOException( getMessage(
1621                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );
1622    
1623                        }
1624                    }
1625    
1626                    if ( decodedMessages != null )
1627                    {
1628                        final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
1629                        final JAXBResult result = new JAXBResult( unmarshaller );
1630                        transformer.transform( source, result );
1631    
1632                        if ( result.getResult() instanceof JAXBElement<?>
1633                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Messages )
1634                        {
1635                            decodedMessages = (Messages) ( (JAXBElement<?>) result.getResult() ).getValue();
1636                        }
1637                        else
1638                        {
1639                            throw new IOException( getMessage(
1640                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );
1641    
1642                        }
1643                    }
1644    
1645                    if ( decodedProperties != null )
1646                    {
1647                        final JAXBSource source = new JAXBSource( marshaller, of.createProperties( decodedProperties ) );
1648                        final JAXBResult result = new JAXBResult( unmarshaller );
1649                        transformer.transform( source, result );
1650    
1651                        if ( result.getResult() instanceof JAXBElement<?>
1652                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Properties )
1653                        {
1654                            decodedProperties = (Properties) ( (JAXBElement<?>) result.getResult() ).getValue();
1655                        }
1656                        else
1657                        {
1658                            throw new IOException( getMessage(
1659                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );
1660    
1661                        }
1662                    }
1663    
1664                    if ( decodedSpecifications != null )
1665                    {
1666                        final JAXBSource source =
1667                            new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );
1668    
1669                        final JAXBResult result = new JAXBResult( unmarshaller );
1670                        transformer.transform( source, result );
1671    
1672                        if ( result.getResult() instanceof JAXBElement<?>
1673                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Specifications )
1674                        {
1675                            decodedSpecifications = (Specifications) ( (JAXBElement<?>) result.getResult() ).getValue();
1676                        }
1677                        else
1678                        {
1679                            throw new IOException( getMessage(
1680                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );
1681    
1682                        }
1683                    }
1684                }
1685    
1686                if ( decodedDependencies != null )
1687                {
1688                    this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
1689                        marshaller, of.createDependencies( decodedDependencies ) ) );
1690    
1691                }
1692    
1693                if ( decodedMessages != null )
1694                {
1695                    this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
1696                        marshaller, of.createMessages( decodedMessages ) ) );
1697    
1698                }
1699    
1700                if ( decodedProperties != null )
1701                {
1702                    this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
1703                        marshaller, of.createProperties( decodedProperties ) ) );
1704    
1705                }
1706    
1707                if ( decodedSpecifications != null )
1708                {
1709                    this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
1710                        marshaller, of.createSpecifications( decodedSpecifications ) ) );
1711    
1712                }
1713            }
1714            catch ( final JAXBException e )
1715            {
1716                String message = getMessage( e );
1717                if ( message == null && e.getLinkedException() != null )
1718                {
1719                    message = getMessage( e.getLinkedException() );
1720                }
1721    
1722                // JDK: As of JDK 6, "new IOException( message, cause )".
1723                throw (IOException) new IOException( message ).initCause( e );
1724            }
1725            catch ( final TransformerException e )
1726            {
1727                String message = getMessage( e );
1728                if ( message == null && e.getException() != null )
1729                {
1730                    message = getMessage( e.getException() );
1731                }
1732    
1733                // JDK: As of JDK 6, "new IOException( message, cause )".
1734                throw (IOException) new IOException( message ).initCause( e );
1735            }
1736        }
1737    
1738        /**
1739         * Gets an attribute from a java class.
1740         *
1741         * @param clazz The java class to get an attribute from.
1742         * @param attributeName The name of the attribute to get.
1743         *
1744         * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null}, if no such attribute
1745         * exists.
1746         *
1747         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1748         * @throws IOException if getting the attribute fails.
1749         *
1750         * @see JavaClass#getAttributes()
1751         */
1752        public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
1753        {
1754            if ( clazz == null )
1755            {
1756                throw new NullPointerException( "clazz" );
1757            }
1758            if ( attributeName == null )
1759            {
1760                throw new NullPointerException( "attributeName" );
1761            }
1762    
1763            final Attribute[] attributes = clazz.getAttributes();
1764    
1765            for ( int i = attributes.length - 1; i >= 0; i-- )
1766            {
1767                final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1768    
1769                if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1770                {
1771                    final Unknown unknown = (Unknown) attributes[i];
1772                    return unknown.getBytes();
1773                }
1774            }
1775    
1776            return null;
1777        }
1778    
1779        /**
1780         * Adds or updates an attribute in a java class.
1781         *
1782         * @param clazz The class to update an attribute of.
1783         * @param attributeName The name of the attribute to update.
1784         * @param data The new data of the attribute to update the {@code clazz} with.
1785         *
1786         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1787         * @throws IOException if updating the class file fails.
1788         *
1789         * @see JavaClass#getAttributes()
1790         */
1791        public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
1792            throws IOException
1793        {
1794            if ( clazz == null )
1795            {
1796                throw new NullPointerException( "clazz" );
1797            }
1798            if ( attributeName == null )
1799            {
1800                throw new NullPointerException( "attributeName" );
1801            }
1802    
1803            final byte[] attributeData = data != null ? data : NO_BYTES;
1804    
1805            /*
1806             The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
1807    
1808             A Java virtual machine implementation is required to silently ignore any
1809             or all attributes in the attributes table of a ClassFile structure that
1810             it does not recognize. Attributes not defined in this specification are
1811             not allowed to affect the semantics of the class file, but only to
1812             provide additional descriptive information (ยง4.7.1).
1813             */
1814            Attribute[] attributes = clazz.getAttributes();
1815    
1816            int attributeIndex = -1;
1817            int nameIndex = -1;
1818    
1819            for ( int i = attributes.length - 1; i >= 0; i-- )
1820            {
1821                final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1822    
1823                if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1824                {
1825                    attributeIndex = i;
1826                    nameIndex = attributes[i].getNameIndex();
1827                }
1828            }
1829    
1830            if ( nameIndex == -1 )
1831            {
1832                final Constant[] pool = clazz.getConstantPool().getConstantPool();
1833                final Constant[] tmp = new Constant[ pool.length + 1 ];
1834                System.arraycopy( pool, 0, tmp, 0, pool.length );
1835                tmp[pool.length] = new ConstantUtf8( attributeName );
1836                nameIndex = pool.length;
1837                clazz.setConstantPool( new ConstantPool( tmp ) );
1838            }
1839    
1840            final Unknown unknown = new Unknown( nameIndex, attributeData.length, attributeData, clazz.getConstantPool() );
1841    
1842            if ( attributeIndex == -1 )
1843            {
1844                final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
1845                System.arraycopy( attributes, 0, tmp, 0, attributes.length );
1846                tmp[attributes.length] = unknown;
1847                attributes = tmp;
1848            }
1849            else
1850            {
1851                attributes[attributeIndex] = unknown;
1852            }
1853    
1854            clazz.setAttributes( attributes );
1855        }
1856    
1857        /**
1858         * Encodes a model object to a byte array.
1859         *
1860         * @param marshaller The marshaller to use for encoding the object.
1861         * @param modelObject The model object to encode.
1862         *
1863         * @return GZIP compressed XML document of {@code modelObject}.
1864         *
1865         * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
1866         * @throws IOException if encoding {@code modelObject} fails.
1867         *
1868         * @see #decodeModelObject(javax.xml.bind.Unmarshaller, byte[], java.lang.Class)
1869         */
1870        public byte[] encodeModelObject( final Marshaller marshaller, final JAXBElement<? extends ModelObject> modelObject )
1871            throws IOException
1872        {
1873            if ( marshaller == null )
1874            {
1875                throw new NullPointerException( "marshaller" );
1876            }
1877            if ( modelObject == null )
1878            {
1879                throw new NullPointerException( "modelObject" );
1880            }
1881    
1882            try
1883            {
1884                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1885                final GZIPOutputStream out = new GZIPOutputStream( baos );
1886                marshaller.marshal( modelObject, out );
1887                out.close();
1888                return baos.toByteArray();
1889            }
1890            catch ( final JAXBException e )
1891            {
1892                String message = getMessage( e );
1893                if ( message == null && e.getLinkedException() != null )
1894                {
1895                    message = getMessage( e.getLinkedException() );
1896                }
1897    
1898                // JDK: As of JDK 6, "new IOException( message, cause )".
1899                throw (IOException) new IOException( message ).initCause( e );
1900            }
1901        }
1902    
1903        /**
1904         * Decodes a model object from a byte array.
1905         *
1906         * @param unmarshaller The unmarshaller to use for decoding the object.
1907         * @param bytes The encoded model object to decode.
1908         * @param type The class of the type of the encoded model object.
1909         * @param <T> The type of the encoded model object.
1910         *
1911         * @return Model object decoded from {@code bytes}.
1912         *
1913         * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
1914         * @throws IOException if decoding {@code bytes} fails.
1915         *
1916         * @see #encodeModelObject(javax.xml.bind.Marshaller, javax.xml.bind.JAXBElement)
1917         */
1918        public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
1919                                                            final Class<T> type ) throws IOException
1920        {
1921            if ( unmarshaller == null )
1922            {
1923                throw new NullPointerException( "unmarshaller" );
1924            }
1925            if ( bytes == null )
1926            {
1927                throw new NullPointerException( "bytes" );
1928            }
1929            if ( type == null )
1930            {
1931                throw new NullPointerException( "type" );
1932            }
1933    
1934            try
1935            {
1936                final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
1937                final GZIPInputStream in = new GZIPInputStream( bais );
1938                final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
1939                in.close();
1940                return element.getValue();
1941            }
1942            catch ( final JAXBException e )
1943            {
1944                String message = getMessage( e );
1945                if ( message == null && e.getLinkedException() != null )
1946                {
1947                    message = getMessage( e.getLinkedException() );
1948                }
1949    
1950                // JDK: As of JDK 6, "new IOException( message, cause )".
1951                throw (IOException) new IOException( message ).initCause( e );
1952            }
1953        }
1954    
1955        private void commitModelObjects( final Specifications specifications, final Implementations implementations,
1956                                         final Marshaller marshaller, final File classesDirectory ) throws IOException
1957        {
1958            if ( specifications != null )
1959            {
1960                for ( int i = specifications.getSpecification().size() - 1; i >= 0; i-- )
1961                {
1962                    this.commitModelObjects( specifications.getSpecification().get( i ), marshaller, classesDirectory );
1963                }
1964            }
1965    
1966            if ( implementations != null )
1967            {
1968                for ( int i = implementations.getImplementation().size() - 1; i >= 0; i-- )
1969                {
1970                    this.commitModelObjects( implementations.getImplementation().get( i ), marshaller, classesDirectory );
1971                }
1972            }
1973        }
1974    
1975        private void commitModelObjects( final Specification specification, final Marshaller marshaller,
1976                                         final File classesDirectory ) throws IOException
1977        {
1978            if ( specification.isClassDeclaration() )
1979            {
1980                final String classLocation = specification.getClazz().replace( '.', File.separatorChar ) + ".class";
1981                final File classFile = new File( classesDirectory, classLocation );
1982    
1983                if ( !classesDirectory.isDirectory() )
1984                {
1985                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
1986                }
1987                if ( !classFile.isFile() )
1988                {
1989                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
1990                }
1991                if ( !( classFile.canRead() && classFile.canWrite() ) )
1992                {
1993                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
1994                }
1995    
1996                if ( this.isLoggable( Level.INFO ) )
1997                {
1998                    this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
1999                }
2000    
2001                final JavaClass javaClass = new ClassParser( classFile.getAbsolutePath() ).parse();
2002                this.commitModelObjects( specification, marshaller, javaClass );
2003                javaClass.dump( classFile );
2004            }
2005        }
2006    
2007        private void commitModelObjects( final Implementation implementation, final Marshaller marshaller,
2008                                         final File classesDirectory ) throws IOException
2009        {
2010            if ( implementation.isClassDeclaration() )
2011            {
2012                final String classLocation = implementation.getClazz().replace( '.', File.separatorChar ) + ".class";
2013                final File classFile = new File( classesDirectory, classLocation );
2014    
2015                if ( !classesDirectory.isDirectory() )
2016                {
2017                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
2018                }
2019                if ( !classFile.isFile() )
2020                {
2021                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
2022                }
2023                if ( !( classFile.canRead() && classFile.canWrite() ) )
2024                {
2025                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
2026                }
2027    
2028                if ( this.isLoggable( Level.INFO ) )
2029                {
2030                    this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
2031                }
2032    
2033                final JavaClass javaClass = new ClassParser( classFile.getAbsolutePath() ).parse();
2034                this.commitModelObjects( implementation, marshaller, javaClass );
2035                javaClass.dump( classFile );
2036            }
2037        }
2038    
2039        private ModelValidationReport validateModelObjects( final Specifications specifications,
2040                                                            final Implementations implementations,
2041                                                            final Unmarshaller unmarshaller, final File classesDirectory )
2042            throws IOException
2043        {
2044            final ModelValidationReport report = new ModelValidationReport();
2045    
2046            if ( specifications != null )
2047            {
2048                for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
2049                {
2050                    final ModelValidationReport current = this.validateModelObjects(
2051                        specifications.getSpecification().get( i ), unmarshaller, classesDirectory );
2052    
2053                    report.getDetails().addAll( current.getDetails() );
2054                }
2055            }
2056    
2057            if ( implementations != null )
2058            {
2059                for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
2060                {
2061                    final ModelValidationReport current = this.validateModelObjects(
2062                        implementations.getImplementation().get( i ), unmarshaller, classesDirectory );
2063    
2064                    report.getDetails().addAll( current.getDetails() );
2065                }
2066            }
2067    
2068            return report;
2069        }
2070    
2071        private ModelValidationReport validateModelObjects( final Specification specification,
2072                                                            final Unmarshaller unmarshaller,
2073                                                            final File classesDirectory ) throws IOException
2074        {
2075            final ModelValidationReport report = new ModelValidationReport();
2076    
2077            if ( specification.isClassDeclaration() )
2078            {
2079                final String classLocation = specification.getClazz().replace( '.', File.separatorChar ) + ".class";
2080                final File classFile = new File( classesDirectory, classLocation );
2081    
2082                if ( !classesDirectory.isDirectory() )
2083                {
2084                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
2085                }
2086                if ( !classFile.isFile() )
2087                {
2088                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
2089                }
2090                if ( !classFile.canRead() )
2091                {
2092                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
2093                }
2094    
2095                if ( this.isLoggable( Level.INFO ) )
2096                {
2097                    this.log( Level.INFO, getMessage( "validating", classFile.getAbsolutePath() ), null );
2098                }
2099    
2100                final ModelValidationReport current = this.validateModelObjects(
2101                    specification, unmarshaller, new ClassParser( classFile.getAbsolutePath() ).parse() );
2102    
2103                report.getDetails().addAll( current.getDetails() );
2104            }
2105    
2106            return report;
2107        }
2108    
2109        private ModelValidationReport validateModelObjects( final Implementation implementation,
2110                                                            final Unmarshaller unmarshaller,
2111                                                            final File classesDirectory ) throws IOException
2112        {
2113            final ModelValidationReport report = new ModelValidationReport();
2114    
2115            if ( implementation.isClassDeclaration() )
2116            {
2117                final String classLocation = implementation.getClazz().replace( '.', File.separatorChar ) + ".class";
2118                final File classFile = new File( classesDirectory, classLocation );
2119    
2120                if ( !classesDirectory.isDirectory() )
2121                {
2122                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
2123                }
2124                if ( !classFile.isFile() )
2125                {
2126                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
2127                }
2128                if ( !classFile.canRead() )
2129                {
2130                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
2131                }
2132    
2133                if ( this.isLoggable( Level.INFO ) )
2134                {
2135                    this.log( Level.INFO, getMessage( "validating", classFile.getAbsolutePath() ), null );
2136                }
2137    
2138                final ModelValidationReport current = this.validateModelObjects(
2139                    implementation, unmarshaller, new ClassParser( classFile.getAbsolutePath() ).parse() );
2140    
2141                report.getDetails().addAll( current.getDetails() );
2142            }
2143    
2144            return report;
2145        }
2146    
2147        private ModelValidationReport validateModelObjects( final Specifications specifications,
2148                                                            final Implementations implementations,
2149                                                            final Unmarshaller unmarshaller, final ModelContext context )
2150            throws IOException, ModelException
2151        {
2152            final ModelValidationReport report = new ModelValidationReport();
2153    
2154            if ( specifications != null )
2155            {
2156                for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
2157                {
2158                    final ModelValidationReport current = this.validateModelObjects(
2159                        specifications.getSpecification().get( i ), unmarshaller, context );
2160    
2161                    report.getDetails().addAll( current.getDetails() );
2162                }
2163            }
2164    
2165            if ( implementations != null )
2166            {
2167                for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
2168                {
2169                    final ModelValidationReport current = this.validateModelObjects(
2170                        implementations.getImplementation().get( i ), unmarshaller, context );
2171    
2172                    report.getDetails().addAll( current.getDetails() );
2173                }
2174            }
2175    
2176            return report;
2177        }
2178    
2179        private ModelValidationReport validateModelObjects( final Specification specification,
2180                                                            final Unmarshaller unmarshaller,
2181                                                            final ModelContext context ) throws IOException, ModelException
2182        {
2183            final ModelValidationReport report = new ModelValidationReport();
2184    
2185            if ( specification.isClassDeclaration() )
2186            {
2187                final String classLocation = specification.getClazz().replace( '.', '/' ) + ".class";
2188    
2189                final URL classUrl = context.findResource( classLocation );
2190    
2191                if ( classUrl == null )
2192                {
2193                    throw new IOException( getMessage( "resourceNotFound", classLocation ) );
2194                }
2195    
2196                if ( this.isLoggable( Level.INFO ) )
2197                {
2198                    this.log( Level.INFO, getMessage( "validatingSpecification", specification.getIdentifier() ), null );
2199                }
2200    
2201                InputStream in = null;
2202                JavaClass javaClass = null;
2203                boolean suppressExceptionOnClose = true;
2204    
2205                try
2206                {
2207                    in = classUrl.openStream();
2208                    javaClass = new ClassParser( in, classUrl.toExternalForm() ).parse();
2209                    suppressExceptionOnClose = false;
2210                }
2211                finally
2212                {
2213                    try
2214                    {
2215                        if ( in != null )
2216                        {
2217                            in.close();
2218                        }
2219                    }
2220                    catch ( final IOException e )
2221                    {
2222                        if ( suppressExceptionOnClose )
2223                        {
2224                            this.log( Level.SEVERE, getMessage( e ), e );
2225                        }
2226                        else
2227                        {
2228                            throw e;
2229                        }
2230                    }
2231                }
2232    
2233                final ModelValidationReport current = this.validateModelObjects( specification, unmarshaller, javaClass );
2234                report.getDetails().addAll( current.getDetails() );
2235            }
2236    
2237            return report;
2238        }
2239    
2240        private ModelValidationReport validateModelObjects( final Implementation implementation,
2241                                                            final Unmarshaller unmarshaller,
2242                                                            final ModelContext context ) throws IOException, ModelException
2243        {
2244            final ModelValidationReport report = new ModelValidationReport();
2245    
2246            if ( implementation.isClassDeclaration() )
2247            {
2248                final String classLocation = implementation.getClazz().replace( '.', '/' ) + ".class";
2249    
2250                final URL classUrl = context.findResource( classLocation );
2251    
2252                if ( classUrl == null )
2253                {
2254                    throw new IOException( getMessage( "resourceNotFound", classLocation ) );
2255                }
2256    
2257                if ( this.isLoggable( Level.INFO ) )
2258                {
2259                    this.log( Level.INFO, getMessage( "validatingImplementation", implementation.getIdentifier() ), null );
2260                }
2261    
2262                InputStream in = null;
2263                JavaClass javaClass = null;
2264                boolean suppressExceptionOnClose = true;
2265    
2266                try
2267                {
2268                    in = classUrl.openStream();
2269                    javaClass = new ClassParser( in, classUrl.toExternalForm() ).parse();
2270                    suppressExceptionOnClose = false;
2271                }
2272                finally
2273                {
2274                    try
2275                    {
2276                        if ( in != null )
2277                        {
2278                            in.close();
2279                        }
2280                    }
2281                    catch ( final IOException e )
2282                    {
2283                        if ( suppressExceptionOnClose )
2284                        {
2285                            this.log( Level.SEVERE, getMessage( e ), e );
2286                        }
2287                        else
2288                        {
2289                            throw e;
2290                        }
2291                    }
2292                }
2293    
2294                final ModelValidationReport current = this.validateModelObjects( implementation, unmarshaller, javaClass );
2295                report.getDetails().addAll( current.getDetails() );
2296            }
2297    
2298            return report;
2299        }
2300    
2301        private void transformModelObjects( final Specifications specifications, final Implementations implementations,
2302                                            final Unmarshaller unmarshaller, final Marshaller marshaller,
2303                                            final File classesDirectory, final List<Transformer> transformers )
2304            throws IOException
2305        {
2306            if ( specifications != null )
2307            {
2308                for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
2309                {
2310                    this.transformModelObjects( specifications.getSpecification().get( i ), marshaller, unmarshaller,
2311                                                classesDirectory, transformers );
2312    
2313                }
2314            }
2315    
2316            if ( implementations != null )
2317            {
2318                for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
2319                {
2320                    this.transformModelObjects( implementations.getImplementation().get( i ), marshaller, unmarshaller,
2321                                                classesDirectory, transformers );
2322    
2323                }
2324            }
2325        }
2326    
2327        private void transformModelObjects( final Specification specification, final Marshaller marshaller,
2328                                            final Unmarshaller unmarshaller, final File classesDirectory,
2329                                            final List<Transformer> transformers ) throws IOException
2330        {
2331            if ( specification.isClassDeclaration() )
2332            {
2333                final String classLocation = specification.getClazz().replace( '.', File.separatorChar ) + ".class";
2334                final File classFile = new File( classesDirectory, classLocation );
2335    
2336                if ( !classesDirectory.isDirectory() )
2337                {
2338                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
2339                }
2340                if ( !classFile.isFile() )
2341                {
2342                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
2343                }
2344                if ( !( classFile.canRead() && classFile.canWrite() ) )
2345                {
2346                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
2347                }
2348    
2349                if ( this.isLoggable( Level.INFO ) )
2350                {
2351                    this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
2352                }
2353    
2354                final JavaClass javaClass = new ClassParser( classFile.getAbsolutePath() ).parse();
2355                this.transformModelObjects( specification, marshaller, unmarshaller, javaClass, transformers );
2356                javaClass.dump( classFile );
2357            }
2358        }
2359    
2360        private void transformModelObjects( final Implementation implementation, final Marshaller marshaller,
2361                                            final Unmarshaller unmarshaller, final File classesDirectory,
2362                                            final List<Transformer> transformers ) throws IOException
2363        {
2364            if ( implementation.isClassDeclaration() )
2365            {
2366                final String classLocation = implementation.getClazz().replace( '.', File.separatorChar ) + ".class";
2367                final File classFile = new File( classesDirectory, classLocation );
2368    
2369                if ( !classesDirectory.isDirectory() )
2370                {
2371                    throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
2372                }
2373                if ( !classFile.isFile() )
2374                {
2375                    throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
2376                }
2377                if ( !( classFile.canRead() && classFile.canWrite() ) )
2378                {
2379                    throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
2380                }
2381    
2382                if ( this.isLoggable( Level.INFO ) )
2383                {
2384                    this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
2385                }
2386    
2387                final JavaClass javaClass = new ClassParser( classFile.getAbsolutePath() ).parse();
2388                this.transformModelObjects( implementation, marshaller, unmarshaller, javaClass, transformers );
2389                javaClass.dump( classFile );
2390            }
2391        }
2392    
2393        private static String getMessage( final String key, final Object... arguments )
2394        {
2395            return MessageFormat.format( ResourceBundle.getBundle(
2396                ClassFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2397    
2398        }
2399    
2400        private static String getMessage( final Throwable t )
2401        {
2402            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2403        }
2404    
2405    }