/*
* Copyright (c) 2011, 2012 Roberto Tyley
*
* This file is part of 'Agit' - an Android Git client.
*
* Agit is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Agit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ .
*/
package com.madgag.android.jgit;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import org.eclipse.jgit.lib.InflaterCache;
import org.eclipse.jgit.lib.InflaterFactory;
/**
* This class is a fix for two separate issues with Android Inflater support:
* <p/>
* Inflater and the Zero-Byte Killer (pre-HoneyComb)
* https://issues.apache.org/jira/browse/HARMONY-6637 - basically, Harmony JRE will not set finished()=true when you ask
* it to inflate zero bytes of data, even if you are looking at a zero-length stream or just happened to have already
* inflated exactly the amount of data you were already looking for. This causes problem for IndexPack.inflate() and
* other Inflater-users.
* <p/>
* InflaterInputStream and This Is Not The End for my Inflater
* https://github.com/rtyley/agit/issues/47 - Calling InflaterInputStream.close() on Oracle Java doesn't call end() on
* its Inflater if you supplied the Inflater in the constructor - but Android, incorrectly, *does*. Inflaters can't be
* used after end() has been called, which means you can't re-use Inflaters after using them with an InflaterInputStream
* on Android - which is precisely what JGit tries to do with InflaterCache. This results in either NullPointerException
* or IllegalStateException depending on what version of Android you're using.
*/
public class HarmonyFixInflater extends Inflater {
public static final String TAG = "HFI";
public static final InflaterFactory HARMONY_FIX_FACTORY = new InflaterFactory() {
public Inflater create() {
return new HarmonyFixInflater();
}
@Override
public void decommision(Inflater inflater) {
((HarmonyFixInflater) inflater).decommision();
}
};
public static void establishHarmoniousRepose() {
InflaterCache.INFLATER_FACTORY = HARMONY_FIX_FACTORY;
}
public static boolean checkHarmoniousRepose() {
Inflater inflater = new Inflater();
try {
inflater.setInput(demoDeflatedZeroBytes());
inflater.inflate(new byte[0], 0, 0);
} catch (Exception e) {
throw new RuntimeException(e);
}
return inflater.finished();
}
private static byte[] demoDeflatedZeroBytes() throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream);
deflaterOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
private static final byte[] oneByteArray = new byte[1];
private HarmonyFixInflater() {
super(false);
}
public void setInput(byte[] b, int off, int len) {
super.setInput(b, off, len);
}
public void setInput(byte[] b) {
super.setInput(b);
}
public void setDictionary(byte[] b, int off, int len) {
super.setDictionary(b, off, len);
}
public void setDictionary(byte[] b) {
super.setDictionary(b);
}
public int getRemaining() {
return super.getRemaining();
}
public boolean needsInput() {
return super.needsInput();
}
public boolean needsDictionary() {
return super.needsDictionary();
}
public boolean finished() {
return super.finished();
}
public int inflate(byte[] b, int off, int len) throws DataFormatException {
if (len != 0) {
return super.inflate(b, off, len);
}
int bytesInflated = super.inflate(oneByteArray, 0, 1); // have to pretend to want at least one byte so that
// the finished flag is correctly set
if (bytesInflated > 0) {
throw new RuntimeException("The Harmony-Fix hack has served you ill, we were not supposed to read any " +
"data...");
}
return 0;
}
public int inflate(byte[] b) throws DataFormatException {
return super.inflate(b);
}
public int getAdler() {
return super.getAdler();
}
public int getTotalIn() {
return super.getTotalIn();
}
public long getBytesRead() {
return super.getBytesRead();
}
public int getTotalOut() {
return super.getTotalOut();
}
public long getBytesWritten() {
return super.getBytesWritten();
}
public void reset() {
//Log.d(TAG,this+" - reset()");
// Thread.dumpStack();
super.reset();
}
public void end() {
// Log.d(TAG, this + " - end() called, will ignore");
// Thread.dumpStack();
// DO NOT call end method on wrapped inflater, because the InflaterCache will want to re-use it
}
private void decommision() {
Log.d(TAG, this + " - decommision(). See https://github.com/rtyley/agit/issues/47");
super.end();
}
}