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 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 compressionMethod = "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 return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) { 104 public void flush() throws IOException { 105 if (!supported) { 106 super.flush(); 107 return; 108 } 109 int count = 0; 110 if (!def.needsInput()) { 111 do { 112 count = def.deflate(buf, 0, buf.length); 113 out.write(buf, 0, count); 114 } while (count > 0); 115 out.flush(); 116 } 117 try { 118 do { 119 count = (Integer) method.invoke(def, buf, 0, buf.length, 2); 120 out.write(buf, 0, count); 121 } while (count > 0); 122 } catch (IllegalArgumentException e) { 123 throw new IOException("Can't flush"); 124 } catch (IllegalAccessException e) { 125 throw new IOException("Can't flush"); 126 } catch (InvocationTargetException e) { 127 throw new IOException("Can't flush"); 128 } 129 super.flush(); 130 } 131 }; 132 } 133 134}