001    /*
002     *   Copyright (c) 2009 The JOMC Project
003     *   Copyright (c) 2005 Christian Schulte <schulte2005@users.sourceforge.net>
004     *   All rights reserved.
005     *
006     *   Redistribution and use in source and binary forms, with or without
007     *   modification, are permitted provided that the following conditions
008     *   are met:
009     *
010     *     o Redistributions of source code must retain the above copyright
011     *       notice, this list of conditions and the following disclaimer.
012     *
013     *     o Redistributions in binary form must reproduce the above copyright
014     *       notice, this list of conditions and the following disclaimer in
015     *       the documentation and/or other materials provided with the
016     *       distribution.
017     *
018     *   THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019     *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020     *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021     *   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022     *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023     *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024     *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025     *   OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026     *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027     *   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028     *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029     *
030     *   $Id: ClassFileProcessor.java 1562 2010-03-07 01:06:40Z schulte2005 $
031     *
032     */
033    package org.jomc.tools;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.ByteArrayOutputStream;
037    import java.io.File;
038    import java.io.IOException;
039    import java.io.InputStream;
040    import java.net.URL;
041    import java.text.MessageFormat;
042    import java.util.List;
043    import java.util.ResourceBundle;
044    import java.util.logging.Level;
045    import java.util.zip.GZIPInputStream;
046    import java.util.zip.GZIPOutputStream;
047    import javax.xml.bind.JAXBElement;
048    import javax.xml.bind.JAXBException;
049    import javax.xml.bind.Marshaller;
050    import javax.xml.bind.Unmarshaller;
051    import javax.xml.bind.util.JAXBResult;
052    import javax.xml.bind.util.JAXBSource;
053    import javax.xml.transform.Transformer;
054    import javax.xml.transform.TransformerException;
055    import org.apache.bcel.classfile.Attribute;
056    import org.apache.bcel.classfile.ClassParser;
057    import org.apache.bcel.classfile.Constant;
058    import org.apache.bcel.classfile.ConstantPool;
059    import org.apache.bcel.classfile.ConstantUtf8;
060    import org.apache.bcel.classfile.JavaClass;
061    import org.apache.bcel.classfile.Unknown;
062    import org.jomc.model.Dependencies;
063    import org.jomc.model.Dependency;
064    import org.jomc.model.Implementation;
065    import org.jomc.model.Message;
066    import org.jomc.model.Messages;
067    import org.jomc.model.ModelObject;
068    import org.jomc.model.ModelValidationReport;
069    import org.jomc.model.Module;
070    import org.jomc.model.ObjectFactory;
071    import org.jomc.model.Properties;
072    import org.jomc.model.Property;
073    import org.jomc.model.Specification;
074    import org.jomc.model.SpecificationReference;
075    import org.jomc.model.Specifications;
076    import org.jomc.util.ParseException;
077    import org.jomc.util.TokenMgrError;
078    import org.jomc.util.VersionParser;
079    
080    /**
081     * Processes class files.
082     *
083     * <p><b>Use cases</b><br/><ul>
084     * <li>{@link #commitModelObjects(Marshaller, File) }</li>
085     * <li>{@link #commitModelObjects(Module, Marshaller, File) }</li>
086     * <li>{@link #commitModelObjects(Specification, Marshaller, File) }</li>
087     * <li>{@link #commitModelObjects(Implementation, Marshaller, File) }</li>
088     * <li>{@link #validateModelObjects(Unmarshaller, File) }</li>
089     * <li>{@link #validateModelObjects(Unmarshaller, ClassLoader) }</li>
090     * <li>{@link #validateModelObjects(Module, Unmarshaller, File) }</li>
091     * <li>{@link #validateModelObjects(Module, Unmarshaller, ClassLoader) }</li>
092     * <li>{@link #validateModelObjects(Specification, Unmarshaller, JavaClass) }</li>
093     * <li>{@link #validateModelObjects(Implementation, Unmarshaller, JavaClass) }</li>
094     * <li>{@link #transformModelObjects(Marshaller, Unmarshaller, File, List) }</li>
095     * <li>{@link #transformModelObjects(Module, Marshaller, Unmarshaller, File, List) }</li>
096     * <li>{@link #transformModelObjects(Specification, Marshaller, Unmarshaller, JavaClass, List) }</li>
097     * <li>{@link #transformModelObjects(Implementation, Marshaller, Unmarshaller, JavaClass, List) }</li>
098     * </ul></p>
099     *
100     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
101     * @version $Id: ClassFileProcessor.java 1562 2010-03-07 01:06:40Z schulte2005 $
102     *
103     * @see #getModules()
104     */
105    public class ClassFileProcessor extends JomcTool
106    {
107    
108        /** Creates a new {@code ClassFileProcessor} instance. */
109        public ClassFileProcessor()
110        {
111            super();
112        }
113    
114        /**
115         * Creates a new {@code ClassFileProcessor} instance taking a {@code ClassFileProcessor} instance to initialize the
116         * instance with.
117         *
118         * @param tool The instance to initialize the new instance with,
119         *
120         * @throws NullPointerException if {@code tool} is {@code null}.
121         * @throws IOException if copying {@code tool} fails.
122         */
123        public ClassFileProcessor( final ClassFileProcessor tool ) throws IOException
124        {
125            super( tool );
126        }
127    
128        /**
129         * Commits model objects of the modules of the instance to class files.
130         *
131         * @param marshaller The marshaller to use for committing the model objects.
132         * @param classesDirectory The directory holding the class files.
133         *
134         * @throws NullPointerException if {@code marshaller} or {@code classesDirectory} is {@code null}.
135         * @throws IOException if committing model objects fails.
136         *
137         * @see #commitModelObjects(org.jomc.model.Module, javax.xml.bind.Marshaller, java.io.File)
138         * @see org.jomc.model.ModelContext#createMarshaller()
139         */
140        public void commitModelObjects( final Marshaller marshaller, final File classesDirectory ) throws IOException
141        {
142            if ( marshaller == null )
143            {
144                throw new NullPointerException( "marshaller" );
145            }
146            if ( classesDirectory == null )
147            {
148                throw new NullPointerException( "classesDirectory" );
149            }
150    
151            for ( Module m : this.getModules().getModule() )
152            {
153                this.commitModelObjects( m, marshaller, classesDirectory );
154            }
155        }
156    
157        /**
158         * Commits model objects of a given module of the modules of the instance to class files.
159         *
160         * @param module The module to process.
161         * @param marshaller The marshaller to use for committing the model objects.
162         * @param classesDirectory The directory holding the class files.
163         *
164         * @throws NullPointerException if {@code module}, {@code marshaller} or {@code classesDirectory} is {@code null}.
165         * @throws IOException if committing model objects fails.
166         *
167         * @see #commitModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, java.io.File)
168         * @see #commitModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, java.io.File)
169         * @see org.jomc.model.ModelContext#createMarshaller()
170         */
171        public void commitModelObjects( final Module module, final Marshaller marshaller, final File classesDirectory )
172            throws IOException
173        {
174            if ( module == null )
175            {
176                throw new NullPointerException( "module" );
177            }
178            if ( marshaller == null )
179            {
180                throw new NullPointerException( "marshaller" );
181            }
182            if ( classesDirectory == null )
183            {
184                throw new NullPointerException( "classesDirectory" );
185            }
186    
187            if ( module.getSpecifications() != null )
188            {
189                for ( Specification s : module.getSpecifications().getSpecification() )
190                {
191                    this.commitModelObjects( s, marshaller, classesDirectory );
192                }
193            }
194            if ( module.getImplementations() != null )
195            {
196                for ( Implementation i : module.getImplementations().getImplementation() )
197                {
198                    this.commitModelObjects( i, marshaller, classesDirectory );
199                }
200            }
201        }
202    
203        /**
204         * Commits model objects of a given specification of the modules of the instance to class files.
205         *
206         * @param specification The specification to process.
207         * @param marshaller The marshaller to use for committing the model objects.
208         * @param classesDirectory The directory holding the class files.
209         *
210         * @throws NullPointerException if {@code specification}, {@code marshaller} or {@code classesDirectory} is
211         * {@code null}.
212         * @throws IOException if committing model objects fails.
213         *
214         * @see org.jomc.model.ModelContext#createMarshaller()
215         */
216        public void commitModelObjects( final Specification specification, final Marshaller marshaller,
217                                        final File classesDirectory ) throws IOException
218        {
219            if ( specification == null )
220            {
221                throw new NullPointerException( "specification" );
222            }
223            if ( marshaller == null )
224            {
225                throw new NullPointerException( "marshaller" );
226            }
227            if ( classesDirectory == null )
228            {
229                throw new NullPointerException( "classesDirectory" );
230            }
231    
232            if ( specification.isClassDeclaration() )
233            {
234                final String classLocation = specification.getClazz().replace( '.', File.separatorChar ) + ".class";
235                final File classFile = new File( classesDirectory, classLocation );
236                if ( this.isLoggable( Level.INFO ) )
237                {
238                    this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
239                }
240    
241                final JavaClass javaClass = this.getJavaClass( classFile );
242                this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
243                    marshaller, new ObjectFactory().createSpecification( specification ) ) );
244    
245                javaClass.dump( classFile );
246            }
247        }
248    
249        /**
250         * Commits model objects of a given implementation of the modules of the instance to class files.
251         *
252         * @param implementation The implementation to process.
253         * @param marshaller The marshaller to use for committing the model objects.
254         * @param classesDirectory The directory holding the class files.
255         *
256         * @throws NullPointerException if {@code implementation}, {@code marshaller} or {@code classesDirectory} is
257         * {@code null}.
258         * @throws IOException if committing model objects fails.
259         *
260         * @see org.jomc.model.ModelContext#createMarshaller()
261         */
262        public void commitModelObjects( final Implementation implementation, final Marshaller marshaller,
263                                        final File classesDirectory ) throws IOException
264        {
265            if ( implementation == null )
266            {
267                throw new NullPointerException( "implementation" );
268            }
269            if ( marshaller == null )
270            {
271                throw new NullPointerException( "marshaller" );
272            }
273            if ( classesDirectory == null )
274            {
275                throw new NullPointerException( "classesDirectory" );
276            }
277    
278            if ( implementation.isClassDeclaration() )
279            {
280                Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
281                if ( dependencies == null )
282                {
283                    dependencies = new Dependencies();
284                }
285    
286                Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
287                if ( properties == null )
288                {
289                    properties = new Properties();
290                }
291    
292                Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
293                if ( messages == null )
294                {
295                    messages = new Messages();
296                }
297    
298                Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
299                if ( specifications == null )
300                {
301                    specifications = new Specifications();
302                }
303    
304                for ( SpecificationReference r : specifications.getReference() )
305                {
306                    if ( specifications.getSpecification( r.getIdentifier() ) == null &&
307                         this.isLoggable( Level.WARNING ) )
308                    {
309                        this.log( Level.WARNING, getMessage( "unresolvedSpecification", r.getIdentifier(),
310                                                             implementation.getIdentifier() ), null );
311    
312                    }
313                }
314    
315                for ( Dependency d : dependencies.getDependency() )
316                {
317                    final Specification s = this.getModules().getSpecification( d.getIdentifier() );
318    
319                    if ( s != null )
320                    {
321                        if ( specifications.getSpecification( s.getIdentifier() ) == null )
322                        {
323                            specifications.getSpecification().add( s );
324                        }
325                    }
326                    else if ( this.isLoggable( Level.WARNING ) )
327                    {
328                        this.log( Level.WARNING, getMessage( "unresolvedDependencySpecification", d.getIdentifier(),
329                                                             d.getName(), implementation.getIdentifier() ), null );
330    
331                    }
332                }
333    
334                final String classLocation = implementation.getClazz().replace( '.', File.separatorChar ) + ".class";
335                final File classFile = new File( classesDirectory, classLocation );
336    
337                if ( this.isLoggable( Level.INFO ) )
338                {
339                    this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
340                }
341    
342                final JavaClass javaClass = this.getJavaClass( classFile );
343                final ObjectFactory of = new ObjectFactory();
344    
345                this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
346                    marshaller, of.createDependencies( dependencies ) ) );
347    
348                this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
349                    marshaller, of.createProperties( properties ) ) );
350    
351                this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
352                    marshaller, of.createMessages( messages ) ) );
353    
354                this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
355                    marshaller, of.createSpecifications( specifications ) ) );
356    
357                javaClass.dump( classFile );
358            }
359        }
360    
361        /**
362         * Validates model objects of class files.
363         *
364         * @param unmarshaller The unmarshaller to use for validating model objects.
365         * @param classesDirectory The directory holding the class files.
366         *
367         * @return The report of the validation.
368         *
369         * @throws NullPointerException if {@code unmarshaller} or {@code classesDirectory} is {@code null}.
370         * @throws IOException if validating model objects fails.
371         *
372         * @see #validateModelObjects(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.io.File)
373         * @see org.jomc.model.ModelContext#createUnmarshaller()
374         */
375        public ModelValidationReport validateModelObjects( final Unmarshaller unmarshaller, final File classesDirectory )
376            throws IOException
377        {
378            if ( unmarshaller == null )
379            {
380                throw new NullPointerException( "unmarshaller" );
381            }
382            if ( classesDirectory == null )
383            {
384                throw new NullPointerException( "classesDirectory" );
385            }
386    
387            final ModelValidationReport report = new ModelValidationReport();
388    
389            for ( Module m : this.getModules().getModule() )
390            {
391                final ModelValidationReport current = this.validateModelObjects( m, unmarshaller, classesDirectory );
392                report.getDetails().addAll( current.getDetails() );
393            }
394    
395            return report;
396        }
397    
398        /**
399         * Validates model objects of class files.
400         *
401         * @param unmarshaller The unmarshaller to use for validating model objects.
402         * @param classLoader The class loader to search for class files.
403         *
404         * @return The report of the validation.
405         *
406         * @throws NullPointerException if {@code unmarshaller} or {@code classLoader} is {@code null}.
407         * @throws IOException if validating model objects fails.
408         *
409         * @see #validateModelObjects(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.lang.ClassLoader)
410         * @see org.jomc.model.ModelContext#createUnmarshaller()
411         */
412        public ModelValidationReport validateModelObjects( final Unmarshaller unmarshaller, final ClassLoader classLoader )
413            throws IOException
414        {
415            if ( unmarshaller == null )
416            {
417                throw new NullPointerException( "unmarshaller" );
418            }
419            if ( classLoader == null )
420            {
421                throw new NullPointerException( "classLoader" );
422            }
423    
424            final ModelValidationReport report = new ModelValidationReport();
425    
426            for ( Module m : this.getModules().getModule() )
427            {
428                final ModelValidationReport current = this.validateModelObjects( m, unmarshaller, classLoader );
429                report.getDetails().addAll( current.getDetails() );
430            }
431    
432            return report;
433        }
434    
435        /**
436         * Validates model objects of class files.
437         *
438         * @param module The module to process.
439         * @param unmarshaller The unmarshaller to use for validating model objects.
440         * @param classesDirectory The directory holding the class files.
441         *
442         * @return The report of the validation.
443         *
444         * @throws NullPointerException if {@code module}, {@code unmarshaller} or {@code classesDirectory} is {@code null}.
445         * @throws IOException if validating model objects fails.
446         *
447         * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
448         * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
449         * @see org.jomc.model.ModelContext#createUnmarshaller()
450         */
451        public ModelValidationReport validateModelObjects( final Module module, final Unmarshaller unmarshaller,
452                                                           final File classesDirectory ) throws IOException
453        {
454            if ( module == null )
455            {
456                throw new NullPointerException( "module" );
457            }
458            if ( unmarshaller == null )
459            {
460                throw new NullPointerException( "unmarshaller" );
461            }
462            if ( classesDirectory == null )
463            {
464                throw new NullPointerException( "classesDirectory" );
465            }
466    
467            final ModelValidationReport report = new ModelValidationReport();
468    
469            if ( module.getSpecifications() != null )
470            {
471                for ( Specification s : module.getSpecifications().getSpecification() )
472                {
473                    if ( s.isClassDeclaration() )
474                    {
475                        final String classLocation = s.getClazz().replace( '.', File.separatorChar ) + ".class";
476                        final File classFile = new File( classesDirectory, classLocation );
477                        final ModelValidationReport current =
478                            this.validateModelObjects( s, unmarshaller, this.getJavaClass( classFile ) );
479    
480                        report.getDetails().addAll( current.getDetails() );
481                    }
482                }
483            }
484    
485            if ( module.getImplementations() != null )
486            {
487                for ( Implementation i : module.getImplementations().getImplementation() )
488                {
489                    if ( i.isClassDeclaration() )
490                    {
491                        final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
492                        final File classFile = new File( classesDirectory, classLocation );
493                        final JavaClass javaClass = this.getJavaClass( classFile );
494                        final ModelValidationReport current =
495                            this.validateModelObjects( i, unmarshaller, javaClass );
496    
497                        report.getDetails().addAll( current.getDetails() );
498                    }
499                }
500            }
501    
502            return report;
503        }
504    
505        /**
506         * Validates model objects of class files.
507         *
508         * @param module The module to process.
509         * @param unmarshaller The unmarshaller to use for validating model objects.
510         * @param classLoader The class loader to search for class files.
511         *
512         * @return The report of the validation.
513         *
514         * @throws NullPointerException if {@code module}, {@code unmarshaller} or {@code classLoader} is {@code null}.
515         * @throws IOException if validating model objects fails.
516         *
517         * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
518         * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
519         * @see org.jomc.model.ModelContext#createUnmarshaller()
520         */
521        public ModelValidationReport validateModelObjects( final Module module, final Unmarshaller unmarshaller,
522                                                           final ClassLoader classLoader ) throws IOException
523        {
524            if ( module == null )
525            {
526                throw new NullPointerException( "module" );
527            }
528            if ( unmarshaller == null )
529            {
530                throw new NullPointerException( "unmarshaller" );
531            }
532            if ( classLoader == null )
533            {
534                throw new NullPointerException( "classLoader" );
535            }
536    
537            final ModelValidationReport report = new ModelValidationReport();
538    
539            if ( module.getSpecifications() != null )
540            {
541                for ( Specification s : module.getSpecifications().getSpecification() )
542                {
543                    if ( s.isClassDeclaration() )
544                    {
545                        final String classLocation = s.getClazz().replace( '.', '/' ) + ".class";
546                        final URL classUrl = classLoader.getResource( classLocation );
547    
548                        if ( classUrl == null )
549                        {
550                            throw new IOException( getMessage( "resourceNotFound", classLocation ) );
551                        }
552    
553                        final JavaClass javaClass = this.getJavaClass( classUrl, classLocation );
554                        final ModelValidationReport current =
555                            this.validateModelObjects( s, unmarshaller, javaClass );
556    
557                        report.getDetails().addAll( current.getDetails() );
558                    }
559                }
560            }
561    
562            if ( module.getImplementations() != null )
563            {
564                for ( Implementation i : module.getImplementations().getImplementation() )
565                {
566                    if ( i.isClassDeclaration() )
567                    {
568                        final String classLocation = i.getClazz().replace( '.', '/' ) + ".class";
569                        final URL classUrl = classLoader.getResource( classLocation );
570    
571                        if ( classUrl == null )
572                        {
573                            throw new IOException( getMessage( "resourceNotFound", classLocation ) );
574                        }
575    
576                        final JavaClass javaClass = this.getJavaClass( classUrl, classLocation );
577                        final ModelValidationReport current = this.validateModelObjects( i, unmarshaller, javaClass );
578                        report.getDetails().addAll( current.getDetails() );
579                    }
580                }
581            }
582    
583            return report;
584        }
585    
586        /**
587         * Validates model objects of class files.
588         *
589         * @param specification The specification to process.
590         * @param unmarshaller The unmarshaller to use for validating model objects.
591         * @param classFile The class file to validate model objects of.
592         *
593         * @return The report of the validation.
594         *
595         * @throws NullPointerException if {@code specification}, {@code unmarshaller} or {@code classFile} is {@code null}.
596         * @throws IOException if validating model objects fails.
597         *
598         * @see org.jomc.model.ModelContext#createUnmarshaller()
599         */
600        public ModelValidationReport validateModelObjects( final Specification specification,
601                                                           final Unmarshaller unmarshaller, final JavaClass classFile )
602            throws IOException
603        {
604            if ( specification == null )
605            {
606                throw new NullPointerException( "specification" );
607            }
608            if ( unmarshaller == null )
609            {
610                throw new NullPointerException( "unmarshaller" );
611            }
612            if ( classFile == null )
613            {
614                throw new NullPointerException( "classFile" );
615            }
616    
617            if ( this.isLoggable( Level.INFO ) )
618            {
619                this.log( Level.INFO, getMessage( "validatingSpecification", specification.getIdentifier() ), null );
620            }
621    
622            final ModelValidationReport report = new ModelValidationReport();
623    
624            Specification decoded = null;
625            final byte[] bytes = this.getClassfileAttribute( classFile, Specification.class.getName() );
626            if ( bytes != null )
627            {
628                decoded = this.decodeModelObject( unmarshaller, bytes, Specification.class );
629            }
630    
631            if ( decoded != null )
632            {
633                if ( decoded.getMultiplicity() != specification.getMultiplicity() )
634                {
635                    report.getDetails().add( new ModelValidationReport.Detail(
636                        "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
637                        "illegalMultiplicity", specification.getIdentifier(), specification.getMultiplicity().value(),
638                        decoded.getMultiplicity().value() ), new ObjectFactory().createSpecification( specification ) ) );
639    
640                }
641    
642                if ( decoded.getScope() == null
643                     ? specification.getScope() != null
644                     : !decoded.getScope().equals( specification.getScope() ) )
645                {
646                    report.getDetails().add( new ModelValidationReport.Detail(
647                        "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
648                        "illegalScope", specification.getIdentifier(),
649                        specification.getScope() == null ? "Multiton" : specification.getScope(),
650                        decoded.getScope() == null ? "Multiton" : decoded.getScope() ),
651                        new ObjectFactory().createSpecification( specification ) ) );
652    
653                }
654    
655                if ( decoded.getClazz() == null
656                     ? specification.getClazz() != null
657                     : !decoded.getClazz().equals( specification.getClazz() ) )
658                {
659                    report.getDetails().add( new ModelValidationReport.Detail(
660                        "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
661                        "illegalSpecificationClass", decoded.getIdentifier(),
662                        specification.getClazz(), decoded.getClazz() ),
663                        new ObjectFactory().createSpecification( specification ) ) );
664    
665                }
666            }
667            else if ( this.isLoggable( Level.WARNING ) )
668            {
669                this.log( Level.WARNING, getMessage( "cannotValidateSpecification", specification.getIdentifier(),
670                                                     Specification.class.getName() ), null );
671    
672            }
673    
674            return report;
675        }
676    
677        /**
678         * Validates model objects of class files.
679         *
680         * @param implementation The implementation to process.
681         * @param unmarshaller The unmarshaller to use for validating model objects.
682         * @param classFile The class file to validate model objects of.
683         *
684         * @return The report of the validation.
685         *
686         * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code classFile} is {@code null}.
687         * @throws IOException if validating model objects fails.
688         *
689         * @see org.jomc.model.ModelContext#createUnmarshaller()
690         */
691        public ModelValidationReport validateModelObjects( final Implementation implementation,
692                                                           final Unmarshaller unmarshaller, final JavaClass classFile )
693            throws IOException
694        {
695            if ( implementation == null )
696            {
697                throw new NullPointerException( "implementation" );
698            }
699            if ( unmarshaller == null )
700            {
701                throw new NullPointerException( "unmarshaller" );
702            }
703            if ( classFile == null )
704            {
705                throw new NullPointerException( "classFile" );
706            }
707    
708            if ( this.isLoggable( Level.INFO ) )
709            {
710                this.log( Level.INFO, getMessage( "validatingImplementation", implementation.getIdentifier() ), null );
711            }
712    
713            final ModelValidationReport report = new ModelValidationReport();
714    
715            try
716            {
717                Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
718                if ( dependencies == null )
719                {
720                    dependencies = new Dependencies();
721                }
722    
723                Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
724                if ( properties == null )
725                {
726                    properties = new Properties();
727                }
728    
729                Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
730                if ( messages == null )
731                {
732                    messages = new Messages();
733                }
734    
735                Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
736                if ( specifications == null )
737                {
738                    specifications = new Specifications();
739                }
740    
741                Dependencies decodedDependencies = null;
742                byte[] bytes = this.getClassfileAttribute( classFile, Dependencies.class.getName() );
743                if ( bytes != null )
744                {
745                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
746                }
747    
748                Properties decodedProperties = null;
749                bytes = this.getClassfileAttribute( classFile, Properties.class.getName() );
750                if ( bytes != null )
751                {
752                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
753                }
754    
755                Messages decodedMessages = null;
756                bytes = this.getClassfileAttribute( classFile, Messages.class.getName() );
757                if ( bytes != null )
758                {
759                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
760                }
761    
762                Specifications decodedSpecifications = null;
763                bytes = this.getClassfileAttribute( classFile, Specifications.class.getName() );
764                if ( bytes != null )
765                {
766                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
767                }
768    
769                if ( decodedDependencies != null )
770                {
771                    for ( Dependency decodedDependency : decodedDependencies.getDependency() )
772                    {
773                        final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
774    
775                        if ( dependency == null )
776                        {
777                            report.getDetails().add( new ModelValidationReport.Detail(
778                                "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
779                                "missingDependency", implementation.getIdentifier(), decodedDependency.getName() ),
780                                new ObjectFactory().createImplementation( implementation ) ) );
781    
782                        }
783    
784                        final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );
785    
786                        if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null &&
787                             VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
788                        {
789                            final Module moduleOfSpecification =
790                                this.getModules().getModuleOfSpecification( s.getIdentifier() );
791    
792                            final Module moduleOfImplementation =
793                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
794    
795                            report.getDetails().add( new ModelValidationReport.Detail(
796                                "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
797                                "incompatibleDependency", classFile.getClassName(),
798                                moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
799                                s.getIdentifier(),
800                                moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
801                                decodedDependency.getVersion(), s.getVersion() ),
802                                new ObjectFactory().createImplementation( implementation ) ) );
803    
804                        }
805                    }
806                }
807                else if ( this.isLoggable( Level.WARNING ) )
808                {
809                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
810                                                         Dependencies.class.getName() ), null );
811    
812                }
813    
814                if ( decodedProperties != null )
815                {
816                    for ( Property decodedProperty : decodedProperties.getProperty() )
817                    {
818                        final Property property = properties.getProperty( decodedProperty.getName() );
819    
820                        if ( property == null )
821                        {
822                            report.getDetails().add( new ModelValidationReport.Detail(
823                                "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
824                                "missingProperty", implementation.getIdentifier(), decodedProperty.getName() ),
825                                new ObjectFactory().createImplementation( implementation ) ) );
826    
827                        }
828                        else
829                        {
830                            if ( decodedProperty.getType() == null ? property.getType() != null
831                                 : !decodedProperty.getType().equals( property.getType() ) )
832                            {
833                                report.getDetails().add( new ModelValidationReport.Detail(
834                                    "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
835                                    "illegalPropertyType", implementation.getIdentifier(), decodedProperty.getName(),
836                                    property.getType() == null ? "default" : property.getType(),
837                                    decodedProperty.getType() == null ? "default" : decodedProperty.getType() ),
838                                    new ObjectFactory().createImplementation( implementation ) ) );
839    
840                            }
841                        }
842                    }
843                }
844                else if ( this.isLoggable( Level.WARNING ) )
845                {
846                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
847                                                         Properties.class.getName() ), null );
848    
849                }
850    
851                if ( decodedMessages != null )
852                {
853                    for ( Message decodedMessage : decodedMessages.getMessage() )
854                    {
855                        final Message message = messages.getMessage( decodedMessage.getName() );
856    
857                        if ( message == null )
858                        {
859                            report.getDetails().add( new ModelValidationReport.Detail(
860                                "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE, getMessage(
861                                "missingMessage", implementation.getIdentifier(), decodedMessage.getName() ),
862                                new ObjectFactory().createImplementation( implementation ) ) );
863    
864                        }
865                    }
866                }
867                else if ( this.isLoggable( Level.WARNING ) )
868                {
869                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation",
870                                                         implementation.getIdentifier(), Messages.class.getName() ), null );
871    
872                }
873    
874                if ( decodedSpecifications != null )
875                {
876                    for ( Specification decodedSpecification : decodedSpecifications.getSpecification() )
877                    {
878                        final Specification specification =
879                            this.getModules().getSpecification( decodedSpecification.getIdentifier() );
880    
881                        if ( specification == null )
882                        {
883                            report.getDetails().add( new ModelValidationReport.Detail(
884                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
885                                "missingSpecification", implementation.getIdentifier(),
886                                decodedSpecification.getIdentifier() ),
887                                new ObjectFactory().createImplementation( implementation ) ) );
888    
889                        }
890                        else
891                        {
892                            if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
893                            {
894                                report.getDetails().add( new ModelValidationReport.Detail(
895                                    "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
896                                    "illegalMultiplicity", specification.getIdentifier(),
897                                    specification.getMultiplicity().value(),
898                                    decodedSpecification.getMultiplicity().value() ),
899                                    new ObjectFactory().createImplementation( implementation ) ) );
900    
901                            }
902    
903                            if ( decodedSpecification.getScope() == null
904                                 ? specification.getScope() != null
905                                 : !decodedSpecification.getScope().equals( specification.getScope() ) )
906                            {
907                                report.getDetails().add( new ModelValidationReport.Detail(
908                                    "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
909                                    "illegalScope", decodedSpecification.getIdentifier(),
910                                    specification.getScope() == null ? "Multiton" : specification.getScope(),
911                                    decodedSpecification.getScope() == null ? "Multiton" : decodedSpecification.getScope() ),
912                                    new ObjectFactory().createImplementation( implementation ) ) );
913    
914                            }
915    
916                            if ( decodedSpecification.getClazz() == null ? specification.getClazz() != null
917                                 : !decodedSpecification.getClazz().equals( specification.getClazz() ) )
918                            {
919                                report.getDetails().add( new ModelValidationReport.Detail(
920                                    "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
921                                    "illegalSpecificationClass", decodedSpecification.getIdentifier(),
922                                    specification.getClazz(), decodedSpecification.getClazz() ),
923                                    new ObjectFactory().createImplementation( implementation ) ) );
924    
925                            }
926                        }
927                    }
928    
929                    for ( SpecificationReference decodedReference : decodedSpecifications.getReference() )
930                    {
931                        final Specification specification =
932                            specifications.getSpecification( decodedReference.getIdentifier() );
933    
934                        if ( specification == null )
935                        {
936                            report.getDetails().add( new ModelValidationReport.Detail(
937                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
938                                "missingSpecification", implementation.getIdentifier(), decodedReference.getIdentifier() ),
939                                new ObjectFactory().createImplementation( implementation ) ) );
940    
941                        }
942                        else if ( decodedReference.getVersion() != null && specification.getVersion() != null &&
943                                  VersionParser.compare( decodedReference.getVersion(), specification.getVersion() ) != 0 )
944                        {
945                            final Module moduleOfSpecification =
946                                this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );
947    
948                            final Module moduleOfImplementation =
949                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
950    
951                            report.getDetails().add( new ModelValidationReport.Detail(
952                                "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE, getMessage(
953                                "incompatibleImplementation", classFile.getClassName(),
954                                moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
955                                specification.getIdentifier(),
956                                moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
957                                decodedReference.getVersion(), specification.getVersion() ),
958                                new ObjectFactory().createImplementation( implementation ) ) );
959    
960                        }
961                    }
962                }
963                else if ( this.isLoggable( Level.WARNING ) )
964                {
965                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
966                                                         Specifications.class.getName() ), null );
967    
968                }
969    
970                return report;
971            }
972            catch ( final ParseException e )
973            {
974                throw (IOException) new IOException( e.getMessage() ).initCause( e );
975            }
976            catch ( final TokenMgrError e )
977            {
978                throw (IOException) new IOException( e.getMessage() ).initCause( e );
979            }
980        }
981    
982        /**
983         * Transforms model objects of class files.
984         *
985         * @param marshaller The marshaller to use for transforming model objects.
986         * @param unmarshaller The unmarshaller to use for transforming model objects.
987         * @param classesDirectory The directory holding the class files.
988         * @param transformers The transformers to use for transforming model objects.
989         *
990         * @throws NullPointerException if {@code marshaller}, {@code unmarshaller}, {@code classesDirectory} or
991         * {@code transformers} is {@code null}.
992         * @throws IOException if transforming model objects fails.
993         *
994         * @see #transformModelObjects(org.jomc.model.Module, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List)
995         * @see org.jomc.model.ModelContext#createMarshaller()
996         * @see org.jomc.model.ModelContext#createUnmarshaller()
997         */
998        public void transformModelObjects( final Marshaller marshaller, final Unmarshaller unmarshaller,
999                                           final File classesDirectory, final List<Transformer> transformers )
1000            throws IOException
1001        {
1002            if ( marshaller == null )
1003            {
1004                throw new NullPointerException( "marshaller" );
1005            }
1006            if ( unmarshaller == null )
1007            {
1008                throw new NullPointerException( "unmarshaller" );
1009            }
1010            if ( transformers == null )
1011            {
1012                throw new NullPointerException( "transformers" );
1013            }
1014            if ( classesDirectory == null )
1015            {
1016                throw new NullPointerException( "classesDirectory" );
1017            }
1018    
1019            for ( Module m : this.getModules().getModule() )
1020            {
1021                this.transformModelObjects( m, marshaller, unmarshaller, classesDirectory, transformers );
1022            }
1023        }
1024    
1025        /**
1026         * Transforms model objects of class files.
1027         *
1028         * @param module The module to process.
1029         * @param marshaller The marshaller to use for transforming model objects.
1030         * @param unmarshaller The unmarshaller to use for transforming model objects.
1031         * @param classesDirectory The directory holding the class files.
1032         * @param transformers The transformers to use for transforming the model objects.
1033         *
1034         * @throws NullPointerException if {@code module}, {@code marshaller}, {@code unmarshaller},
1035         * {@code classesDirectory} or {@code transformers} is {@code null}.
1036         * @throws IOException if transforming model objects fails.
1037         *
1038         * @see #transformModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1039         * @see #transformModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1040         * @see org.jomc.model.ModelContext#createMarshaller()
1041         * @see org.jomc.model.ModelContext#createUnmarshaller()
1042         */
1043        public void transformModelObjects( final Module module, final Marshaller marshaller, final Unmarshaller unmarshaller,
1044                                           final File classesDirectory, final List<Transformer> transformers )
1045            throws IOException
1046        {
1047            if ( module == null )
1048            {
1049                throw new NullPointerException( "module" );
1050            }
1051            if ( marshaller == null )
1052            {
1053                throw new NullPointerException( "marshaller" );
1054            }
1055            if ( unmarshaller == null )
1056            {
1057                throw new NullPointerException( "unmarshaller" );
1058            }
1059            if ( transformers == null )
1060            {
1061                throw new NullPointerException( "transformers" );
1062            }
1063            if ( classesDirectory == null )
1064            {
1065                throw new NullPointerException( "classesDirectory" );
1066            }
1067    
1068            if ( module.getSpecifications() != null )
1069            {
1070                for ( Specification s : module.getSpecifications().getSpecification() )
1071                {
1072                    if ( s.isClassDeclaration() )
1073                    {
1074                        final String classLocation = s.getIdentifier().replace( '.', File.separatorChar ) + ".class";
1075                        final File classFile = new File( classesDirectory, classLocation );
1076    
1077                        if ( this.isLoggable( Level.INFO ) )
1078                        {
1079                            this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
1080                        }
1081    
1082                        final JavaClass javaClass = this.getJavaClass( classFile );
1083                        this.transformModelObjects( s, marshaller, unmarshaller, javaClass, transformers );
1084                        javaClass.dump( classFile );
1085                    }
1086                }
1087            }
1088    
1089            if ( module.getImplementations() != null )
1090            {
1091                for ( Implementation i : module.getImplementations().getImplementation() )
1092                {
1093                    if ( i.isClassDeclaration() )
1094                    {
1095                        final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
1096                        final File classFile = new File( classesDirectory, classLocation );
1097    
1098                        if ( this.isLoggable( Level.INFO ) )
1099                        {
1100                            this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
1101                        }
1102    
1103                        final JavaClass javaClass = this.getJavaClass( classFile );
1104                        this.transformModelObjects( i, marshaller, unmarshaller, javaClass, transformers );
1105                        javaClass.dump( classFile );
1106                    }
1107                }
1108            }
1109        }
1110    
1111        /**
1112         * Transforms model objects of class files.
1113         *
1114         * @param specification The specification to process.
1115         * @param marshaller The marshaller to use for transforming model objects.
1116         * @param unmarshaller The unmarshaller to use for transforming model objects.
1117         * @param classFile The class file to transform model objects of.
1118         * @param transformers The transformers to use for transforming the model objects.
1119         *
1120         * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
1121         * {@code classFile} or {@code transformers} is {@code null}.
1122         * @throws IOException if transforming model objects fails.
1123         *
1124         * @see org.jomc.model.ModelContext#createMarshaller()
1125         * @see org.jomc.model.ModelContext#createUnmarshaller()
1126         */
1127        public void transformModelObjects( final Specification specification, final Marshaller marshaller,
1128                                           final Unmarshaller unmarshaller, final JavaClass classFile,
1129                                           final List<Transformer> transformers ) throws IOException
1130        {
1131            if ( specification == null )
1132            {
1133                throw new NullPointerException( "specification" );
1134            }
1135            if ( marshaller == null )
1136            {
1137                throw new NullPointerException( "marshaller" );
1138            }
1139            if ( unmarshaller == null )
1140            {
1141                throw new NullPointerException( "unmarshaller" );
1142            }
1143            if ( classFile == null )
1144            {
1145                throw new NullPointerException( "classFile" );
1146            }
1147            if ( transformers == null )
1148            {
1149                throw new NullPointerException( "transformers" );
1150            }
1151    
1152            try
1153            {
1154                Specification decodedSpecification = null;
1155                final ObjectFactory objectFactory = new ObjectFactory();
1156                final byte[] bytes = this.getClassfileAttribute( classFile, Specification.class.getName() );
1157                if ( bytes != null )
1158                {
1159                    decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
1160                }
1161    
1162                if ( decodedSpecification != null )
1163                {
1164                    for ( Transformer transformer : transformers )
1165                    {
1166                        final JAXBSource source =
1167                            new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );
1168    
1169                        final JAXBResult result = new JAXBResult( unmarshaller );
1170                        transformer.transform( source, result );
1171                        decodedSpecification = ( (JAXBElement<Specification>) result.getResult() ).getValue();
1172                    }
1173    
1174                    this.setClassfileAttribute( classFile, Specification.class.getName(), this.encodeModelObject(
1175                        marshaller, objectFactory.createSpecification( decodedSpecification ) ) );
1176    
1177                }
1178            }
1179            catch ( final JAXBException e )
1180            {
1181                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1182            }
1183            catch ( final TransformerException e )
1184            {
1185                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1186            }
1187        }
1188    
1189        /**
1190         * Transforms model objects of class files.
1191         *
1192         * @param implementation The implementation to process.
1193         * @param marshaller The marshaller to use for transforming model objects.
1194         * @param unmarshaller The unmarshaller to use for transforming model objects.
1195         * @param classFile The class file to transform model objects of.
1196         * @param transformers The transformers to use for transforming the model objects.
1197         *
1198         * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
1199         * {@code classFile} or {@code transformers} is {@code null}.
1200         * @throws IOException if transforming model objects fails.
1201         *
1202         * @see org.jomc.model.ModelContext#createMarshaller()
1203         * @see org.jomc.model.ModelContext#createUnmarshaller()
1204         */
1205        public void transformModelObjects( final Implementation implementation, final Marshaller marshaller,
1206                                           final Unmarshaller unmarshaller, final JavaClass classFile,
1207                                           final List<Transformer> transformers ) throws IOException
1208        {
1209            if ( implementation == null )
1210            {
1211                throw new NullPointerException( "implementation" );
1212            }
1213            if ( marshaller == null )
1214            {
1215                throw new NullPointerException( "marshaller" );
1216            }
1217            if ( unmarshaller == null )
1218            {
1219                throw new NullPointerException( "unmarshaller" );
1220            }
1221            if ( classFile == null )
1222            {
1223                throw new NullPointerException( "classFile" );
1224            }
1225            if ( transformers == null )
1226            {
1227                throw new NullPointerException( "transformers" );
1228            }
1229    
1230            try
1231            {
1232                Dependencies decodedDependencies = null;
1233                byte[] bytes = this.getClassfileAttribute( classFile, Dependencies.class.getName() );
1234                if ( bytes != null )
1235                {
1236                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
1237                }
1238    
1239                Messages decodedMessages = null;
1240                bytes = this.getClassfileAttribute( classFile, Messages.class.getName() );
1241                if ( bytes != null )
1242                {
1243                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
1244                }
1245    
1246                Properties decodedProperties = null;
1247                bytes = this.getClassfileAttribute( classFile, Properties.class.getName() );
1248                if ( bytes != null )
1249                {
1250                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
1251                }
1252    
1253                Specifications decodedSpecifications = null;
1254                bytes = this.getClassfileAttribute( classFile, Specifications.class.getName() );
1255                if ( bytes != null )
1256                {
1257                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
1258                }
1259    
1260                final ObjectFactory of = new ObjectFactory();
1261                for ( Transformer transformer : transformers )
1262                {
1263                    if ( decodedDependencies != null )
1264                    {
1265                        final JAXBSource source = new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );
1266                        final JAXBResult result = new JAXBResult( unmarshaller );
1267                        transformer.transform( source, result );
1268                        decodedDependencies = ( (JAXBElement<Dependencies>) result.getResult() ).getValue();
1269                    }
1270    
1271                    if ( decodedMessages != null )
1272                    {
1273                        final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
1274                        final JAXBResult result = new JAXBResult( unmarshaller );
1275                        transformer.transform( source, result );
1276                        decodedMessages = ( (JAXBElement<Messages>) result.getResult() ).getValue();
1277                    }
1278    
1279                    if ( decodedProperties != null )
1280                    {
1281                        final JAXBSource source = new JAXBSource( marshaller, of.createProperties( decodedProperties ) );
1282                        final JAXBResult result = new JAXBResult( unmarshaller );
1283                        transformer.transform( source, result );
1284                        decodedProperties = ( (JAXBElement<Properties>) result.getResult() ).getValue();
1285                    }
1286    
1287                    if ( decodedSpecifications != null )
1288                    {
1289                        final JAXBSource source =
1290                            new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );
1291    
1292                        final JAXBResult result = new JAXBResult( unmarshaller );
1293                        transformer.transform( source, result );
1294                        decodedSpecifications = ( (JAXBElement<Specifications>) result.getResult() ).getValue();
1295                    }
1296                }
1297    
1298                if ( decodedDependencies != null )
1299                {
1300                    this.setClassfileAttribute( classFile, Dependencies.class.getName(), this.encodeModelObject(
1301                        marshaller, of.createDependencies( decodedDependencies ) ) );
1302    
1303                }
1304    
1305                if ( decodedMessages != null )
1306                {
1307                    this.setClassfileAttribute( classFile, Messages.class.getName(), this.encodeModelObject(
1308                        marshaller, of.createMessages( decodedMessages ) ) );
1309    
1310                }
1311    
1312                if ( decodedProperties != null )
1313                {
1314                    this.setClassfileAttribute( classFile, Properties.class.getName(), this.encodeModelObject(
1315                        marshaller, of.createProperties( decodedProperties ) ) );
1316    
1317                }
1318    
1319                if ( decodedSpecifications != null )
1320                {
1321                    this.setClassfileAttribute( classFile, Specifications.class.getName(), this.encodeModelObject(
1322                        marshaller, of.createSpecifications( decodedSpecifications ) ) );
1323    
1324                }
1325            }
1326            catch ( final JAXBException e )
1327            {
1328                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1329            }
1330            catch ( final TransformerException e )
1331            {
1332                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1333            }
1334        }
1335    
1336        /**
1337         * Parses a class file.
1338         *
1339         * @param classFile The class file to parse.
1340         *
1341         * @return The parsed class file.
1342         *
1343         * @throws NullPointerException if {@code classFile} is {@code null}.
1344         * @throws IOException if parsing {@code classFile} fails.
1345         *
1346         * @see JavaClass
1347         */
1348        public JavaClass getJavaClass( final File classFile ) throws IOException
1349        {
1350            if ( classFile == null )
1351            {
1352                throw new NullPointerException( "classFile" );
1353            }
1354    
1355            return this.getJavaClass( classFile.toURI().toURL(), classFile.getName() );
1356        }
1357    
1358        /**
1359         * Parses a class file.
1360         *
1361         * @param url The URL of the class file to parse.
1362         * @param className The name of the class at {@code url}.
1363         *
1364         * @return The parsed class file.
1365         *
1366         * @throws NullPointerException if {@code url} or {@code className} is {@code null}.
1367         * @throws IOException if parsing fails.
1368         *
1369         * @see JavaClass
1370         */
1371        public JavaClass getJavaClass( final URL url, final String className ) throws IOException
1372        {
1373            if ( url == null )
1374            {
1375                throw new NullPointerException( "url" );
1376            }
1377            if ( className == null )
1378            {
1379                throw new NullPointerException( "className" );
1380            }
1381    
1382            return this.getJavaClass( url.openStream(), className );
1383        }
1384    
1385        /**
1386         * Parses a class file.
1387         *
1388         * @param stream The stream to read the class file from.
1389         * @param className The name of the class to read from {@code stream}.
1390         *
1391         * @return The parsed class file.
1392         *
1393         * @throws NullPointerException if {@code stream} or {@code className} is {@code null}.
1394         * @throws IOException if parsing fails.
1395         *
1396         * @see JavaClass
1397         */
1398        public JavaClass getJavaClass( final InputStream stream, final String className ) throws IOException
1399        {
1400            if ( stream == null )
1401            {
1402                throw new NullPointerException( "stream" );
1403            }
1404            if ( className == null )
1405            {
1406                throw new NullPointerException( "className" );
1407            }
1408    
1409            final ClassParser parser = new ClassParser( stream, className );
1410            final JavaClass clazz = parser.parse();
1411            stream.close();
1412            return clazz;
1413        }
1414    
1415        /**
1416         * Gets an attribute from a java class.
1417         *
1418         * @param clazz The java class to get an attribute from.
1419         * @param attributeName The name of the attribute to get.
1420         *
1421         * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null} if no such attribute
1422         * exists.
1423         *
1424         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1425         * @throws IOException if getting the attribute fails.
1426         *
1427         * @see JavaClass#getAttributes()
1428         */
1429        public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
1430        {
1431            if ( clazz == null )
1432            {
1433                throw new NullPointerException( "clazz" );
1434            }
1435            if ( attributeName == null )
1436            {
1437                throw new NullPointerException( "attributeName" );
1438            }
1439    
1440            final Attribute[] attributes = clazz.getAttributes();
1441    
1442            for ( int i = attributes.length - 1; i >= 0; i-- )
1443            {
1444                final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1445    
1446                if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1447                {
1448                    final Unknown unknown = (Unknown) attributes[i];
1449                    return unknown.getBytes();
1450                }
1451            }
1452    
1453            return null;
1454        }
1455    
1456        /**
1457         * Adds or updates an attribute in a java class.
1458         *
1459         * @param clazz The class to update.
1460         * @param attributeName The name of the attribute to update.
1461         * @param data The new data of the attribute to update the {@code classFile} with.
1462         *
1463         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1464         * @throws IOException if updating the class file fails.
1465         *
1466         * @see JavaClass#getAttributes()
1467         */
1468        public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
1469            throws IOException
1470        {
1471            if ( clazz == null )
1472            {
1473                throw new NullPointerException( "clazz" );
1474            }
1475            if ( attributeName == null )
1476            {
1477                throw new NullPointerException( "attributeName" );
1478            }
1479    
1480            /*
1481            The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
1482    
1483            A Java virtual machine implementation is required to silently ignore any
1484            or all attributes in the attributes table of a ClassFile structure that
1485            it does not recognize. Attributes not defined in this specification are
1486            not allowed to affect the semantics of the class file, but only to
1487            provide additional descriptive information (ยง4.7.1).
1488             */
1489            Attribute[] attributes = clazz.getAttributes();
1490    
1491            int attributeIndex = -1;
1492            int nameIndex = -1;
1493    
1494            for ( int i = attributes.length - 1; i >= 0; i-- )
1495            {
1496                final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1497    
1498                if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1499                {
1500                    attributeIndex = i;
1501                    nameIndex = attributes[i].getNameIndex();
1502                }
1503            }
1504    
1505            if ( nameIndex == -1 )
1506            {
1507                final Constant[] pool = clazz.getConstantPool().getConstantPool();
1508                final Constant[] tmp = new Constant[ pool.length + 1 ];
1509                System.arraycopy( pool, 0, tmp, 0, pool.length );
1510                tmp[pool.length] = new ConstantUtf8( attributeName );
1511                nameIndex = pool.length;
1512                clazz.setConstantPool( new ConstantPool( tmp ) );
1513            }
1514    
1515            final Unknown unknown = new Unknown( nameIndex, data.length, data, clazz.getConstantPool() );
1516    
1517            if ( attributeIndex == -1 )
1518            {
1519                final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
1520                System.arraycopy( attributes, 0, tmp, 0, attributes.length );
1521                tmp[attributes.length] = unknown;
1522                attributes = tmp;
1523            }
1524            else
1525            {
1526                attributes[attributeIndex] = unknown;
1527            }
1528    
1529            clazz.setAttributes( attributes );
1530        }
1531    
1532        /**
1533         * Encodes a model object to a byte array.
1534         *
1535         * @param marshaller The marshaller to use for encoding the object.
1536         * @param modelObject The model object to encode.
1537         *
1538         * @return GZIP compressed XML document for {@code modelObject}.
1539         *
1540         * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
1541         * @throws IOException if encoding {@code modelObject} fails.
1542         */
1543        public byte[] encodeModelObject( final Marshaller marshaller, final JAXBElement<? extends ModelObject> modelObject )
1544            throws IOException
1545        {
1546            if ( marshaller == null )
1547            {
1548                throw new NullPointerException( "marshaller" );
1549            }
1550            if ( modelObject == null )
1551            {
1552                throw new NullPointerException( "modelObject" );
1553            }
1554    
1555            try
1556            {
1557                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1558                final GZIPOutputStream out = new GZIPOutputStream( baos );
1559                marshaller.marshal( modelObject, out );
1560                out.close();
1561                return baos.toByteArray();
1562            }
1563            catch ( final JAXBException e )
1564            {
1565                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1566            }
1567        }
1568    
1569        /**
1570         * Decodes a model object from a byte array.
1571         *
1572         * @param unmarshaller The unmarshaller to use for decoding the object.
1573         * @param bytes The encoded model object to decode.
1574         * @param type The type of the encoded model object.
1575         * @param <T> The type of the decoded model object.
1576         *
1577         * @return Model object decoded from {@code bytes}.
1578         *
1579         * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
1580         * @throws IOException if decoding {@code bytes} fails.
1581         */
1582        public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
1583                                                            final Class<T> type ) throws IOException
1584        {
1585            if ( unmarshaller == null )
1586            {
1587                throw new NullPointerException( "unmarshaller" );
1588            }
1589            if ( bytes == null )
1590            {
1591                throw new NullPointerException( "bytes" );
1592            }
1593            if ( type == null )
1594            {
1595                throw new NullPointerException( "type" );
1596            }
1597    
1598            try
1599            {
1600                final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
1601                final GZIPInputStream in = new GZIPInputStream( bais );
1602                final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
1603                in.close();
1604                return element.getValue();
1605            }
1606            catch ( final JAXBException e )
1607            {
1608                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1609            }
1610        }
1611    
1612        private static String getMessage( final String key, final Object... arguments )
1613        {
1614            if ( key == null )
1615            {
1616                throw new NullPointerException( "key" );
1617            }
1618    
1619            return MessageFormat.format( ResourceBundle.getBundle( ClassFileProcessor.class.getName().replace( '.', '/' ) ).
1620                getString( key ), arguments );
1621    
1622        }
1623    
1624    }