blob: f8c55dc665630c9ad9a1b94b1e299fb68a13fc23 [file] [log] [blame]
/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.java.util.jar.pack;
import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Date;
import java.util.jar.*;
import java.util.zip.*;
import java.util.*;
//import com.sun.java.util.jar.pack.Pack200;
class NativeUnpack {
// Pointer to the native unpacker obj
private long unpackerPtr;
// Input stream.
private BufferedInputStream in;
private static synchronized native void initIDs();
// Starts processing at the indicated position in the buffer.
// If the buffer is null, the readInputFn callback is used to get bytes.
// Returns (s<<32|f), the number of following segments and files.
private synchronized native long start(ByteBuffer buf, long offset);
// Returns true if there's another, and fills in the parts.
private synchronized native boolean getNextFile(Object[] parts);
private synchronized native ByteBuffer getUnusedInput();
// Resets the engine and frees all resources.
// Returns total number of bytes consumed by the engine.
private synchronized native long finish();
// Setting state in the unpacker.
protected synchronized native boolean setOption(String opt, String value);
protected synchronized native String getOption(String opt);
private int _verbose;
// State for progress bar:
private long _byteCount; // bytes read in current segment
private int _segCount; // number of segs scanned
private int _fileCount; // number of files written
private long _estByteLimit; // estimate of eventual total
private int _estSegLimit; // ditto
private int _estFileLimit; // ditto
private int _prevPercent = -1; // for monotonicity
private final CRC32 _crc32 = new CRC32();
private byte[] _buf = new byte[1<<14];
private UnpackerImpl _p200;
private PropMap _props;
static {
// If loading from stand alone build uncomment this.
// System.loadLibrary("unpack");
java.security.AccessController.doPrivileged(
new sun.security.action.LoadLibraryAction("unpack"));
initIDs();
}
NativeUnpack(UnpackerImpl p200) {
super();
_p200 = p200;
_props = p200._props;
p200._nunp = this;
}
// for JNI callbacks
static private Object currentInstance() {
UnpackerImpl p200 = (UnpackerImpl) Utils.currentInstance.get();
return (p200 == null)? null: p200._nunp;
}
// Callback from the unpacker engine to get more data.
private long readInputFn(ByteBuffer pbuf, long minlen) throws IOException {
if (in == null) return 0; // nothing is readable
long maxlen = pbuf.capacity() - pbuf.position();
assert(minlen <= maxlen); // don't talk nonsense
long numread = 0;
int steps = 0;
while (numread < minlen) {
steps++;
// read available input, up to buf.length or maxlen
int readlen = _buf.length;
if (readlen > (maxlen - numread))
readlen = (int)(maxlen - numread);
int nr = in.read(_buf, 0, readlen);
if (nr <= 0) break;
numread += nr;
assert(numread <= maxlen);
// %%% get rid of this extra copy by using nio?
pbuf.put(_buf, 0, nr);
}
if (_verbose > 1)
Utils.log.fine("readInputFn("+minlen+","+maxlen+") => "+numread+" steps="+steps);
if (maxlen > 100) {
_estByteLimit = _byteCount + maxlen;
} else {
_estByteLimit = (_byteCount + numread) * 20;
}
_byteCount += numread;
updateProgress();
return numread;
}
private void updateProgress() {
// Progress is a combination of segment reading and file writing.
final double READ_WT = 0.33;
final double WRITE_WT = 0.67;
double readProgress = _segCount;
if (_estByteLimit > 0 && _byteCount > 0)
readProgress += (double)_byteCount / _estByteLimit;
double writeProgress = _fileCount;
double scaledProgress
= READ_WT * readProgress / Math.max(_estSegLimit,1)
+ WRITE_WT * writeProgress / Math.max(_estFileLimit,1);
int percent = (int) Math.round(100*scaledProgress);
if (percent > 100) percent = 100;
if (percent > _prevPercent) {
_prevPercent = percent;
_props.setInteger(Pack200.Unpacker.PROGRESS, percent);
if (_verbose > 0)
Utils.log.info("progress = "+percent);
}
}
private void copyInOption(String opt) {
String val = _props.getProperty(opt);
if (_verbose > 0)
Utils.log.info("set "+opt+"="+val);
if (val != null) {
boolean set = setOption(opt, val);
if (!set)
Utils.log.warning("Invalid option "+opt+"="+val);
}
}
void run(InputStream inRaw, JarOutputStream jstream,
ByteBuffer presetInput) throws IOException {
BufferedInputStream in = new BufferedInputStream(inRaw);
this.in = in; // for readInputFn to see
_verbose = _props.getInteger(Utils.DEBUG_VERBOSE);
// Fix for BugId: 4902477, -unpack.modification.time = 1059010598000
// TODO eliminate and fix in unpack.cpp
final int modtime = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, "0")) ?
Constants.NO_MODTIME : _props.getTime(Utils.UNPACK_MODIFICATION_TIME);
copyInOption(Utils.DEBUG_VERBOSE);
copyInOption(Pack200.Unpacker.DEFLATE_HINT);
if (modtime == Constants.NO_MODTIME) // Dont pass KEEP && NOW
copyInOption(Utils.UNPACK_MODIFICATION_TIME);
updateProgress(); // reset progress bar
for (;;) {
// Read the packed bits.
long counts = start(presetInput, 0);
_byteCount = _estByteLimit = 0; // reset partial scan counts
++_segCount; // just finished scanning a whole segment...
int nextSeg = (int)( counts >>> 32 );
int nextFile = (int)( counts >>> 0 );
// Estimate eventual total number of segments and files.
_estSegLimit = _segCount + nextSeg;
double filesAfterThisSeg = _fileCount + nextFile;
_estFileLimit = (int)( (filesAfterThisSeg *
_estSegLimit) / _segCount );
// Write the files.
int[] intParts = { 0,0, 0, 0 };
// intParts = {size.hi/lo, mod, defl}
Object[] parts = { intParts, null, null, null };
// parts = { {intParts}, name, data0/1 }
while (getNextFile(parts)) {
//BandStructure.printArrayTo(System.out, intParts, 0, parts.length);
String name = (String) parts[1];
long size = ( (long)intParts[0] << 32)
+ (((long)intParts[1] << 32) >>> 32);
long mtime = (modtime != Constants.NO_MODTIME ) ?
modtime : intParts[2] ;
boolean deflateHint = (intParts[3] != 0);
ByteBuffer data0 = (ByteBuffer) parts[2];
ByteBuffer data1 = (ByteBuffer) parts[3];
writeEntry(jstream, name, mtime, size, deflateHint,
data0, data1);
++_fileCount;
updateProgress();
}
long consumed = finish();
if (_verbose > 0)
Utils.log.info("bytes consumed = "+consumed);
presetInput = getUnusedInput();
if (presetInput == null &&
!Utils.isPackMagic(Utils.readMagic(in))) {
break;
}
if (_verbose > 0 ) {
if (presetInput != null)
Utils.log.info("unused input = "+presetInput);
}
}
}
void run(InputStream in, JarOutputStream jstream) throws IOException {
run(in, jstream, null);
}
void run(File inFile, JarOutputStream jstream) throws IOException {
// %%% maybe memory-map the file, and pass it straight into unpacker
ByteBuffer mappedFile = null;
FileInputStream fis = new FileInputStream(inFile);
run(fis, jstream, mappedFile);
fis.close();
// Note: caller is responsible to finish with jstream.
}
private void writeEntry(JarOutputStream j, String name,
long mtime, long lsize, boolean deflateHint,
ByteBuffer data0, ByteBuffer data1) throws IOException {
int size = (int)lsize;
if (size != lsize)
throw new IOException("file too large: "+lsize);
CRC32 crc32 = _crc32;
if (_verbose > 1)
Utils.log.fine("Writing entry: "+name+" size="+size
+(deflateHint?" deflated":""));
if (_buf.length < size) {
int newSize = size;
while (newSize < _buf.length) {
newSize <<= 1;
if (newSize <= 0) {
newSize = size;
break;
}
}
_buf = new byte[newSize];
}
assert(_buf.length >= size);
int fillp = 0;
if (data0 != null) {
int size0 = data0.capacity();
data0.get(_buf, fillp, size0);
fillp += size0;
}
if (data1 != null) {
int size1 = data1.capacity();
data1.get(_buf, fillp, size1);
fillp += size1;
}
while (fillp < size) {
// Fill in rest of data from the stream itself.
int nr = in.read(_buf, fillp, size - fillp);
if (nr <= 0) throw new IOException("EOF at end of archive");
fillp += nr;
}
ZipEntry z = new ZipEntry(name);
z.setTime( (long)mtime * 1000);
if (size == 0) {
z.setMethod(ZipOutputStream.STORED);
z.setSize(0);
z.setCrc(0);
z.setCompressedSize(0);
} else if (!deflateHint) {
z.setMethod(ZipOutputStream.STORED);
z.setSize(size);
z.setCompressedSize(size);
crc32.reset();
crc32.update(_buf, 0, size);
z.setCrc(crc32.getValue());
} else {
z.setMethod(Deflater.DEFLATED);
z.setSize(size);
}
j.putNextEntry(z);
if (size > 0)
j.write(_buf, 0, size);
j.closeEntry();
if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z));
}
}