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 898 2009-11-02 06:51:58Z 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 898 2009-11-02 06:51:58Z 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().equals( specification.getClazz() ) )
679                {
680                    report.getDetails().add( new ModelObjectValidationReport.Detail(
681                        "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
682                        this.getMessage( "illegalSpecificationClass", new Object[]
683                        {
684                            decoded.getIdentifier(), specification.getClazz(), decoded.getClazz()
685                        } ) ) );
686    
687                }
688            }
689            else if ( this.isLoggable( Level.WARNING ) )
690            {
691                this.log( Level.WARNING, this.getMessage( "cannotValidateSpecification", new Object[]
692                    {
693                        specification.getIdentifier(), Specification.class.getName()
694                    } ), null );
695    
696            }
697    
698            return report;
699        }
700    
701        /**
702         * Validates compiled Java classes against a given implementation of the modules of the instance.
703         *
704         * @param implementation The implementation to process.
705         * @param unmarshaller The unmarshaller to use for validating classes.
706         * @param javaClass The class to validate.
707         *
708         * @return The report of the validation.
709         *
710         * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code javaClass} is
711         * {@code null}.
712         * @throws IOException if reading class files fails.
713         */
714        public ModelObjectValidationReport validateClasses( final Implementation implementation,
715                                                            final Unmarshaller unmarshaller, final JavaClass javaClass )
716            throws IOException
717        {
718            if ( implementation == null )
719            {
720                throw new NullPointerException( "implementation" );
721            }
722            if ( unmarshaller == null )
723            {
724                throw new NullPointerException( "unmarshaller" );
725            }
726            if ( javaClass == null )
727            {
728                throw new NullPointerException( "javaClass" );
729            }
730    
731            if ( this.isLoggable( Level.INFO ) )
732            {
733                this.log( Level.INFO, this.getMessage( "validatingImplementation", new Object[]
734                    {
735                        implementation.getIdentifier()
736                    } ), null );
737    
738            }
739    
740            final ModelObjectValidationReport report = new ModelObjectValidationReport(
741                new ObjectFactory().createImplementation( implementation ) );
742    
743            try
744            {
745                Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
746                if ( dependencies == null )
747                {
748                    dependencies = new Dependencies();
749                }
750    
751                Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
752                if ( properties == null )
753                {
754                    properties = new Properties();
755                }
756    
757                Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
758                if ( messages == null )
759                {
760                    messages = new Messages();
761                }
762    
763                Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
764                if ( specifications == null )
765                {
766                    specifications = new Specifications();
767                }
768    
769                Dependencies decodedDependencies = null;
770                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
771                if ( bytes != null )
772                {
773                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
774                }
775    
776                Properties decodedProperties = null;
777                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
778                if ( bytes != null )
779                {
780                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
781                }
782    
783                Messages decodedMessages = null;
784                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
785                if ( bytes != null )
786                {
787                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
788                }
789    
790                Specifications decodedSpecifications = null;
791                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
792                if ( bytes != null )
793                {
794                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
795                }
796    
797                if ( decodedDependencies != null )
798                {
799                    for ( Dependency decodedDependency : decodedDependencies.getDependency() )
800                    {
801                        final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
802    
803                        if ( dependency == null )
804                        {
805                            report.getDetails().add( new ModelObjectValidationReport.Detail(
806                                "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
807                                this.getMessage( "missingDependency", new Object[]
808                                {
809                                    implementation.getIdentifier(), decodedDependency.getName()
810                                } ) ) );
811    
812                        }
813    
814                        final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );
815    
816                        if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null &&
817                             VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
818                        {
819                            final Module moduleOfSpecification =
820                                this.getModules().getModuleOfSpecification( s.getIdentifier() );
821    
822                            final Module moduleOfImplementation =
823                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
824    
825                            report.getDetails().add( new ModelObjectValidationReport.Detail(
826                                "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
827                                this.getMessage( "incompatibleDependency", new Object[]
828                                {
829                                    javaClass.getClassName(), moduleOfImplementation == null
830                                                              ? "<>" : moduleOfImplementation.getName(),
831                                    s.getIdentifier(), moduleOfSpecification == null
832                                                       ? "<>" : moduleOfSpecification.getName(),
833                                    decodedDependency.getVersion(), s.getVersion()
834                                } ) ) );
835    
836                        }
837                    }
838                }
839                else if ( this.isLoggable( Level.WARNING ) )
840                {
841                    this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
842                        {
843                            implementation.getIdentifier(), Dependencies.class.getName()
844                        } ), null );
845    
846                }
847    
848                if ( decodedProperties != null )
849                {
850                    for ( Property decodedProperty : decodedProperties.getProperty() )
851                    {
852                        final Property property = properties.getProperty( decodedProperty.getName() );
853    
854                        if ( property == null )
855                        {
856                            report.getDetails().add( new ModelObjectValidationReport.Detail(
857                                "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE,
858                                this.getMessage( "missingProperty", new Object[]
859                                {
860                                    implementation.getIdentifier(), decodedProperty.getName()
861                                } ) ) );
862    
863                        }
864                        else
865                        {
866                            if ( decodedProperty.getType() == null
867                                 ? property.getType() != null
868                                 : !decodedProperty.getType().equals( property.getType() ) )
869                            {
870                                report.getDetails().add( new ModelObjectValidationReport.Detail(
871                                    "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE,
872                                    this.getMessage( "illegalPropertyType", new Object[]
873                                    {
874                                        implementation.getIdentifier(), decodedProperty.getName(),
875                                        property.getType() == null ? "default" : property.getType(),
876                                        decodedProperty.getType() == null ? "default" : decodedProperty.getType()
877                                    } ) ) );
878    
879                            }
880                        }
881                    }
882                }
883                else if ( this.isLoggable( Level.WARNING ) )
884                {
885                    this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
886                        {
887                            implementation.getIdentifier(), Properties.class.getName()
888                        } ), null );
889    
890                }
891    
892                if ( decodedMessages != null )
893                {
894                    for ( Message decodedMessage : decodedMessages.getMessage() )
895                    {
896                        final Message message = messages.getMessage( decodedMessage.getName() );
897    
898                        if ( message == null )
899                        {
900                            report.getDetails().add( new ModelObjectValidationReport.Detail(
901                                "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE,
902                                this.getMessage( "missingMessage", new Object[]
903                                {
904                                    implementation.getIdentifier(), decodedMessage.getName()
905                                } ) ) );
906    
907                        }
908                    }
909                }
910                else if ( this.isLoggable( Level.WARNING ) )
911                {
912                    this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
913                        {
914                            implementation.getIdentifier(), Messages.class.getName()
915                        } ), null );
916    
917                }
918    
919                if ( decodedSpecifications != null )
920                {
921                    for ( Specification decodedSpecification : decodedSpecifications.getSpecification() )
922                    {
923                        final Specification specification =
924                            this.getModules().getSpecification( decodedSpecification.getIdentifier() );
925    
926                        if ( specification == null )
927                        {
928                            report.getDetails().add( new ModelObjectValidationReport.Detail(
929                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
930                                this.getMessage( "missingSpecification", new Object[]
931                                {
932                                    implementation.getIdentifier(), decodedSpecification.getIdentifier()
933                                } ) ) );
934    
935                        }
936                        else
937                        {
938                            if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
939                            {
940                                report.getDetails().add( new ModelObjectValidationReport.Detail(
941                                    "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE,
942                                    this.getMessage( "illegalMultiplicity", new Object[]
943                                    {
944                                        specification.getIdentifier(), specification.getMultiplicity().value(),
945                                        decodedSpecification.getMultiplicity().value()
946                                    } ) ) );
947    
948                            }
949    
950                            if ( decodedSpecification.getScope() == null
951                                 ? specification.getScope() != null
952                                 : !decodedSpecification.getScope().equals( specification.getScope() ) )
953                            {
954                                report.getDetails().add( new ModelObjectValidationReport.Detail(
955                                    "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE,
956                                    this.getMessage( "illegalScope", new Object[]
957                                    {
958                                        decodedSpecification.getIdentifier(),
959                                        specification.getScope() == null
960                                        ? "Multiton" : specification.getScope(),
961                                        decodedSpecification.getScope() == null
962                                        ? "Multiton" : decodedSpecification.getScope()
963                                    } ) ) );
964    
965                            }
966    
967                            if ( !decodedSpecification.getClazz().equals( specification.getClazz() ) )
968                            {
969                                report.getDetails().add( new ModelObjectValidationReport.Detail(
970                                    "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
971                                    this.getMessage( "illegalSpecificationClass", new Object[]
972                                    {
973                                        decodedSpecification.getIdentifier(), specification.getClazz(),
974                                        decodedSpecification.getClazz()
975                                    } ) ) );
976    
977                            }
978                        }
979                    }
980    
981                    for ( SpecificationReference decodedReference : decodedSpecifications.getReference() )
982                    {
983                        final Specification specification =
984                            specifications.getSpecification( decodedReference.getIdentifier() );
985    
986                        if ( specification == null )
987                        {
988                            report.getDetails().add( new ModelObjectValidationReport.Detail(
989                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
990                                this.getMessage( "missingSpecification", new Object[]
991                                {
992                                    implementation.getIdentifier(), decodedReference.getIdentifier()
993                                } ) ) );
994    
995                        }
996                        else if ( decodedReference.getVersion() != null && specification.getVersion() != null &&
997                                  VersionParser.compare( decodedReference.getVersion(), specification.getVersion() ) != 0 )
998                        {
999                            final Module moduleOfSpecification =
1000                                this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );
1001    
1002                            final Module moduleOfImplementation =
1003                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
1004    
1005                            report.getDetails().add( new ModelObjectValidationReport.Detail(
1006                                "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE,
1007                                this.getMessage( "incompatibleImplementation", new Object[]
1008                                {
1009                                    javaClass.getClassName(), moduleOfImplementation == null
1010                                                              ? "<>" : moduleOfImplementation.getName(),
1011                                    specification.getIdentifier(), moduleOfSpecification == null
1012                                                                   ? "<>" : moduleOfSpecification.getName(),
1013                                    decodedReference.getVersion(), specification.getVersion()
1014                                } ) ) );
1015    
1016                        }
1017                    }
1018                }
1019                else if ( this.isLoggable( Level.WARNING ) )
1020                {
1021                    this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
1022                        {
1023                            implementation.getIdentifier(), Specifications.class.getName()
1024                        } ), null );
1025    
1026                }
1027    
1028                return report;
1029            }
1030            catch ( final ParseException e )
1031            {
1032                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1033            }
1034            catch ( final TokenMgrError e )
1035            {
1036                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1037            }
1038        }
1039    
1040        /**
1041         * Transforms committed meta-data of compiled Java classes of the modules of the instance.
1042         *
1043         * @param marshaller The marshaller to use for transforming classes.
1044         * @param unmarshaller The unmarshaller to use for transforming classes.
1045         * @param classesDirectory The directory holding the compiled class files.
1046         * @param transformers The transformers to use for transforming the classes.
1047         *
1048         * @throws NullPointerException if {@code marshaller}, {@code unmarshaller}, {@code classesDirectory} or
1049         * {@code transformers} is {@code null}.
1050         * @throws IOException if accessing class files fails.
1051         * @throws TransformerException if transforming class files fails.
1052         *
1053         * @see #transformClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List)
1054         */
1055        public void transformClasses( final Marshaller marshaller, final Unmarshaller unmarshaller,
1056                                      final File classesDirectory, final List<Transformer> transformers )
1057            throws IOException, TransformerException
1058        {
1059            if ( marshaller == null )
1060            {
1061                throw new NullPointerException( "marshaller" );
1062            }
1063            if ( unmarshaller == null )
1064            {
1065                throw new NullPointerException( "unmarshaller" );
1066            }
1067            if ( transformers == null )
1068            {
1069                throw new NullPointerException( "transformers" );
1070            }
1071            if ( classesDirectory == null )
1072            {
1073                throw new NullPointerException( "classesDirectory" );
1074            }
1075    
1076            for ( Module m : this.getModules().getModule() )
1077            {
1078                this.transformClasses( m, marshaller, unmarshaller, classesDirectory, transformers );
1079            }
1080        }
1081    
1082        /**
1083         * Transforms committed meta-data of compiled Java classes of a given module of the modules of the instance.
1084         *
1085         * @param module The module to process.
1086         * @param marshaller The marshaller to use for transforming classes.
1087         * @param unmarshaller The unmarshaller to use for transforming classes.
1088         * @param classesDirectory The directory holding the compiled class files.
1089         * @param transformers The transformers to use for transforming the classes.
1090         *
1091         * @throws NullPointerException if {@code module}, {@code marshaller}, {@code unmarshaller},
1092         * {@code classesDirectory} or {@code transformers} is {@code null}.
1093         * @throws IOException if accessing class files fails.
1094         * @throws TransformerException if transforming class files fails.
1095         *
1096         * @see #transformClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1097         * @see #transformClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1098         */
1099        public void transformClasses( final Module module, final Marshaller marshaller, final Unmarshaller unmarshaller,
1100                                      final File classesDirectory, final List<Transformer> transformers )
1101            throws IOException, TransformerException
1102        {
1103            if ( module == null )
1104            {
1105                throw new NullPointerException( "module" );
1106            }
1107            if ( marshaller == null )
1108            {
1109                throw new NullPointerException( "marshaller" );
1110            }
1111            if ( unmarshaller == null )
1112            {
1113                throw new NullPointerException( "unmarshaller" );
1114            }
1115            if ( transformers == null )
1116            {
1117                throw new NullPointerException( "transformers" );
1118            }
1119            if ( classesDirectory == null )
1120            {
1121                throw new NullPointerException( "classesDirectory" );
1122            }
1123    
1124            if ( module.getSpecifications() != null )
1125            {
1126                for ( Specification s : module.getSpecifications().getSpecification() )
1127                {
1128                    if ( this.isJavaClassDeclaration( s ) )
1129                    {
1130                        final String classLocation = s.getIdentifier().replace( '.', File.separatorChar ) + ".class";
1131                        final File classFile = new File( classesDirectory, classLocation );
1132    
1133                        if ( this.isLoggable( Level.INFO ) )
1134                        {
1135                            this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1136                                {
1137                                    classFile.getAbsolutePath()
1138                                } ), null );
1139    
1140                        }
1141    
1142                        final JavaClass javaClass = this.getJavaClass( classFile );
1143                        this.transformClasses( s, marshaller, unmarshaller, javaClass, transformers );
1144                        javaClass.dump( classFile );
1145                    }
1146                }
1147            }
1148    
1149            if ( module.getImplementations() != null )
1150            {
1151                for ( Implementation i : module.getImplementations().getImplementation() )
1152                {
1153                    if ( this.isJavaClassDeclaration( i ) )
1154                    {
1155                        final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
1156                        final File classFile = new File( classesDirectory, classLocation );
1157    
1158                        if ( this.isLoggable( Level.INFO ) )
1159                        {
1160                            this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1161                                {
1162                                    classFile.getAbsolutePath()
1163                                } ), null );
1164    
1165                        }
1166    
1167                        final JavaClass javaClass = this.getJavaClass( classFile );
1168                        this.transformClasses( i, marshaller, unmarshaller, javaClass, transformers );
1169                        javaClass.dump( classFile );
1170                    }
1171                }
1172            }
1173        }
1174    
1175        /**
1176         * Transforms committed meta-data of compiled Java classes of a given specification of the modules of the instance.
1177         *
1178         * @param specification The specification to process.
1179         * @param marshaller The marshaller to use for transforming classes.
1180         * @param unmarshaller The unmarshaller to use for transforming classes.
1181         * @param javaClass The java class to process.
1182         * @param transformers The transformers to use for transforming the classes.
1183         *
1184         * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
1185         * {@code javaClass} or {@code transformers} is {@code null}.
1186         * @throws IOException if accessing class files fails.
1187         * @throws TransformerException if transforming class files fails.
1188         */
1189        public void transformClasses( final Specification specification, final Marshaller marshaller,
1190                                      final Unmarshaller unmarshaller, final JavaClass javaClass,
1191                                      final List<Transformer> transformers ) throws IOException, TransformerException
1192        {
1193            if ( specification == null )
1194            {
1195                throw new NullPointerException( "specification" );
1196            }
1197            if ( marshaller == null )
1198            {
1199                throw new NullPointerException( "marshaller" );
1200            }
1201            if ( unmarshaller == null )
1202            {
1203                throw new NullPointerException( "unmarshaller" );
1204            }
1205            if ( javaClass == null )
1206            {
1207                throw new NullPointerException( "javaClass" );
1208            }
1209            if ( transformers == null )
1210            {
1211                throw new NullPointerException( "transformers" );
1212            }
1213    
1214            try
1215            {
1216                Specification decodedSpecification = null;
1217                final ObjectFactory objectFactory = new ObjectFactory();
1218                final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
1219                if ( bytes != null )
1220                {
1221                    decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
1222                }
1223    
1224                if ( decodedSpecification != null )
1225                {
1226                    for ( Transformer transformer : transformers )
1227                    {
1228                        final JAXBSource source =
1229                            new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );
1230    
1231                        final JAXBResult result = new JAXBResult( unmarshaller );
1232                        transformer.transform( source, result );
1233                        decodedSpecification = ( (JAXBElement<Specification>) result.getResult() ).getValue();
1234                    }
1235    
1236                    this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
1237                        marshaller, objectFactory.createSpecification( decodedSpecification ) ) );
1238    
1239                }
1240            }
1241            catch ( final JAXBException e )
1242            {
1243                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1244            }
1245        }
1246    
1247        /**
1248         * Transforms committed meta-data of compiled Java classes of a given implementation of the modules of the instance.
1249         *
1250         * @param implementation The implementation to process.
1251         * @param marshaller The marshaller to use for transforming classes.
1252         * @param unmarshaller The unmarshaller to use for transforming classes.
1253         * @param javaClass The java class to process.
1254         * @param transformers The transformers to use for transforming the classes.
1255         *
1256         * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
1257         * {@code javaClass} or {@code transformers} is {@code null}.
1258         * @throws IOException if accessing class files fails.
1259         * @throws TransformerException if transforming class files fails.
1260         */
1261        public void transformClasses( final Implementation implementation, final Marshaller marshaller,
1262                                      final Unmarshaller unmarshaller, final JavaClass javaClass,
1263                                      final List<Transformer> transformers ) throws TransformerException, IOException
1264        {
1265            if ( implementation == null )
1266            {
1267                throw new NullPointerException( "implementation" );
1268            }
1269            if ( marshaller == null )
1270            {
1271                throw new NullPointerException( "marshaller" );
1272            }
1273            if ( unmarshaller == null )
1274            {
1275                throw new NullPointerException( "unmarshaller" );
1276            }
1277            if ( javaClass == null )
1278            {
1279                throw new NullPointerException( "javaClass" );
1280            }
1281            if ( transformers == null )
1282            {
1283                throw new NullPointerException( "transformers" );
1284            }
1285    
1286            try
1287            {
1288                Dependencies decodedDependencies = null;
1289                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
1290                if ( bytes != null )
1291                {
1292                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
1293                }
1294    
1295                Messages decodedMessages = null;
1296                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
1297                if ( bytes != null )
1298                {
1299                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
1300                }
1301    
1302                Properties decodedProperties = null;
1303                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
1304                if ( bytes != null )
1305                {
1306                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
1307                }
1308    
1309                Specifications decodedSpecifications = null;
1310                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
1311                if ( bytes != null )
1312                {
1313                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
1314                }
1315    
1316                final ObjectFactory of = new ObjectFactory();
1317                for ( Transformer transformer : transformers )
1318                {
1319                    if ( decodedDependencies != null )
1320                    {
1321                        final JAXBSource source = new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );
1322                        final JAXBResult result = new JAXBResult( unmarshaller );
1323                        transformer.transform( source, result );
1324                        decodedDependencies = ( (JAXBElement<Dependencies>) result.getResult() ).getValue();
1325                    }
1326    
1327                    if ( decodedMessages != null )
1328                    {
1329                        final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
1330                        final JAXBResult result = new JAXBResult( unmarshaller );
1331                        transformer.transform( source, result );
1332                        decodedMessages = ( (JAXBElement<Messages>) result.getResult() ).getValue();
1333                    }
1334    
1335                    if ( decodedProperties != null )
1336                    {
1337                        final JAXBSource source = new JAXBSource( marshaller, of.createProperties( decodedProperties ) );
1338                        final JAXBResult result = new JAXBResult( unmarshaller );
1339                        transformer.transform( source, result );
1340                        decodedProperties = ( (JAXBElement<Properties>) result.getResult() ).getValue();
1341                    }
1342    
1343                    if ( decodedSpecifications != null )
1344                    {
1345                        final JAXBSource source =
1346                            new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );
1347    
1348                        final JAXBResult result = new JAXBResult( unmarshaller );
1349                        transformer.transform( source, result );
1350                        decodedSpecifications = ( (JAXBElement<Specifications>) result.getResult() ).getValue();
1351                    }
1352                }
1353    
1354                if ( decodedDependencies != null )
1355                {
1356                    this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
1357                        marshaller, of.createDependencies( decodedDependencies ) ) );
1358    
1359                }
1360    
1361                if ( decodedMessages != null )
1362                {
1363                    this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
1364                        marshaller, of.createMessages( decodedMessages ) ) );
1365    
1366                }
1367    
1368                if ( decodedProperties != null )
1369                {
1370                    this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
1371                        marshaller, of.createProperties( decodedProperties ) ) );
1372    
1373                }
1374    
1375                if ( decodedSpecifications != null )
1376                {
1377                    this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
1378                        marshaller, of.createSpecifications( decodedSpecifications ) ) );
1379    
1380                }
1381            }
1382            catch ( final JAXBException e )
1383            {
1384                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1385            }
1386        }
1387    
1388        /**
1389         * Parses a class file.
1390         *
1391         * @param classFile The class file to parse.
1392         *
1393         * @return The parsed class file.
1394         *
1395         * @throws NullPointerException if {@code classFile} is {@code null}.
1396         * @throws IOException if parsing {@code classFile} fails.
1397         *
1398         * @see JavaClass
1399         */
1400        public JavaClass getJavaClass( final File classFile ) throws IOException
1401        {
1402            if ( classFile == null )
1403            {
1404                throw new NullPointerException( "classFile" );
1405            }
1406    
1407            return this.getJavaClass( classFile.toURI().toURL(), classFile.getName() );
1408        }
1409    
1410        /**
1411         * Parses a class file.
1412         *
1413         * @param url The URL of the class file to parse.
1414         * @param className The name of the class at {@code url}.
1415         *
1416         * @return The parsed class file.
1417         *
1418         * @throws NullPointerException if {@code url} or {@code className} is {@code null}.
1419         * @throws IOException if parsing fails.
1420         *
1421         * @see JavaClass
1422         */
1423        public JavaClass getJavaClass( final URL url, final String className ) throws IOException
1424        {
1425            if ( url == null )
1426            {
1427                throw new NullPointerException( "url" );
1428            }
1429            if ( className == null )
1430            {
1431                throw new NullPointerException( "className" );
1432            }
1433    
1434            return this.getJavaClass( url.openStream(), className );
1435        }
1436    
1437        /**
1438         * Parses a class file.
1439         *
1440         * @param stream The stream to read the class file from.
1441         * @param className The name of the class to read from {@code stream}.
1442         *
1443         * @return The parsed class file.
1444         *
1445         * @throws NullPointerException if {@code stream} or {@code className} is {@code null}.
1446         * @throws IOException if parsing fails.
1447         *
1448         * @see JavaClass
1449         */
1450        public JavaClass getJavaClass( final InputStream stream, final String className ) throws IOException
1451        {
1452            if ( stream == null )
1453            {
1454                throw new NullPointerException( "stream" );
1455            }
1456            if ( className == null )
1457            {
1458                throw new NullPointerException( "className" );
1459            }
1460    
1461            final ClassParser parser = new ClassParser( stream, className );
1462            final JavaClass clazz = parser.parse();
1463            stream.close();
1464            return clazz;
1465        }
1466    
1467        /**
1468         * Gets an attribute from a java class.
1469         *
1470         * @param clazz The java class to get an attribute from.
1471         * @param attributeName The name of the attribute to get.
1472         *
1473         * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null} if no such attribute
1474         * exists.
1475         *
1476         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1477         * @throws IOException if getting the attribute fails.
1478         *
1479         * @see JavaClass#getAttributes()
1480         */
1481        public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
1482        {
1483            if ( clazz == null )
1484            {
1485                throw new NullPointerException( "clazz" );
1486            }
1487            if ( attributeName == null )
1488            {
1489                throw new NullPointerException( "attributeName" );
1490            }
1491    
1492            final Attribute[] attributes = clazz.getAttributes();
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                    final Unknown unknown = (Unknown) attributes[i];
1501                    return unknown.getBytes();
1502                }
1503            }
1504    
1505            return null;
1506        }
1507    
1508        /**
1509         * Adds or updates an attribute in a java class.
1510         *
1511         * @param clazz The class to update.
1512         * @param attributeName The name of the attribute to update.
1513         * @param data The new data of the attribute to update the {@code classFile} with.
1514         *
1515         * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1516         * @throws IOException if updating the class file fails.
1517         *
1518         * @see JavaClass#getAttributes()
1519         */
1520        public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
1521            throws IOException
1522        {
1523            if ( clazz == null )
1524            {
1525                throw new NullPointerException( "clazz" );
1526            }
1527            if ( attributeName == null )
1528            {
1529                throw new NullPointerException( "attributeName" );
1530            }
1531    
1532            /*
1533            The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
1534    
1535            A Java virtual machine implementation is required to silently ignore any
1536            or all attributes in the attributes table of a ClassFile structure that
1537            it does not recognize. Attributes not defined in this specification are
1538            not allowed to affect the semantics of the class file, but only to
1539            provide additional descriptive information (ยง4.7.1).
1540             */
1541            Attribute[] attributes = clazz.getAttributes();
1542    
1543            int attributeIndex = -1;
1544            int nameIndex = -1;
1545    
1546            for ( int i = attributes.length - 1; i >= 0; i-- )
1547            {
1548                final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1549    
1550                if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1551                {
1552                    attributeIndex = i;
1553                    nameIndex = attributes[i].getNameIndex();
1554                }
1555            }
1556    
1557            if ( nameIndex == -1 )
1558            {
1559                final Constant[] pool = clazz.getConstantPool().getConstantPool();
1560                final Constant[] tmp = new Constant[ pool.length + 1 ];
1561                System.arraycopy( pool, 0, tmp, 0, pool.length );
1562                tmp[pool.length] = new ConstantUtf8( attributeName );
1563                nameIndex = pool.length;
1564                clazz.setConstantPool( new ConstantPool( tmp ) );
1565            }
1566    
1567            final Unknown unknown = new Unknown( nameIndex, data.length, data, clazz.getConstantPool() );
1568    
1569            if ( attributeIndex == -1 )
1570            {
1571                final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
1572                System.arraycopy( attributes, 0, tmp, 0, attributes.length );
1573                tmp[attributes.length] = unknown;
1574                attributes = tmp;
1575            }
1576            else
1577            {
1578                attributes[attributeIndex] = unknown;
1579            }
1580    
1581            clazz.setAttributes( attributes );
1582        }
1583    
1584        /**
1585         * Encodes a model object to a byte array.
1586         *
1587         * @param marshaller The marshaller to use for encoding the object.
1588         * @param modelObject The model object to encode.
1589         *
1590         * @return GZIP compressed XML document for {@code modelObject}.
1591         *
1592         * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
1593         * @throws IOException if encoding {@code modelObject} fails.
1594         */
1595        public byte[] encodeModelObject( final Marshaller marshaller,
1596                                         final JAXBElement<? extends ModelObject> modelObject ) throws IOException
1597        {
1598            if ( marshaller == null )
1599            {
1600                throw new NullPointerException( "marshaller" );
1601            }
1602            if ( modelObject == null )
1603            {
1604                throw new NullPointerException( "modelObject" );
1605            }
1606    
1607            try
1608            {
1609                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1610                final GZIPOutputStream out = new GZIPOutputStream( baos );
1611                marshaller.marshal( modelObject, out );
1612                out.close();
1613                return baos.toByteArray();
1614            }
1615            catch ( final JAXBException e )
1616            {
1617                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1618            }
1619        }
1620    
1621        /**
1622         * Decodes a model object from a byte array.
1623         *
1624         * @param unmarshaller The unmarshaller to use for decoding the object.
1625         * @param bytes The encoded model object to decode.
1626         * @param type The type of the encoded model object.
1627         * @param <T> The type of the decoded model object.
1628         *
1629         * @return Model object decoded from {@code bytes}.
1630         *
1631         * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
1632         * @throws IOException if decoding {@code bytes} fails.
1633         */
1634        public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
1635                                                            final Class<T> type ) throws IOException
1636        {
1637            if ( unmarshaller == null )
1638            {
1639                throw new NullPointerException( "unmarshaller" );
1640            }
1641            if ( bytes == null )
1642            {
1643                throw new NullPointerException( "bytes" );
1644            }
1645            if ( type == null )
1646            {
1647                throw new NullPointerException( "type" );
1648            }
1649    
1650            try
1651            {
1652                final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
1653                final GZIPInputStream in = new GZIPInputStream( bais );
1654                final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
1655                in.close();
1656                return element.getValue();
1657            }
1658            catch ( final JAXBException e )
1659            {
1660                throw (IOException) new IOException( e.getMessage() ).initCause( e );
1661            }
1662        }
1663    
1664        private String getMessage( final String key, final Object args )
1665        {
1666            final ResourceBundle b = ResourceBundle.getBundle( JavaClasses.class.getName().replace( '.', '/' ) );
1667            final MessageFormat f = new MessageFormat( b.getString( key ) );
1668            return f.format( args );
1669        }
1670    
1671    }