Java7ZlibInputOutputStream.java

  1. /**
  2.  *
  3.  * Copyright 2013-2014 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.compression;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.lang.reflect.InvocationTargetException;
  22. import java.lang.reflect.Method;
  23. import java.util.zip.Deflater;
  24. import java.util.zip.DeflaterOutputStream;
  25. import java.util.zip.Inflater;
  26. import java.util.zip.InflaterInputStream;

  27. /**
  28.  * This class provides XMPP "zlib" compression with the help of the Deflater class of the Java API.
  29.  * Note that the method needed for compression with synchronous flush support is available since
  30.  * Java7, so it will only work with Java7 or higher (hence it's name). On Android, the required
  31.  * <code>deflate()</code> method is available on API 19 or higher.
  32.  * <p>
  33.  * See also:
  34.  * <ul>
  35.  * <li><a href=
  36.  * "http://docs.oracle.com/javase/7/docs/api/java/util/zip/Deflater.html#deflate(byte[], int, int, int)"
  37.  * >The required deflate() method (Java7)</a>
  38.  * <li><a href=
  39.  * "http://developer.android.com/reference/java/util/zip/Deflater.html#deflate(byte[], int, int, int)"
  40.  * >The required deflate() method (Android)</a>
  41.  *
  42.  * @author Florian Schmaus
  43.  */
  44. public class Java7ZlibInputOutputStream extends XMPPInputOutputStream {
  45.     private final static Method method;
  46.     private final static boolean supported;
  47.     private final static int compressionLevel = Deflater.DEFAULT_COMPRESSION;

  48.     private static final int SYNC_FLUSH_INT = 2;
  49.     private static final int FULL_FLUSH_INT = 3;

  50.     static {
  51.         Method m = null;
  52.         try {
  53.             m = Deflater.class.getMethod("deflate", byte[].class, int.class, int.class, int.class);
  54.         } catch (SecurityException e) {
  55.         } catch (NoSuchMethodException e) {
  56.         }
  57.         method = m;
  58.         supported = (method != null);
  59.     }

  60.     public Java7ZlibInputOutputStream() {
  61.         super("zlib");
  62.     }

  63.     @Override
  64.     public boolean isSupported() {
  65.         return supported;
  66.     }

  67.     @Override
  68.     public InputStream getInputStream(InputStream inputStream) {
  69.         return new InflaterInputStream(inputStream, new Inflater(), 512) {
  70.             /**
  71.              * Provide a more InputStream compatible version. A return value of 1 means that it is likely to read one
  72.              * byte without blocking, 0 means that the system is known to block for more input.
  73.              *
  74.              * @return 0 if no data is available, 1 otherwise
  75.              * @throws IOException
  76.              */
  77.             @Override
  78.             public int available() throws IOException {
  79.                 /*
  80.                  * aSmack related remark (where KXmlParser is used):
  81.                  * This is one of the funny code blocks. InflaterInputStream.available violates the contract of
  82.                  * InputStream.available, which breaks kXML2.
  83.                  *
  84.                  * I'm not sure who's to blame, oracle/sun for a broken api or the google guys for mixing a sun bug with
  85.                  * a xml reader that can't handle it....
  86.                  *
  87.                  * Anyway, this simple if breaks suns distorted reality, but helps to use the api as intended.
  88.                  */
  89.                 if (inf.needsInput()) {
  90.                     return 0;
  91.                 }
  92.                 return super.available();
  93.             }
  94.         };
  95.     }

  96.     @Override
  97.     public OutputStream getOutputStream(OutputStream outputStream) {
  98.         final int flushMethodInt;
  99.         if (flushMethod == FlushMethod.SYNC_FLUSH) {
  100.             flushMethodInt = SYNC_FLUSH_INT;
  101.         } else {
  102.             flushMethodInt = FULL_FLUSH_INT;
  103.         }
  104.         return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) {
  105.             @Override
  106.             public void flush() throws IOException {
  107.                 if (!supported) {
  108.                     super.flush();
  109.                     return;
  110.                 }
  111.                 try {
  112.                     int count;
  113.                     while ((count = (Integer) method.invoke(def, buf, 0, buf.length, flushMethodInt)) != 0) {
  114.                         out.write(buf, 0, count);
  115.                     }
  116.                 } catch (IllegalArgumentException e) {
  117.                     throw new IOException("Can't flush");
  118.                 } catch (IllegalAccessException e) {
  119.                     throw new IOException("Can't flush");
  120.                 } catch (InvocationTargetException e) {
  121.                     throw new IOException("Can't flush");
  122.                 }
  123.                 super.flush();
  124.             }
  125.         };
  126.     }

  127. }