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= 038 * "http://docs.oracle.com/javase/7/docs/api/java/util/zip/Deflater.html#deflate(byte[], int, int, int)" 039 * >The required deflate() method (Java7)</a> 040 * <li><a href= 041 * "http://developer.android.com/reference/java/util/zip/Deflater.html#deflate(byte[], int, int, int)" 042 * >The required deflate() method (Android)</a> 043 * 044 * @author Florian Schmaus 045 */ 046public class Java7ZlibInputOutputStream extends XMPPInputOutputStream { 047 private final static Method method; 048 private final static boolean supported; 049 private final static int compressionLevel = Deflater.DEFAULT_COMPRESSION; 050 051 private static final int SYNC_FLUSH_INT = 2; 052 private static final int FULL_FLUSH_INT = 3; 053 054 static { 055 Method m = null; 056 try { 057 m = Deflater.class.getMethod("deflate", byte[].class, int.class, int.class, int.class); 058 } catch (SecurityException e) { 059 } catch (NoSuchMethodException e) { 060 } 061 method = m; 062 supported = (method != null); 063 } 064 065 public Java7ZlibInputOutputStream() { 066 compressionMethod = "zlib"; 067 } 068 069 @Override 070 public boolean isSupported() { 071 return supported; 072 } 073 074 @Override 075 public InputStream getInputStream(InputStream inputStream) { 076 return new InflaterInputStream(inputStream, new Inflater(), 512) { 077 /** 078 * Provide a more InputStream compatible version. A return value of 1 means that it is likely to read one 079 * byte without blocking, 0 means that the system is known to block for more input. 080 * 081 * @return 0 if no data is available, 1 otherwise 082 * @throws IOException 083 */ 084 @Override 085 public int available() throws IOException { 086 /* 087 * aSmack related remark (where KXmlParser is used): 088 * This is one of the funny code blocks. InflaterInputStream.available violates the contract of 089 * InputStream.available, which breaks kXML2. 090 * 091 * I'm not sure who's to blame, oracle/sun for a broken api or the google guys for mixing a sun bug with 092 * a xml reader that can't handle it.... 093 * 094 * Anyway, this simple if breaks suns distorted reality, but helps to use the api as intended. 095 */ 096 if (inf.needsInput()) { 097 return 0; 098 } 099 return super.available(); 100 } 101 }; 102 } 103 104 @Override 105 public OutputStream getOutputStream(OutputStream outputStream) { 106 final int flushMethodInt; 107 if (flushMethod == FlushMethod.SYNC_FLUSH) { 108 flushMethodInt = SYNC_FLUSH_INT; 109 } else { 110 flushMethodInt = FULL_FLUSH_INT; 111 } 112 return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) { 113 public void flush() throws IOException { 114 if (!supported) { 115 super.flush(); 116 return; 117 } 118 try { 119 int count; 120 while ((count = (Integer) method.invoke(def, buf, 0, buf.length, flushMethodInt)) != 0) { 121 out.write(buf, 0, count); 122 } 123 } catch (IllegalArgumentException e) { 124 throw new IOException("Can't flush"); 125 } catch (IllegalAccessException e) { 126 throw new IOException("Can't flush"); 127 } catch (InvocationTargetException e) { 128 throw new IOException("Can't flush"); 129 } 130 super.flush(); 131 } 132 }; 133 } 134 135}