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