// Copyright 2011 Google Inc.
//
// 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 com.google.appengine.tools.pipeline.impl.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * @author rudominer@google.com (Your Name Here)
 *
 */
public class SerializationUtils {

  private static final int MAX_UNCOMPRESSED_BYTE_SIZE = 1000000;
  private static final int ZLIB_COMPRESSION = 1;

  private static class InternalByteArrayOutputStream extends ByteArrayOutputStream {

    public InternalByteArrayOutputStream(int size) {
      super(size);
    }

    private byte[] getInternalBuffer() {
      return buf;
    }
  }

  public static byte[] serialize(Object x) throws IOException {
    InternalByteArrayOutputStream bytes = new InternalByteArrayOutputStream(512);
    try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
      out.writeObject(x);
    }
    if (bytes.size() <= MAX_UNCOMPRESSED_BYTE_SIZE) {
      return bytes.toByteArray();
    }
    Deflater deflater =  new Deflater(Deflater.BEST_COMPRESSION, true);
    ByteArrayOutputStream compressedBytes = new ByteArrayOutputStream(bytes.size() / 4);
    compressedBytes.write(0);
    compressedBytes.write(ZLIB_COMPRESSION);
    try (DeflaterOutputStream out = new DeflaterOutputStream(compressedBytes, deflater)) {
      // Use internal buffer to avoid copying it.
      out.write(bytes.getInternalBuffer(), 0, bytes.size());
    }
    return compressedBytes.toByteArray();
  }

  public static Object deserialize(byte[] bytes) throws IOException {
    if (bytes.length < 2) {
      throw new IOException("Invalid bytes content");
    }
    InputStream in = new ByteArrayInputStream(bytes);
    if (bytes[0] == 0) {
      in.read(); // consume the marker;
      if (in.read() != ZLIB_COMPRESSION) {
        throw new IOException("Unknown compression type");
      }
      Inflater inflater =  new Inflater(true);
      in = new InflaterInputStream(in, inflater);
    }
    try (ObjectInputStream oin = new ObjectInputStream(in)) {
      try {
        return oin.readObject();
      } catch (ClassNotFoundException e) {
        throw new IOException("Exception while deserilaizing.", e);
      }
    }
  }
}

@@ -1770,80 +1770,8 @@
   %7D%0A
-    Deflater deflater =  new Deflater(Deflater.BEST_COMPRESSION, true);%0A
     
@@ -1855,16 +1855,16 @@
 ) / 4);%0A
+
     comp
@@ -1930,16 +1930,88 @@
 SSION);%0A
+    Deflater deflater =  new Deflater(Deflater.BEST_COMPRESSION, true);%0A
     try 
@@ -2204,16 +2204,54 @@
 ize());%0A
+    %7D finally %7B%0A      deflater.end();%0A
     %7D%0A  
@@ -2688,16 +2688,22 @@
 %7D%0A      
+final 
 Inflater
@@ -2783,16 +2783,200 @@
 nflater)
+ %7B%0A        @Override public void close() throws IOException %7B%0A          try %7B%0A            super.close();%0A          %7D finally %7B%0A            inflater.end();%0A          %7D%0A        %7D%0A      %7D
 ;%0A    %7D%0A

call end on inflater/deflater upon closing the associated stream to avoid memory leak.