/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.shrinkwrap.impl.base.serialization;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Assignable;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.serialization.ZipSerializableView;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.Validate;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;

/**
 * Copy of {@link ZipSerializableViewImpl} so we may test for backwards-compatibility of the wire protocol. This class
 * is *never to change* once we publish/lock the wire protocol. It is used to mock different ShrinkWrap versions
 * communicating with one another.
 *
 * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
 * @version $Revision: $
 */
public class ZipSerializableOriginalImpl implements ZipSerializableView {

    // -------------------------------------------------------------------------------------||
    // Class Members ----------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 1L;

    /**
     * Logger
     */
    private static final Logger log = Logger.getLogger(ZipSerializableViewImpl.class.getName());

    // -------------------------------------------------------------------------------------||
    // Instance Members -------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Name of the archive; to be serialized
     */
    private final String name;

    /**
     * Underlying archive. Won't be directly serialized; instead we'll encode it as ZIP and send that
     */
    private transient Archive<?> archive;

    // -------------------------------------------------------------------------------------||
    // Constructor ------------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Creates a new instance, wrapping the specified {@link Archive}
     */
    public ZipSerializableOriginalImpl(final Archive<?> archive) {
        Validate.notNull(archive, "Archive must be specified");
        final String name = archive.getName();
        Validate.notNullOrEmpty(name, "Name of archive must be specified");
        this.archive = archive;
        this.name = name;
    }

    // -------------------------------------------------------------------------------------||
    // Required Implementations -----------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.shrinkwrap.api.Assignable#as(java.lang.Class)
     */
    @Override
    public <TYPE extends Assignable> TYPE as(final Class<TYPE> clazz) {
        return archive.as(clazz);
    }

    // -------------------------------------------------------------------------------------||
    // Serialization ----------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Serializes the invocation with a custom form
     *
     * @serialData After all non-transient fields are written, we send the {@link Archive} contents encoded as ZIP.
     */
    private void writeObject(final ObjectOutputStream out) throws IOException {
        // Default write of non-transient fields
        out.defaultWriteObject();

        // Write as ZIP
        final InputStream in = archive.as(ZipExporter.class).exportAsInputStream();
        try {
            IOUtil.copy(in, out); // Don't close the outstream
        } finally {
            // In case we get an InputStream type that supports closing
            in.close();
        }

        // Log
        if (log.isLoggable(Level.FINER)) {
            log.finer("Wrote archive: " + archive.toString());
        }
    }

    /**
     * Deserializes according to the custom form defined by
     * {@link ZipSerializableViewImpl#writeObject(ObjectOutputStream)}
     */
    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        // Get default form
        in.defaultReadObject();

        // Create new Archive
        final String name = this.name;
        final ZipImporter archive = ShrinkWrap.create(ZipImporter.class, name);

        // Read in
        archive.importFrom(in);

        // Set
        this.archive = archive.as(JavaArchive.class);

        // Log
        if (log.isLoggable(Level.FINER)) {
            log.finer("Read in archive: " + archive.toString());
        }

        /*
         * Leave this bit here.
         *
         * After reading in the ZIP stream contents, we need to also get to the EOF marker (which is not read in by the
         * ZIP import process because it's the ZIP header, not part of the true contents. Putting this loop here ensures
         * we reach the marker, which is *not* the true end of the stream. Object data may be read again after here via
         * something like:
         *
         * in.readObject();
         *
         * Without this loop we'll get an OptionalDataException when trying to read more objects in from the stream. In
         * the future we may add state which needs to be part of the serialization protocol, and things need to stay in
         * order, so they'll be added *after* the archive ZIP contents. Thus we must be able to read them.
         */
        while (in.read() != -1) {
            ;
        }

    }

}
