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