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