package zip;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.util.zip.*;

/**
 * @author Peter Büttner <Peter.Buettner@gmx.net>
 * @version 15.04.2003 16:45
 */
public class ZipPatch extends ZipFile{
//	access  private class ZipFileInputStream {
	protected Hashtable entries;

	protected Field fldJzfile;
	protected Method getNextEntry;
	protected Method freeEntry;

	protected Constructor conZipFileInputStream;
	protected Constructor conZipEntry;

public ZipPatch(String name) throws IOException, ZipPatchException  {this(new File(name), OPEN_READ);}
public ZipPatch(File file) throws IOException, ZipPatchException  {this(file, OPEN_READ);}


public ZipPatch(File file, int mode) throws IOException, ZipPatchException {
	super(file,mode);

	// ------------------ first get access -----------------------------
	try{
//		private long jzfile
		fldJzfile = ZipFile.class.getDeclaredField("jzfile");
		if (fldJzfile!=null) fldJzfile.setAccessible(true);

//		private static native long getNextEntry(long jzfile, int i)
		getNextEntry = ZipFile.class.getDeclaredMethod("getNextEntry", new Class[] {Long.TYPE, Integer.TYPE});
		if (getNextEntry!=null) getNextEntry.setAccessible(true);

//		private static native void freeEntry(long jzfile, long jzentry);
		try{freeEntry= ZipFile.class.getDeclaredMethod("freeEntry", new Class[] {Long.TYPE, Long.TYPE});}
		catch(NoSuchMethodException e){}//eat! 1.2 doesn't have this
		if(freeEntry!=null)freeEntry.setAccessible(true);

		conZipFileInputStream = Class.forName("java.util.zip.ZipFile$ZipFileInputStream").getDeclaredConstructor(
										new Class[]{ZipFile.class, Long.TYPE, Long.TYPE, ZipFile.class}
										);
		if(conZipFileInputStream!=null)conZipFileInputStream.setAccessible(true);

		conZipEntry = ZipEntry.class.getDeclaredConstructor(new Class[]{Long.TYPE});
		if(conZipEntry!=null)conZipEntry.setAccessible(true);

	// ----------- now read in the zip contens into a hash: -------------
		int count = size();
		Long jzfile = getJzfile();
		entries = new Hashtable(count, 1f);
		for (int i=0;i<count;i++){
			Long jzentry = getNextEntry(jzfile,i);
			if (jzentry==null) throw new InternalError("jzentry == null");

			ZipEntry ze = newZipEntry(jzentry);
			entries.put(ze.getName(), new Integer(i));
			freeEntry(jzfile, jzentry);
			}
		}
	// ----------- catch all, Patch doesn't work, security? --------------------
	catch(NoSuchFieldException e){ throw new ZipPatchException();}
	catch(NoSuchMethodException e){throw new ZipPatchException();}
	catch(ClassNotFoundException e){throw new ZipPatchException();}
	catch(IllegalAccessException e){throw new ZipPatchException();}
	catch(InvocationTargetException e){throw new ZipPatchException();}
	catch(InstantiationException e) {throw new ZipPatchException();}
}// ----------------------------------------------------------------------------

public ZipEntry getEntry(String name){
	Long jzentry=null;
	ZipEntry zipEntry=null;

	try {jzentry = getJzentry(name);}
	catch (Exception e) {e.printStackTrace();}

	if (jzentry == null) return null;

	try {zipEntry = newZipEntry(jzentry);}
	catch (Exception e) {e.printStackTrace();}

	try {if (jzentry!=null)freeEntry(getJzfile(), jzentry);}
	catch (Exception e) {e.printStackTrace();}

	return zipEntry;
}// ---------------------------------------------

public InputStream getInputStream(ZipEntry entry) throws IOException {
	//don't do freeEntry(getJzfile(), jzentry); 'cause ZipFileInputStream frees it!
	try{
		Long jzentry = getJzentry( entry.getName());
		if (getJzfile()==null || jzentry==null) return null;

		switch (entry.getMethod()) {
			case ZipEntry.STORED: return newZipFileInputStream(jzentry);
			case ZipEntry.DEFLATED: return new PKZipInputStream (newZipFileInputStream(jzentry));
			default:throw new ZipException("invalid compression method");
			}
		}
	catch(IllegalAccessException e){throw new IOException("Could not patch ZipFile.");}
	catch(InvocationTargetException e){throw new IOException("Could not patch ZipFile.");}
	catch (InstantiationException e) {throw new IOException("Could not patch ZipFile.");}
}// ---------------------------------------------

protected Long getJzentry(String name) throws IllegalAccessException, InvocationTargetException{
	Integer index = (Integer)entries.get(name);
	if (index==null) return null;
	Long jzentry = getNextEntry( getJzfile(), index.intValue());
	return jzentry;
}// --------------------------------------------------------

// accessed members:
protected Long getNextEntry( Long jzfile,int index) throws IllegalAccessException, InvocationTargetException{
	return (Long)getNextEntry.invoke(null,new Object[]{jzfile, new Integer(index)});
}// ---------------------------------------------------
protected void freeEntry( Long jzfile, Long jzentry) throws IllegalAccessException, InvocationTargetException{
	if(freeEntry!=null)freeEntry.invoke(null, new Object[]{ jzfile, jzentry});
}// ---------------------------------------------------
protected ZipEntry newZipEntry(Long jzentry) throws InstantiationException, IllegalAccessException, InvocationTargetException{
	return (ZipEntry)conZipEntry.newInstance(new Object[]{jzentry});
}// ---------------------------------------------------
protected InputStream newZipFileInputStream(Long jzentry) throws InstantiationException, IllegalAccessException, InvocationTargetException{
//	new ZipFile.ZipFileInputStream(long jzfile, long jzentry, ZipFile zf)
	return (InputStream)conZipFileInputStream.newInstance(new Object[] {this,getJzfile(),jzentry,this});
}// ---------------------------------------------------
protected Long getJzfile() throws IllegalAccessException {return (Long)fldJzfile.get(this);}
//---------------------------------------------------

class PKZipInputStream extends InflaterInputStream{
// provide an extra "dummy" byte at the end of the input stream (->fill).
// see comment at Inflater(boolean nowrap) (difference between GZIP and PKZIP)
// PKZIP needs nowrap==true

public PKZipInputStream(InputStream in){super(in, getInflater());}
private boolean isClosed = false;
public void close() throws IOException {
	if (!isClosed) {
		releaseInflater(inf);
		this.in.close();
		isClosed = true;
		}
	}

private boolean eof;
protected void fill() throws IOException {
	if (eof) throw new EOFException("Unexpected end of ZLIB input stream");
	len = this.in.read(buf, 0, buf.length);
	if (len == -1) {buf[0] = 0;len = 1;eof = true;}// dummy byte
	inf.setInput(buf, 0, len);
	}

public int available()throws IOException{return (super.available()==0)? 0 : this.in.available();}
}// ------------------- PKZipInputStream ---------------------------------

// Pool for inflaters, most times 1 is used
private ArrayList inflaters = new ArrayList();
private Inflater getInflater() {
	synchronized (inflaters) {
		if (inflaters.size() == 0) return new Inflater(true);
			Inflater inf = (Inflater)inflaters.remove(inflaters.size()-1);
			inf.reset();
			return inf;
			}
	}
private void releaseInflater(Inflater inf) {synchronized (inflaters) {inflaters.add(inf);}}
}

class ZipPatchException extends Exception{}

