ClassFileProcessor.java

/*
 *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     o Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     o Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *   $JOMC: ClassFileProcessor.java 5043 2015-05-27 07:03:39Z schulte $
 *
 */
package org.jomc.tools;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.text.MessageFormat;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBResult;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.validation.Schema;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Unknown;
import org.jomc.model.Dependencies;
import org.jomc.model.Dependency;
import org.jomc.model.Implementation;
import org.jomc.model.Implementations;
import org.jomc.model.Message;
import org.jomc.model.Messages;
import org.jomc.model.ModelObject;
import org.jomc.model.ModelObjectException;
import org.jomc.model.Module;
import org.jomc.model.ObjectFactory;
import org.jomc.model.Properties;
import org.jomc.model.Property;
import org.jomc.model.Specification;
import org.jomc.model.SpecificationReference;
import org.jomc.model.Specifications;
import org.jomc.modlet.ModelContext;
import org.jomc.modlet.ModelException;
import org.jomc.modlet.ModelValidationReport;
import org.jomc.util.ParseException;
import org.jomc.util.TokenMgrError;
import org.jomc.util.VersionParser;

/**
 * Processes class files.
 *
 * <p>
 * <b>Use Cases:</b><br/><ul>
 * <li>{@link #commitModelObjects(org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #commitModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #commitModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #commitModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #validateModelObjects(org.jomc.modlet.ModelContext) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext) }</li>
 * <li>{@link #validateModelObjects(org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File) }</li>
 * <li>{@link #transformModelObjects(org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
 * <li>{@link #transformModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
 * <li>{@link #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
 * <li>{@link #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List) }</li>
 * </ul></p>
 *
 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
 * @version $JOMC: ClassFileProcessor.java 5043 2015-05-27 07:03:39Z schulte $
 *
 * @see #getModules()
 */
public class ClassFileProcessor extends JomcTool
{

    /**
     * Empty byte array.
     */
    private static final byte[] NO_BYTES =
    {
    };

    /**
     * Creates a new {@code ClassFileProcessor} instance.
     */
    public ClassFileProcessor()
    {
        super();
    }

    /**
     * Creates a new {@code ClassFileProcessor} instance taking a {@code ClassFileProcessor} instance to initialize the
     * instance with.
     *
     * @param tool The instance to initialize the new instance with.
     *
     * @throws NullPointerException if {@code tool} is {@code null}.
     * @throws IOException if copying {@code tool} fails.
     */
    public ClassFileProcessor( final ClassFileProcessor tool ) throws IOException
    {
        super( tool );
    }

    /**
     * Commits model objects of the modules of the instance to class files.
     *
     * @param context The model context to use for committing the model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @throws NullPointerException if {@code context} or {@code classesDirectory} is {@code null}.
     * @throws IOException if committing model objects fails.
     *
     * @see #commitModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File)
     */
    public final void commitModelObjects( final ModelContext context, final File classesDirectory ) throws IOException
    {
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            if ( this.getModules() != null )
            {
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );

                this.commitModelObjects( this.getModules().getSpecifications(), this.getModules().getImplementations(),
                                         m, classesDirectory );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Commits model objects of a given module of the modules of the instance to class files.
     *
     * @param module The module to process.
     * @param context The model context to use for committing the model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @throws NullPointerException if {@code module}, {@code context} or {@code classesDirectory} is {@code null}.
     * @throws IOException if committing model objects fails.
     *
     * @see #commitModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File)
     * @see #commitModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File)
     */
    public final void commitModelObjects( final Module module, final ModelContext context, final File classesDirectory )
        throws IOException
    {
        if ( module == null )
        {
            throw new NullPointerException( "module" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
            {
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );

                this.commitModelObjects( module.getSpecifications(), module.getImplementations(), m, classesDirectory );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Commits model objects of a given specification of the modules of the instance to class files.
     *
     * @param specification The specification to process.
     * @param context The model context to use for committing the model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @throws NullPointerException if {@code specification}, {@code context} or {@code classesDirectory} is
     * {@code null}.
     * @throws IOException if committing model objects fails.
     *
     * @see #commitModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final void commitModelObjects( final Specification specification, final ModelContext context,
                                          final File classesDirectory ) throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getSpecification( specification.getIdentifier() ) != null )
            {
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );

                this.commitModelObjects( specification, m, classesDirectory );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Commits model objects of a given implementation of the modules of the instance to class files.
     *
     * @param implementation The implementation to process.
     * @param context The model context to use for committing the model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @throws NullPointerException if {@code implementation}, {@code context} or {@code classesDirectory} is
     * {@code null}.
     * @throws IOException if committing model objects fails.
     *
     * @see #commitModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final void commitModelObjects( final Implementation implementation, final ModelContext context,
                                          final File classesDirectory ) throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                m.setSchema( context.createSchema( this.getModel().getIdentifier() ) );

                this.commitModelObjects( implementation, m, classesDirectory );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Commits model objects of a given specification of the modules of the instance to a given class file.
     *
     * @param specification The specification to process.
     * @param marshaller The marshaller to use for committing the model objects.
     * @param javaClass The java class to commit to.
     *
     * @throws NullPointerException if {@code specification}, {@code marshaller} or {@code javaClass} is {@code null}.
     * @throws IOException if committing model objects fails.
     */
    public void commitModelObjects( final Specification specification, final Marshaller marshaller,
                                    final JavaClass javaClass ) throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( marshaller == null )
        {
            throw new NullPointerException( "marshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }

        if ( this.getModules() != null
                 && this.getModules().getSpecification( specification.getIdentifier() ) != null )
        {
            this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
                                        marshaller, new ObjectFactory().createSpecification( specification ) ) );

        }
        else if ( this.isLoggable( Level.WARNING ) )
        {
            this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
        }
    }

    /**
     * Commits model objects of a given implementation of the modules of the instance to a given class file.
     *
     * @param implementation The implementation to process.
     * @param marshaller The marshaller to use for committing the model objects.
     * @param javaClass The java class to commit to.
     *
     * @throws NullPointerException if {@code implementation}, {@code marshaller} or {@code javaClass} is {@code null}.
     * @throws IOException if committing model objects fails.
     */
    public void commitModelObjects( final Implementation implementation, final Marshaller marshaller,
                                    final JavaClass javaClass ) throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( marshaller == null )
        {
            throw new NullPointerException( "marshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }

        if ( this.getModules() != null
                 && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
        {
            final ObjectFactory of = new ObjectFactory();

            Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
            if ( dependencies == null )
            {
                dependencies = new Dependencies();
            }

            Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
            if ( properties == null )
            {
                properties = new Properties();
            }

            Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
            if ( messages == null )
            {
                messages = new Messages();
            }

            Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
            if ( specifications == null )
            {
                specifications = new Specifications();
            }

            for ( int i = 0, s0 = specifications.getReference().size(); i < s0; i++ )
            {
                final SpecificationReference r = specifications.getReference().get( i );

                if ( specifications.getSpecification( r.getIdentifier() ) == null && this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "unresolvedSpecification", r.getIdentifier(),
                                                         implementation.getIdentifier() ), null );

                }
            }

            for ( int i = 0, s0 = dependencies.getDependency().size(); i < s0; i++ )
            {
                final Dependency d = dependencies.getDependency().get( i );
                final Specification s = this.getModules().getSpecification( d.getIdentifier() );

                if ( s != null )
                {
                    if ( specifications.getSpecification( s.getIdentifier() ) == null )
                    {
                        specifications.getSpecification().add( s );
                    }
                }
                else if ( this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "unresolvedDependencySpecification", d.getIdentifier(),
                                                         d.getName(), implementation.getIdentifier() ), null );

                }
            }

            this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
                                        marshaller, of.createDependencies( dependencies ) ) );

            this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
                                        marshaller, of.createProperties( properties ) ) );

            this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
                                        marshaller, of.createMessages( messages ) ) );

            this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
                                        marshaller, of.createSpecifications( specifications ) ) );

        }
        else if ( this.isLoggable( Level.WARNING ) )
        {
            this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
        }
    }

    /**
     * Validates model objects of class files of the modules of the instance.
     *
     * @param context The model context to use for validating model objects.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code context} is {@code null}.
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext)
     */
    public final ModelValidationReport validateModelObjects( final ModelContext context ) throws IOException
    {
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( this.getModules().getSpecifications(),
                                                    this.getModules().getImplementations(), u, context );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given module of the modules of the instance.
     *
     * @param module The module to process.
     * @param context The model context to use for validating model objects.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code module} or {@code context} is {@code null}.
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext)
     * @see #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext)
     */
    public final ModelValidationReport validateModelObjects( final Module module, final ModelContext context )
        throws IOException
    {
        if ( module == null )
        {
            throw new NullPointerException( "module" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( module.getSpecifications(), module.getImplementations(), u,
                                                    context );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given specification of the modules of the instance.
     *
     * @param specification The specification to process.
     * @param context The model context to use for validating model objects.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code specification} or {@code context} is {@code null}.
     *
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final ModelValidationReport validateModelObjects( final Specification specification,
                                                             final ModelContext context ) throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null
                     && this.getModules().getSpecification( specification.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( specification, u, context );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given implementation of the modules of the instance.
     *
     * @param implementation The implementation to process.
     * @param context The model context to use for validating model objects.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code implementation} or {@code context} is {@code null}.
     *
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final ModelValidationReport validateModelObjects( final Implementation implementation,
                                                             final ModelContext context ) throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( implementation, u, context );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of the modules of the instance.
     *
     * @param context The model context to use for validating model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code context} or {@code classesDirectory} is {@code null}.
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File)
     */
    public final ModelValidationReport validateModelObjects( final ModelContext context, final File classesDirectory )
        throws IOException
    {
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( this.getModules().getSpecifications(),
                                                    this.getModules().getImplementations(), u, classesDirectory );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given module of the modules of the instance.
     *
     * @param module The module to process.
     * @param context The model context to use for validating model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code module}, {@code context} or {@code classesDirectory} is {@code null}.
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File)
     * @see #validateModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File)
     */
    public final ModelValidationReport validateModelObjects( final Module module, final ModelContext context,
                                                             final File classesDirectory ) throws IOException
    {
        if ( module == null )
        {
            throw new NullPointerException( "module" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( module.getSpecifications(), module.getImplementations(), u,
                                                    classesDirectory );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given specification of the modules of the instance.
     *
     * @param specification The specification to process.
     * @param context The model context to use for validating model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code specification}, {@code context} or {@code classesDirectory} is
     * {@code null}.
     *
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final ModelValidationReport validateModelObjects( final Specification specification,
                                                             final ModelContext context, final File classesDirectory )
        throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null
                     && this.getModules().getSpecification( specification.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( specification, u, classesDirectory );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of class files of a given implementation of the modules of the instance.
     *
     * @param implementation The implementation to process.
     * @param context The model context to use for validating model objects.
     * @param classesDirectory The directory holding the class files.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code implementation}, {@code context} or {@code classesDirectory} is
     * {@code null}.
     *
     * @throws IOException if validating model objects fails.
     *
     * @see #validateModelObjects(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
     */
    public final ModelValidationReport validateModelObjects( final Implementation implementation,
                                                             final ModelContext context, final File classesDirectory )
        throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                u.setSchema( context.createSchema( this.getModel().getIdentifier() ) );
                report = this.validateModelObjects( implementation, u, classesDirectory );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Validates model objects of a given specification of the modules of the instance.
     *
     * @param specification The specification to process.
     * @param unmarshaller The unmarshaller to use for validating model objects.
     * @param javaClass The java class to validate.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code specification}, {@code unmarshaller} or {@code javaClass} is {@code null}.
     * @throws IOException if validating model objects fails.
     */
    public ModelValidationReport validateModelObjects( final Specification specification,
                                                       final Unmarshaller unmarshaller, final JavaClass javaClass )
        throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( unmarshaller == null )
        {
            throw new NullPointerException( "unmarshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }

        ModelValidationReport report = null;

        if ( this.getModules() != null && this.getModules().getSpecification( specification.getIdentifier() ) != null )
        {
            report = new ModelValidationReport();

            Specification decoded = null;
            final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
            if ( bytes != null )
            {
                decoded = this.decodeModelObject( unmarshaller, bytes, Specification.class );
            }

            if ( decoded != null )
            {
                if ( decoded.getMultiplicity() != specification.getMultiplicity() )
                {
                    report.getDetails().add( new ModelValidationReport.Detail(
                        "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
                            "illegalMultiplicity", specification.getIdentifier(),
                            specification.getMultiplicity().value(),
                            decoded.getMultiplicity().value() ),
                        new ObjectFactory().createSpecification( specification ) ) );

                }

                if ( decoded.getScope() == null
                         ? specification.getScope() != null
                         : !decoded.getScope().equals( specification.getScope() ) )
                {
                    report.getDetails().add( new ModelValidationReport.Detail(
                        "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
                            "illegalScope", specification.getIdentifier(),
                            specification.getScope() == null ? "Multiton" : specification.getScope(),
                            decoded.getScope() == null ? "Multiton" : decoded.getScope() ),
                        new ObjectFactory().createSpecification( specification ) ) );

                }

                if ( decoded.getClazz() == null
                         ? specification.getClazz() != null
                         : !decoded.getClazz().equals( specification.getClazz() ) )
                {
                    report.getDetails().add( new ModelValidationReport.Detail(
                        "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
                            "illegalSpecificationClass", decoded.getIdentifier(),
                            specification.getClazz(), decoded.getClazz() ),
                        new ObjectFactory().createSpecification( specification ) ) );

                }
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "cannotValidateSpecification", specification.getIdentifier(),
                                                     Specification.class.getName() ), null );

            }
        }
        else if ( this.isLoggable( Level.WARNING ) )
        {
            this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
        }

        return report;
    }

    /**
     * Validates model objects of a given implementation of the modules of the instance.
     *
     * @param implementation The implementation to process.
     * @param unmarshaller The unmarshaller to use for validating model objects.
     * @param javaClass The java class to validate.
     *
     * @return The report of the validation or {@code null}, if no model objects are found.
     *
     * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code javaClass} is {@code null}.
     * @throws IOException if validating model objects fails.
     */
    public ModelValidationReport validateModelObjects( final Implementation implementation,
                                                       final Unmarshaller unmarshaller, final JavaClass javaClass )
        throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( unmarshaller == null )
        {
            throw new NullPointerException( "unmarshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }

        try
        {
            ModelValidationReport report = null;

            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                report = new ModelValidationReport();
                Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
                if ( dependencies == null )
                {
                    dependencies = new Dependencies();
                }

                Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
                if ( properties == null )
                {
                    properties = new Properties();
                }

                Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
                if ( messages == null )
                {
                    messages = new Messages();
                }

                Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
                if ( specifications == null )
                {
                    specifications = new Specifications();
                }

                Dependencies decodedDependencies = null;
                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
                if ( bytes != null )
                {
                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
                }

                Properties decodedProperties = null;
                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
                if ( bytes != null )
                {
                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
                }

                Messages decodedMessages = null;
                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
                if ( bytes != null )
                {
                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
                }

                Specifications decodedSpecifications = null;
                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
                if ( bytes != null )
                {
                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
                }

                if ( decodedDependencies != null )
                {
                    for ( int i = 0, s0 = decodedDependencies.getDependency().size(); i < s0; i++ )
                    {
                        final Dependency decodedDependency = decodedDependencies.getDependency().get( i );
                        final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
                        final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );

                        if ( dependency == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
                                    "missingDependency", implementation.getIdentifier(), decodedDependency.getName() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                        else if ( decodedDependency.getImplementationName() != null
                                      && dependency.getImplementationName() == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_DEPENDENCY_IMPLEMENTATION_NAME", Level.SEVERE, getMessage(
                                    "missingDependencyImplementationName", implementation.getIdentifier(),
                                    decodedDependency.getName() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }

                        if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null
                                 && VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
                        {
                            final Module moduleOfSpecification =
                                this.getModules().getModuleOfSpecification( s.getIdentifier() );

                            final Module moduleOfImplementation =
                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );

                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE, getMessage(
                                    "incompatibleDependency", javaClass.getClassName(),
                                    moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
                                    s.getIdentifier(),
                                    moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
                                    decodedDependency.getVersion(), s.getVersion() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                    }
                }
                else if ( this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
                                                         Dependencies.class.getName() ), null );

                }

                if ( decodedProperties != null )
                {
                    for ( int i = 0, s0 = decodedProperties.getProperty().size(); i < s0; i++ )
                    {
                        final Property decodedProperty = decodedProperties.getProperty().get( i );
                        final Property property = properties.getProperty( decodedProperty.getName() );

                        if ( property == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
                                    "missingProperty", implementation.getIdentifier(), decodedProperty.getName() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                        else if ( decodedProperty.getType() == null
                                      ? property.getType() != null
                                      : !decodedProperty.getType().equals( property.getType() ) )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE, getMessage(
                                    "illegalPropertyType", implementation.getIdentifier(), decodedProperty.getName(),
                                    property.getType() == null ? "<>" : property.getType(),
                                    decodedProperty.getType() == null ? "<>" : decodedProperty.getType() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                    }
                }
                else if ( this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
                                                         Properties.class.getName() ), null );

                }

                if ( decodedMessages != null )
                {
                    for ( int i = 0, s0 = decodedMessages.getMessage().size(); i < s0; i++ )
                    {
                        final Message decodedMessage = decodedMessages.getMessage().get( i );
                        final Message message = messages.getMessage( decodedMessage.getName() );

                        if ( message == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE, getMessage(
                                    "missingMessage", implementation.getIdentifier(), decodedMessage.getName() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                    }
                }
                else if ( this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
                                                         Messages.class.getName() ), null );

                }

                if ( decodedSpecifications != null )
                {
                    for ( int i = 0, s0 = decodedSpecifications.getSpecification().size(); i < s0; i++ )
                    {
                        final Specification decodedSpecification = decodedSpecifications.getSpecification().get( i );
                        final Specification specification =
                            this.getModules().getSpecification( decodedSpecification.getIdentifier() );

                        if ( specification == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
                                    "missingSpecification", implementation.getIdentifier(),
                                    decodedSpecification.getIdentifier() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                        else
                        {
                            if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
                            {
                                report.getDetails().add( new ModelValidationReport.Detail(
                                    "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE, getMessage(
                                        "illegalMultiplicity", specification.getIdentifier(),
                                        specification.getMultiplicity().value(),
                                        decodedSpecification.getMultiplicity().value() ),
                                    new ObjectFactory().createImplementation( implementation ) ) );

                            }

                            if ( decodedSpecification.getScope() == null
                                     ? specification.getScope() != null
                                     : !decodedSpecification.getScope().equals( specification.getScope() ) )
                            {
                                report.getDetails().add( new ModelValidationReport.Detail(
                                    "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE, getMessage(
                                        "illegalScope", decodedSpecification.getIdentifier(),
                                        specification.getScope() == null ? "Multiton" : specification.getScope(),
                                        decodedSpecification.getScope() == null
                                            ? "Multiton"
                                            : decodedSpecification.getScope() ),
                                    new ObjectFactory().createImplementation( implementation ) ) );

                            }

                            if ( decodedSpecification.getClazz() == null
                                     ? specification.getClazz() != null
                                     : !decodedSpecification.getClazz().equals( specification.getClazz() ) )
                            {
                                report.getDetails().add( new ModelValidationReport.Detail(
                                    "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE, getMessage(
                                        "illegalSpecificationClass", decodedSpecification.getIdentifier(),
                                        specification.getClazz(), decodedSpecification.getClazz() ),
                                    new ObjectFactory().createImplementation( implementation ) ) );

                            }
                        }
                    }

                    for ( int i = 0, s0 = decodedSpecifications.getReference().size(); i < s0; i++ )
                    {
                        final SpecificationReference decodedReference = decodedSpecifications.getReference().get( i );
                        final Specification specification =
                            specifications.getSpecification( decodedReference.getIdentifier() );

                        if ( specification == null )
                        {
                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_MISSING_SPECIFICATION", Level.SEVERE, getMessage(
                                    "missingSpecification", implementation.getIdentifier(),
                                    decodedReference.getIdentifier() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                        else if ( decodedReference.getVersion() != null && specification.getVersion() != null
                                      && VersionParser.compare( decodedReference.getVersion(),
                                                                specification.getVersion() ) != 0 )
                        {
                            final Module moduleOfSpecification =
                                this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );

                            final Module moduleOfImplementation =
                                this.getModules().getModuleOfImplementation( implementation.getIdentifier() );

                            report.getDetails().add( new ModelValidationReport.Detail(
                                "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE, getMessage(
                                    "incompatibleImplementation", javaClass.getClassName(),
                                    moduleOfImplementation == null ? "<>" : moduleOfImplementation.getName(),
                                    specification.getIdentifier(),
                                    moduleOfSpecification == null ? "<>" : moduleOfSpecification.getName(),
                                    decodedReference.getVersion(), specification.getVersion() ),
                                new ObjectFactory().createImplementation( implementation ) ) );

                        }
                    }
                }
                else if ( this.isLoggable( Level.WARNING ) )
                {
                    this.log( Level.WARNING, getMessage( "cannotValidateImplementation", implementation.getIdentifier(),
                                                         Specifications.class.getName() ), null );

                }
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }

            return report;
        }
        catch ( final ParseException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
        catch ( final TokenMgrError e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Transforms model objects of class files of the modules of the instance.
     *
     * @param context The model context to use for transforming model objects.
     * @param classesDirectory The directory holding the class files.
     * @param transformers The transformers to use for transforming model objects.
     *
     * @throws NullPointerException if {@code context}, {@code classesDirectory} or {@code transformers} is
     * {@code null}.
     * @throws IOException if transforming model objects fails.
     *
     * @see #transformModelObjects(org.jomc.model.Module, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
     */
    public final void transformModelObjects( final ModelContext context, final File classesDirectory,
                                             final List<Transformer> transformers ) throws IOException
    {
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }
        if ( !classesDirectory.isDirectory() )
        {
            throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
        }

        try
        {
            if ( this.getModules() != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                final Schema s = context.createSchema( this.getModel().getIdentifier() );
                u.setSchema( s );
                m.setSchema( s );

                this.transformModelObjects( this.getModules().getSpecifications(),
                                            this.getModules().getImplementations(),
                                            u, m, classesDirectory, transformers );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Transforms model objects of class files of a given module of the modules of the instance.
     *
     * @param module The module to process.
     * @param context The model context to use for transforming model objects.
     * @param classesDirectory The directory holding the class files.
     * @param transformers The transformers to use for transforming the model objects.
     *
     * @throws NullPointerException if {@code module}, {@code context}, {@code classesDirectory} or {@code transformers}
     * is {@code null}.
     * @throws IOException if transforming model objects fails.
     *
     * @see #transformModelObjects(org.jomc.model.Specification, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
     * @see #transformModelObjects(org.jomc.model.Implementation, org.jomc.modlet.ModelContext, java.io.File, java.util.List)
     */
    public final void transformModelObjects( final Module module, final ModelContext context,
                                             final File classesDirectory, final List<Transformer> transformers )
        throws IOException
    {
        if ( module == null )
        {
            throw new NullPointerException( "module" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }
        if ( !classesDirectory.isDirectory() )
        {
            throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
        }

        try
        {
            if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                final Schema s = context.createSchema( this.getModel().getIdentifier() );
                u.setSchema( s );
                m.setSchema( s );

                this.transformModelObjects( module.getSpecifications(), module.getImplementations(), u, m,
                                            classesDirectory, transformers );

            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Transforms model objects of class files of a given specification of the modules of the instance.
     *
     * @param specification The specification to process.
     * @param context The model context to use for transforming model objects.
     * @param classesDirectory The directory holding the class files.
     * @param transformers The transformers to use for transforming the model objects.
     *
     * @throws NullPointerException if {@code specification}, {@code context}, {@code classesDirectory} or
     * {@code transformers} is {@code null}.
     * @throws IOException if transforming model objects fails.
     *
     * @see #transformModelObjects(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
     */
    public final void transformModelObjects( final Specification specification, final ModelContext context,
                                             final File classesDirectory, final List<Transformer> transformers )
        throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }
        if ( !classesDirectory.isDirectory() )
        {
            throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getSpecification( specification.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                final Schema s = context.createSchema( this.getModel().getIdentifier() );
                u.setSchema( s );
                m.setSchema( s );

                this.transformModelObjects( specification, m, u, classesDirectory, transformers );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Transforms model objects of class files of a given implementation of the modules of the instance.
     *
     * @param implementation The implementation to process.
     * @param context The model context to use for transforming model objects.
     * @param classesDirectory The directory holding the class files.
     * @param transformers The transformers to use for transforming the model objects.
     *
     * @throws NullPointerException if {@code implementation}, {@code context}, {@code classesDirectory} or
     * {@code transformers} is {@code null}.
     * @throws IOException if transforming model objects fails.
     *
     * @see #transformModelObjects(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
     */
    public final void transformModelObjects( final Implementation implementation, final ModelContext context,
                                             final File classesDirectory, final List<Transformer> transformers )
        throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( context == null )
        {
            throw new NullPointerException( "context" );
        }
        if ( classesDirectory == null )
        {
            throw new NullPointerException( "classesDirectory" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }
        if ( !classesDirectory.isDirectory() )
        {
            throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                final Unmarshaller u = context.createUnmarshaller( this.getModel().getIdentifier() );
                final Marshaller m = context.createMarshaller( this.getModel().getIdentifier() );
                final Schema s = context.createSchema( this.getModel().getIdentifier() );
                u.setSchema( s );
                m.setSchema( s );

                this.transformModelObjects( implementation, m, u, classesDirectory, transformers );
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }
        }
        catch ( final ModelException e )
        {
            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( getMessage( e ) ).initCause( e );
        }
    }

    /**
     * Transforms model objects of a given specification of the modules of the instance.
     *
     * @param specification The specification to process.
     * @param marshaller The marshaller to use for transforming model objects.
     * @param unmarshaller The unmarshaller to use for transforming model objects.
     * @param javaClass The java class to transform model objects of.
     * @param transformers The transformers to use for transforming the model objects.
     *
     * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
     * {@code javaClass} or {@code transformers} is {@code null}.
     * @throws IOException if transforming model objects fails.
     */
    public void transformModelObjects( final Specification specification, final Marshaller marshaller,
                                       final Unmarshaller unmarshaller, final JavaClass javaClass,
                                       final List<Transformer> transformers ) throws IOException
    {
        if ( specification == null )
        {
            throw new NullPointerException( "specification" );
        }
        if ( marshaller == null )
        {
            throw new NullPointerException( "marshaller" );
        }
        if ( unmarshaller == null )
        {
            throw new NullPointerException( "unmarshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getSpecification( specification.getIdentifier() ) != null )
            {
                Specification decodedSpecification = null;
                final ObjectFactory objectFactory = new ObjectFactory();
                final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
                if ( bytes != null )
                {
                    decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
                }

                if ( decodedSpecification != null )
                {
                    for ( int i = 0, l = transformers.size(); i < l; i++ )
                    {
                        final JAXBSource source =
                            new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );

                        final JAXBResult result = new JAXBResult( unmarshaller );
                        transformers.get( i ).transform( source, result );

                        if ( result.getResult() instanceof JAXBElement<?>
                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Specification )
                        {
                            decodedSpecification = (Specification) ( (JAXBElement<?>) result.getResult() ).getValue();
                        }
                        else
                        {
                            throw new IOException( getMessage(
                                "illegalSpecificationTransformationResult", specification.getIdentifier() ) );

                        }
                    }

                    this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
                                                marshaller,
                                                objectFactory.createSpecification( decodedSpecification ) ) );

                }
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
            }
        }
        catch ( final JAXBException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getLinkedException() != null )
            {
                message = getMessage( e.getLinkedException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
        catch ( final TransformerException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getException() != null )
            {
                message = getMessage( e.getException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
    }

    /**
     * Transforms model objects of a given implementation of the modules of the instance.
     *
     * @param implementation The implementation to process.
     * @param marshaller The marshaller to use for transforming model objects.
     * @param unmarshaller The unmarshaller to use for transforming model objects.
     * @param javaClass The java class to transform model object of.
     * @param transformers The transformers to use for transforming the model objects.
     *
     * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
     * {@code javaClass} or {@code transformers} is {@code null}.
     * @throws IOException if transforming model objects fails.
     */
    public void transformModelObjects( final Implementation implementation, final Marshaller marshaller,
                                       final Unmarshaller unmarshaller, final JavaClass javaClass,
                                       final List<Transformer> transformers ) throws IOException
    {
        if ( implementation == null )
        {
            throw new NullPointerException( "implementation" );
        }
        if ( marshaller == null )
        {
            throw new NullPointerException( "marshaller" );
        }
        if ( unmarshaller == null )
        {
            throw new NullPointerException( "unmarshaller" );
        }
        if ( javaClass == null )
        {
            throw new NullPointerException( "javaClass" );
        }
        if ( transformers == null )
        {
            throw new NullPointerException( "transformers" );
        }

        try
        {
            if ( this.getModules() != null
                     && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
            {
                Dependencies decodedDependencies = null;
                byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
                if ( bytes != null )
                {
                    decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
                }

                Messages decodedMessages = null;
                bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
                if ( bytes != null )
                {
                    decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
                }

                Properties decodedProperties = null;
                bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
                if ( bytes != null )
                {
                    decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
                }

                Specifications decodedSpecifications = null;
                bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
                if ( bytes != null )
                {
                    decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
                }

                final ObjectFactory of = new ObjectFactory();
                for ( int i = 0, l = transformers.size(); i < l; i++ )
                {
                    final Transformer transformer = transformers.get( i );

                    if ( decodedDependencies != null )
                    {
                        final JAXBSource source =
                            new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );

                        final JAXBResult result = new JAXBResult( unmarshaller );
                        transformer.transform( source, result );

                        if ( result.getResult() instanceof JAXBElement<?>
                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Dependencies )
                        {
                            decodedDependencies = (Dependencies) ( (JAXBElement<?>) result.getResult() ).getValue();
                        }
                        else
                        {
                            throw new IOException( getMessage(
                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );

                        }
                    }

                    if ( decodedMessages != null )
                    {
                        final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
                        final JAXBResult result = new JAXBResult( unmarshaller );
                        transformer.transform( source, result );

                        if ( result.getResult() instanceof JAXBElement<?>
                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Messages )
                        {
                            decodedMessages = (Messages) ( (JAXBElement<?>) result.getResult() ).getValue();
                        }
                        else
                        {
                            throw new IOException( getMessage(
                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );

                        }
                    }

                    if ( decodedProperties != null )
                    {
                        final JAXBSource source =
                            new JAXBSource( marshaller, of.createProperties( decodedProperties ) );

                        final JAXBResult result = new JAXBResult( unmarshaller );
                        transformer.transform( source, result );

                        if ( result.getResult() instanceof JAXBElement<?>
                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Properties )
                        {
                            decodedProperties = (Properties) ( (JAXBElement<?>) result.getResult() ).getValue();
                        }
                        else
                        {
                            throw new IOException( getMessage(
                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );

                        }
                    }

                    if ( decodedSpecifications != null )
                    {
                        final JAXBSource source =
                            new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );

                        final JAXBResult result = new JAXBResult( unmarshaller );
                        transformer.transform( source, result );

                        if ( result.getResult() instanceof JAXBElement<?>
                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Specifications )
                        {
                            decodedSpecifications = (Specifications) ( (JAXBElement<?>) result.getResult() ).getValue();
                        }
                        else
                        {
                            throw new IOException( getMessage(
                                "illegalImplementationTransformationResult", implementation.getIdentifier() ) );

                        }
                    }
                }

                if ( decodedDependencies != null )
                {
                    this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
                                                marshaller, of.createDependencies( decodedDependencies ) ) );

                }

                if ( decodedMessages != null )
                {
                    this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
                                                marshaller, of.createMessages( decodedMessages ) ) );

                }

                if ( decodedProperties != null )
                {
                    this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
                                                marshaller, of.createProperties( decodedProperties ) ) );

                }

                if ( decodedSpecifications != null )
                {
                    this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
                                                marshaller, of.createSpecifications( decodedSpecifications ) ) );

                }
            }
            else if ( this.isLoggable( Level.WARNING ) )
            {
                this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
            }
        }
        catch ( final JAXBException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getLinkedException() != null )
            {
                message = getMessage( e.getLinkedException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
        catch ( final TransformerException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getException() != null )
            {
                message = getMessage( e.getException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
    }

    /**
     * Gets an attribute from a java class.
     *
     * @param clazz The java class to get an attribute from.
     * @param attributeName The name of the attribute to get.
     *
     * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null}, if no such attribute
     * exists.
     *
     * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
     * @throws IOException if getting the attribute fails.
     *
     * @see JavaClass#getAttributes()
     */
    public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
    {
        if ( clazz == null )
        {
            throw new NullPointerException( "clazz" );
        }
        if ( attributeName == null )
        {
            throw new NullPointerException( "attributeName" );
        }

        final Attribute[] attributes = clazz.getAttributes();

        for ( int i = attributes.length - 1; i >= 0; i-- )
        {
            final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );

            if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
            {
                final Unknown unknown = (Unknown) attributes[i];
                return unknown.getBytes();
            }
        }

        return null;
    }

    /**
     * Adds or updates an attribute in a java class.
     *
     * @param clazz The class to update an attribute of.
     * @param attributeName The name of the attribute to update.
     * @param data The new data of the attribute to update the {@code clazz} with.
     *
     * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
     * @throws IOException if updating the class file fails.
     *
     * @see JavaClass#getAttributes()
     */
    public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
        throws IOException
    {
        if ( clazz == null )
        {
            throw new NullPointerException( "clazz" );
        }
        if ( attributeName == null )
        {
            throw new NullPointerException( "attributeName" );
        }

        final byte[] attributeData = data != null ? data : NO_BYTES;

        /*
         * The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
         *
         * A Java virtual machine implementation is required to silently ignore any
         * or all attributes in the attributes table of a ClassFile structure that
         * it does not recognize. Attributes not defined in this specification are
         * not allowed to affect the semantics of the class file, but only to
         * provide additional descriptive information (§4.7.1).
         */
        Attribute[] attributes = clazz.getAttributes();

        int attributeIndex = -1;
        int nameIndex = -1;

        for ( int i = attributes.length - 1; i >= 0; i-- )
        {
            final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );

            if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
            {
                attributeIndex = i;
                nameIndex = attributes[i].getNameIndex();
            }
        }

        if ( nameIndex == -1 )
        {
            final Constant[] pool = clazz.getConstantPool().getConstantPool();
            final Constant[] tmp = new Constant[ pool.length + 1 ];
            System.arraycopy( pool, 0, tmp, 0, pool.length );
            tmp[pool.length] = new ConstantUtf8( attributeName );
            nameIndex = pool.length;
            clazz.setConstantPool( new ConstantPool( tmp ) );
        }

        final Unknown unknown = new Unknown( nameIndex, attributeData.length, attributeData, clazz.getConstantPool() );

        if ( attributeIndex == -1 )
        {
            final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
            System.arraycopy( attributes, 0, tmp, 0, attributes.length );
            tmp[attributes.length] = unknown;
            attributes = tmp;
        }
        else
        {
            attributes[attributeIndex] = unknown;
        }

        clazz.setAttributes( attributes );
    }

    /**
     * Encodes a model object to a byte array.
     *
     * @param marshaller The marshaller to use for encoding the object.
     * @param modelObject The model object to encode.
     *
     * @return GZIP compressed XML document of {@code modelObject}.
     *
     * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
     * @throws IOException if encoding {@code modelObject} fails.
     *
     * @see #decodeModelObject(javax.xml.bind.Unmarshaller, byte[], java.lang.Class)
     */
    public byte[] encodeModelObject( final Marshaller marshaller, final JAXBElement<? extends ModelObject> modelObject )
        throws IOException
    {
        if ( marshaller == null )
        {
            throw new NullPointerException( "marshaller" );
        }
        if ( modelObject == null )
        {
            throw new NullPointerException( "modelObject" );
        }

        try
        {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            final GZIPOutputStream out = new GZIPOutputStream( baos );
            marshaller.marshal( modelObject, out );
            out.close();
            return baos.toByteArray();
        }
        catch ( final JAXBException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getLinkedException() != null )
            {
                message = getMessage( e.getLinkedException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
    }

    /**
     * Decodes a model object from a byte array.
     *
     * @param unmarshaller The unmarshaller to use for decoding the object.
     * @param bytes The encoded model object to decode.
     * @param type The class of the type of the encoded model object.
     * @param <T> The type of the encoded model object.
     *
     * @return Model object decoded from {@code bytes}.
     *
     * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
     * @throws IOException if decoding {@code bytes} fails.
     *
     * @see #encodeModelObject(javax.xml.bind.Marshaller, javax.xml.bind.JAXBElement)
     */
    public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
                                                        final Class<T> type ) throws IOException
    {
        if ( unmarshaller == null )
        {
            throw new NullPointerException( "unmarshaller" );
        }
        if ( bytes == null )
        {
            throw new NullPointerException( "bytes" );
        }
        if ( type == null )
        {
            throw new NullPointerException( "type" );
        }

        try
        {
            final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
            final GZIPInputStream in = new GZIPInputStream( bais );
            final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
            in.close();
            return element.getValue();
        }
        catch ( final JAXBException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getLinkedException() != null )
            {
                message = getMessage( e.getLinkedException() );
            }

            // JDK: As of JDK 6, "new IOException( message, cause )".
            throw (IOException) new IOException( message ).initCause( e );
        }
    }

    private void commitModelObjects( final Specifications specifications, final Implementations implementations,
                                     final Marshaller marshaller, final File classesDirectory )
        throws IOException, ModelObjectException
    {
        if ( specifications != null )
        {
            for ( int i = specifications.getSpecification().size() - 1; i >= 0; i-- )
            {
                this.commitModelObjects( specifications.getSpecification().get( i ), marshaller, classesDirectory );
            }
        }

        if ( implementations != null )
        {
            for ( int i = implementations.getImplementation().size() - 1; i >= 0; i-- )
            {
                this.commitModelObjects( implementations.getImplementation().get( i ), marshaller, classesDirectory );
            }
        }
    }

    private void commitModelObjects( final Specification specification, final Marshaller marshaller,
                                     final File classesDirectory ) throws IOException, ModelObjectException
    {
        if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
        {
            final String classLocation =
                specification.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !( classFile.canRead() && classFile.canWrite() ) )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );
            this.commitModelObjects( specification, marshaller, javaClass );
            this.writeJavaClass( javaClass, classFile );
        }
    }

    private void commitModelObjects( final Implementation implementation, final Marshaller marshaller,
                                     final File classesDirectory ) throws IOException, ModelObjectException
    {
        if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
        {
            final String classLocation =
                implementation.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !( classFile.canRead() && classFile.canWrite() ) )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "committing", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );
            this.commitModelObjects( implementation, marshaller, javaClass );
            this.writeJavaClass( javaClass, classFile );
        }
    }

    private ModelValidationReport validateModelObjects( final Specifications specifications,
                                                        final Implementations implementations,
                                                        final Unmarshaller unmarshaller, final File classesDirectory )
        throws IOException, ModelObjectException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( specifications != null )
        {
            for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
            {
                final ModelValidationReport current = this.validateModelObjects(
                    specifications.getSpecification().get( i ), unmarshaller, classesDirectory );

                report.getDetails().addAll( current.getDetails() );
            }
        }

        if ( implementations != null )
        {
            for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
            {
                final ModelValidationReport current = this.validateModelObjects(
                    implementations.getImplementation().get( i ), unmarshaller, classesDirectory );

                report.getDetails().addAll( current.getDetails() );
            }
        }

        return report;
    }

    private ModelValidationReport validateModelObjects( final Specification specification,
                                                        final Unmarshaller unmarshaller,
                                                        final File classesDirectory )
        throws IOException, ModelObjectException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
        {
            final String classLocation =
                specification.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !classFile.canRead() )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "validating", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );

            report.getDetails().addAll(
                this.validateModelObjects( specification, unmarshaller, javaClass ).getDetails() );

        }

        return report;
    }

    private ModelValidationReport validateModelObjects( final Implementation implementation,
                                                        final Unmarshaller unmarshaller,
                                                        final File classesDirectory )
        throws IOException, ModelObjectException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
        {
            final String classLocation =
                implementation.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !classFile.canRead() )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "validating", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );

            report.getDetails().addAll(
                this.validateModelObjects( implementation, unmarshaller, javaClass ).getDetails() );

        }

        return report;
    }

    private ModelValidationReport validateModelObjects( final Specifications specifications,
                                                        final Implementations implementations,
                                                        final Unmarshaller unmarshaller, final ModelContext context )
        throws IOException, ModelException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( specifications != null )
        {
            for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
            {
                final ModelValidationReport current = this.validateModelObjects(
                    specifications.getSpecification().get( i ), unmarshaller, context );

                report.getDetails().addAll( current.getDetails() );
            }
        }

        if ( implementations != null )
        {
            for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
            {
                final ModelValidationReport current = this.validateModelObjects(
                    implementations.getImplementation().get( i ), unmarshaller, context );

                report.getDetails().addAll( current.getDetails() );
            }
        }

        return report;
    }

    private ModelValidationReport validateModelObjects( final Specification specification,
                                                        final Unmarshaller unmarshaller,
                                                        final ModelContext context ) throws IOException, ModelException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
        {
            final String classLocation =
                specification.getJavaTypeName().getClassName().replace( '.', '/' ) + ".class";

            final URL classUrl = context.findResource( classLocation );

            if ( classUrl == null )
            {
                throw new IOException( getMessage( "resourceNotFound", classLocation ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "validatingSpecification", specification.getIdentifier() ), null );
            }

            InputStream in = null;
            JavaClass javaClass = null;
            boolean suppressExceptionOnClose = true;

            try
            {
                in = classUrl.openStream();
                javaClass = new ClassParser( in, classUrl.toExternalForm() ).parse();
                suppressExceptionOnClose = false;
            }
            finally
            {
                try
                {
                    if ( in != null )
                    {
                        in.close();
                    }
                }
                catch ( final IOException e )
                {
                    if ( suppressExceptionOnClose )
                    {
                        this.log( Level.SEVERE, getMessage( e ), e );
                    }
                    else
                    {
                        throw e;
                    }
                }
            }

            report.getDetails().addAll(
                this.validateModelObjects( specification, unmarshaller, javaClass ).getDetails() );

        }

        return report;
    }

    private ModelValidationReport validateModelObjects( final Implementation implementation,
                                                        final Unmarshaller unmarshaller,
                                                        final ModelContext context ) throws IOException, ModelException
    {
        final ModelValidationReport report = new ModelValidationReport();

        if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
        {
            final String classLocation = implementation.getJavaTypeName().getClassName().replace( '.', '/' ) + ".class";
            final URL classUrl = context.findResource( classLocation );

            if ( classUrl == null )
            {
                throw new IOException( getMessage( "resourceNotFound", classLocation ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "validatingImplementation", implementation.getIdentifier() ), null );
            }

            InputStream in = null;
            JavaClass javaClass = null;
            boolean suppressExceptionOnClose = true;

            try
            {
                in = classUrl.openStream();
                javaClass = new ClassParser( in, classUrl.toExternalForm() ).parse();
                suppressExceptionOnClose = false;
            }
            finally
            {
                try
                {
                    if ( in != null )
                    {
                        in.close();
                    }
                }
                catch ( final IOException e )
                {
                    if ( suppressExceptionOnClose )
                    {
                        this.log( Level.SEVERE, getMessage( e ), e );
                    }
                    else
                    {
                        throw e;
                    }
                }
            }

            report.getDetails().addAll(
                this.validateModelObjects( implementation, unmarshaller, javaClass ).getDetails() );

        }

        return report;
    }

    private void transformModelObjects( final Specifications specifications, final Implementations implementations,
                                        final Unmarshaller unmarshaller, final Marshaller marshaller,
                                        final File classesDirectory, final List<Transformer> transformers )
        throws IOException, ModelObjectException
    {
        if ( specifications != null )
        {
            for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
            {
                this.transformModelObjects( specifications.getSpecification().get( i ), marshaller, unmarshaller,
                                            classesDirectory, transformers );

            }
        }

        if ( implementations != null )
        {
            for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
            {
                this.transformModelObjects( implementations.getImplementation().get( i ), marshaller, unmarshaller,
                                            classesDirectory, transformers );

            }
        }
    }

    private void transformModelObjects( final Specification specification, final Marshaller marshaller,
                                        final Unmarshaller unmarshaller, final File classesDirectory,
                                        final List<Transformer> transformers ) throws IOException, ModelObjectException
    {
        if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
        {
            final String classLocation =
                specification.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !( classFile.canRead() && classFile.canWrite() ) )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );
            this.transformModelObjects( specification, marshaller, unmarshaller, javaClass, transformers );
            this.writeJavaClass( javaClass, classFile );
        }
    }

    private void transformModelObjects( final Implementation implementation, final Marshaller marshaller,
                                        final Unmarshaller unmarshaller, final File classesDirectory,
                                        final List<Transformer> transformers ) throws IOException, ModelObjectException
    {
        if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
        {
            final String classLocation =
                implementation.getJavaTypeName().getClassName().replace( '.', File.separatorChar ) + ".class";

            final File classFile = new File( classesDirectory, classLocation );

            if ( !classesDirectory.isDirectory() )
            {
                throw new IOException( getMessage( "directoryNotFound", classesDirectory.getAbsolutePath() ) );
            }
            if ( !classFile.isFile() )
            {
                throw new IOException( getMessage( "fileNotFound", classFile.getAbsolutePath() ) );
            }
            if ( !( classFile.canRead() && classFile.canWrite() ) )
            {
                throw new IOException( getMessage( "fileAccessDenied", classFile.getAbsolutePath() ) );
            }

            if ( this.isLoggable( Level.INFO ) )
            {
                this.log( Level.INFO, getMessage( "transforming", classFile.getAbsolutePath() ), null );
            }

            final JavaClass javaClass = this.readJavaClass( classFile );
            this.transformModelObjects( implementation, marshaller, unmarshaller, javaClass, transformers );
            this.writeJavaClass( javaClass, classFile );
        }
    }

    private JavaClass readJavaClass( final File classFile ) throws IOException
    {
        FileInputStream in = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;
        boolean suppressExceptionOnClose = true;

        try
        {
            in = new FileInputStream( classFile );
            fileChannel = in.getChannel();
            fileLock = fileChannel.lock( 0, classFile.length(), true );

            final JavaClass javaClass = new ClassParser( in, classFile.getAbsolutePath() ).parse();
            suppressExceptionOnClose = false;
            return javaClass;
        }
        finally
        {
            this.releaseAndClose( fileLock, fileChannel, in, suppressExceptionOnClose );
        }
    }

    private void writeJavaClass( final JavaClass javaClass, final File classFile ) throws IOException
    {
        RandomAccessFile randomAccessFile = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;
        boolean suppressExceptionOnClose = true;

        final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        javaClass.dump( byteStream );
        byteStream.close();

        final byte[] bytes = byteStream.toByteArray();

        try
        {
            randomAccessFile = new RandomAccessFile( classFile, "rw" );
            fileChannel = randomAccessFile.getChannel();
            fileLock = fileChannel.lock();
            fileChannel.truncate( bytes.length );
            fileChannel.position( 0L );
            fileChannel.write( ByteBuffer.wrap( bytes ) );
            fileChannel.force( true );
            suppressExceptionOnClose = false;
        }
        finally
        {
            this.releaseAndClose( fileLock, fileChannel, randomAccessFile, suppressExceptionOnClose );
        }
    }

    private void releaseAndClose( final FileLock fileLock, final FileChannel fileChannel,
                                  final Closeable closeable, final boolean suppressExceptions )
        throws IOException
    {
        try
        {
            if ( fileLock != null )
            {
                fileLock.release();
            }
        }
        catch ( final IOException e )
        {
            if ( suppressExceptions )
            {
                this.log( Level.SEVERE, null, e );
            }
            else
            {
                throw e;
            }
        }
        finally
        {
            try
            {
                if ( fileChannel != null )
                {
                    fileChannel.close();
                }
            }
            catch ( final IOException e )
            {
                if ( suppressExceptions )
                {
                    this.log( Level.SEVERE, null, e );
                }
                else
                {
                    throw e;
                }
            }
            finally
            {
                try
                {
                    if ( closeable != null )
                    {
                        closeable.close();
                    }
                }
                catch ( final IOException e )
                {
                    if ( suppressExceptions )
                    {
                        this.log( Level.SEVERE, null, e );
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }
    }

    private static String getMessage( final String key, final Object... arguments )
    {
        return MessageFormat.format( ResourceBundle.getBundle(
            ClassFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );

    }

    private static String getMessage( final Throwable t )
    {
        return t != null
                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
                         ? t.getMessage()
                         : getMessage( t.getCause() )
                   : null;

    }

}