001/** 002 * 003 * Copyright 2013-2014 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.compression; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Method; 024import java.util.zip.Deflater; 025import java.util.zip.DeflaterOutputStream; 026import java.util.zip.Inflater; 027import java.util.zip.InflaterInputStream; 028 029/** 030 * This class provides XMPP "zlib" compression with the help of the Deflater class of the Java API. 031 * Note that the method needed for compression with synchronous flush support is available since 032 * Java7, so it will only work with Java7 or higher (hence it's name). On Android, the required 033 * <code>deflate()</code> method is available on API 19 or higher. 034 * <p> 035 * See also: 036 * <ul> 037 * <li><a href="http://docs.oracle.com/javase/7/docs/api/java/util/zip/Deflater.html#deflate(byte[],%20int,%20int,%20int)">The required deflate() method (Java7)</a> 038 * <li><a href="http://developer.android.com/reference/java/util/zip/Deflater.html#deflate(byte[],%20int,%20int,%20int)">The required deflate() method (Android)</a> 039 * </ul> 040 * 041 * @author Florian Schmaus 042 */ 043public class Java7ZlibInputOutputStream extends XMPPInputOutputStream { 044 private final static Method method; 045 private final static boolean supported; 046 private final static int compressionLevel = Deflater.DEFAULT_COMPRESSION; 047 048 private static final int SYNC_FLUSH_INT = 2; 049 private static final int FULL_FLUSH_INT = 3; 050 051 static { 052 Method m = null; 053 try { 054 m = Deflater.class.getMethod("deflate", byte[].class, int.class, int.class, int.class); 055 } catch (SecurityException e) { 056 } catch (NoSuchMethodException e) { 057 } 058 method = m; 059 supported = (method != null); 060 } 061 062 public Java7ZlibInputOutputStream() { 063 super("zlib"); 064 } 065 066 @Override 067 public boolean isSupported() { 068 return supported; 069 } 070 071 @Override 072 public InputStream getInputStream(InputStream inputStream) { 073 return new InflaterInputStream(inputStream, new Inflater(), 512) { 074 /** 075 * Provide a more InputStream compatible version. A return value of 1 means that it is likely to read one 076 * byte without blocking, 0 means that the system is known to block for more input. 077 * 078 * @return 0 if no data is available, 1 otherwise 079 * @throws IOException 080 */ 081 @Override 082 public int available() throws IOException { 083 /* 084 * aSmack related remark (where KXmlParser is used): 085 * This is one of the funny code blocks. InflaterInputStream.available violates the contract of 086 * InputStream.available, which breaks kXML2. 087 * 088 * I'm not sure who's to blame, oracle/sun for a broken api or the google guys for mixing a sun bug with 089 * a xml reader that can't handle it.... 090 * 091 * Anyway, this simple if breaks suns distorted reality, but helps to use the api as intended. 092 */ 093 if (inf.needsInput()) { 094 return 0; 095 } 096 return super.available(); 097 } 098 }; 099 } 100 101 @Override 102 public OutputStream getOutputStream(OutputStream outputStream) { 103 final int flushMethodInt; 104 if (flushMethod == FlushMethod.SYNC_FLUSH) { 105 flushMethodInt = SYNC_FLUSH_INT; 106 } else { 107 flushMethodInt = FULL_FLUSH_INT; 108 } 109 return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) { 110 @Override 111 public void flush() throws IOException { 112 if (!supported) { 113 super.flush(); 114 return; 115 } 116 try { 117 int count; 118 while ((count = (Integer) method.invoke(def, buf, 0, buf.length, flushMethodInt)) != 0) { 119 out.write(buf, 0, count); 120 } 121 } catch (IllegalArgumentException e) { 122 throw new IOException("Can't flush"); 123 } catch (IllegalAccessException e) { 124 throw new IOException("Can't flush"); 125 } catch (InvocationTargetException e) { 126 throw new IOException("Can't flush"); 127 } 128 super.flush(); 129 } 130 }; 131 } 132 133}