From 2fb849502f57ec29e9913907872183af06a57b3e Mon Sep 17 00:00:00 2001
From: Joshua Hartman <jhartman@linkedin.com>
Date: Sun, 5 Dec 2010 21:20:15 -0800
Subject: [PATCH] Replacing the native lzf compression code with the ning
 open-source compress-lzf library. (Apache 2.0 liscense)

---
 Makefile                                      |  18 +-
 run                                           |   1 +
 src/java/spark/compress/lzf/LZF.java          |  27 ---
 .../spark/compress/lzf/LZFInputStream.java    | 180 ------------------
 .../spark/compress/lzf/LZFOutputStream.java   |  85 ---------
 src/native/Makefile                           |  30 ---
 src/native/spark_compress_lzf_LZF.c           |  90 ---------
 src/scala/spark/Broadcast.scala               |   2 +-
 .../compress-lzf-0.6.0/compress-lzf-0.6.0.jar | Bin 0 -> 14497 bytes
 9 files changed, 8 insertions(+), 425 deletions(-)
 delete mode 100644 src/java/spark/compress/lzf/LZF.java
 delete mode 100644 src/java/spark/compress/lzf/LZFInputStream.java
 delete mode 100644 src/java/spark/compress/lzf/LZFOutputStream.java
 delete mode 100644 src/native/Makefile
 delete mode 100644 src/native/spark_compress_lzf_LZF.c
 create mode 100644 third_party/compress-lzf-0.6.0/compress-lzf-0.6.0.jar

diff --git a/Makefile b/Makefile
index 15ab516d1f..762d752fda 100644
--- a/Makefile
+++ b/Makefile
@@ -15,12 +15,13 @@ JARS += third_party/jetty-7.1.6.v20100715/servlet-api-2.5.jar
 JARS += third_party/apache-log4j-1.2.16/log4j-1.2.16.jar
 JARS += third_party/slf4j-1.6.1/slf4j-api-1.6.1.jar
 JARS += third_party/slf4j-1.6.1/slf4j-log4j12-1.6.1.jar
+JARS += third_party/compress-lzf-0.6.0/compress-lzf-0.6.0.jar
+
 CLASSPATH = $(subst $(SPACE),:,$(JARS))
 
 SCALA_SOURCES =  src/examples/*.scala src/scala/spark/*.scala src/scala/spark/repl/*.scala
 SCALA_SOURCES += src/test/spark/*.scala src/test/spark/repl/*.scala
 
-JAVA_SOURCES = $(wildcard src/java/spark/compress/lzf/*.java)
 
 ifeq ($(USE_FSC),1)
   COMPILER_NAME = fsc
@@ -36,25 +37,19 @@ endif
 
 CONF_FILES = conf/spark-env.sh conf/log4j.properties conf/java-opts
 
-all: scala java conf-files
+all: scala conf-files
 
 build/classes:
 	mkdir -p build/classes
 
-scala: build/classes java
+scala: build/classes
 	$(COMPILER) -d build/classes -classpath build/classes:$(CLASSPATH) $(SCALA_SOURCES)
 
-java: $(JAVA_SOURCES) build/classes
-	javac -d build/classes $(JAVA_SOURCES)
-
-native: java
-	$(MAKE) -C src/native
-
 jar: build/spark.jar build/spark-dep.jar
 
 dep-jar: build/spark-dep.jar
 
-build/spark.jar: scala java
+build/spark.jar: scala
 	jar cf build/spark.jar -C build/classes spark
 
 build/spark-dep.jar:
@@ -73,7 +68,6 @@ test: all
 default: all
 
 clean:
-	$(MAKE) -C src/native clean
 	rm -rf build
 
-.phony: default all clean scala java native jar dep-jar conf-files
+.phony: default all clean scala jar dep-jar conf-files
diff --git a/run b/run
index d6f7d920c5..5c8943c91b 100755
--- a/run
+++ b/run
@@ -48,6 +48,7 @@ CLASSPATH+=:$FWDIR/third_party/jetty-7.1.6.v20100715/servlet-api-2.5.jar
 CLASSPATH+=:$FWDIR/third_party/apache-log4j-1.2.16/log4j-1.2.16.jar
 CLASSPATH+=:$FWDIR/third_party/slf4j-1.6.1/slf4j-api-1.6.1.jar
 CLASSPATH+=:$FWDIR/third_party/slf4j-1.6.1/slf4j-log4j12-1.6.1.jar
+CLASSPATH+=:$FWDIR/third_party/compress-lzf-0.6.0/compress-lzf-0.6.0.jar
 for jar in $FWDIR/third_party/hadoop-0.20.0/lib/*.jar; do
   CLASSPATH+=:$jar
 done
diff --git a/src/java/spark/compress/lzf/LZF.java b/src/java/spark/compress/lzf/LZF.java
deleted file mode 100644
index 294a0494ec..0000000000
--- a/src/java/spark/compress/lzf/LZF.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package spark.compress.lzf;
-
-public class LZF {
-  private static boolean loaded;
-
-  static {
-    try {
-      System.loadLibrary("spark_native");
-      loaded = true;
-    } catch(Throwable t) {
-      System.out.println("Failed to load native LZF library: " + t.toString());
-      loaded = false;
-    }
-  }
-
-  public static boolean isLoaded() {
-    return loaded;
-  }
-
-  public static native int compress(
-      byte[] in, int inOff, int inLen,
-      byte[] out, int outOff, int outLen);
-
-  public static native int decompress(
-      byte[] in, int inOff, int inLen,
-      byte[] out, int outOff, int outLen);
-}
diff --git a/src/java/spark/compress/lzf/LZFInputStream.java b/src/java/spark/compress/lzf/LZFInputStream.java
deleted file mode 100644
index 16bc687489..0000000000
--- a/src/java/spark/compress/lzf/LZFInputStream.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package spark.compress.lzf;
-
-import java.io.EOFException;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class LZFInputStream extends FilterInputStream {
-  private static final int MAX_BLOCKSIZE = 1024 * 64 - 1;
-  private static final int MAX_HDR_SIZE = 7;
-
-  private byte[] inBuf;     // Holds data to decompress (including header)
-  private byte[] outBuf;    // Holds decompressed data to output
-  private int outPos;       // Current position in outBuf
-  private int outSize;      // Total amount of data in outBuf
-
-  private boolean closed;
-  private boolean reachedEof;
-
-  private byte[] singleByte = new byte[1];
-
-  public LZFInputStream(InputStream in) {
-    super(in);
-    if (in == null)
-      throw new NullPointerException();
-    inBuf = new byte[MAX_BLOCKSIZE + MAX_HDR_SIZE];
-    outBuf = new byte[MAX_BLOCKSIZE + MAX_HDR_SIZE];
-    outPos = 0;
-    outSize = 0;
-  }
-
-  private void ensureOpen() throws IOException {
-    if (closed) throw new IOException("Stream closed");
-  }
-
-  @Override
-  public int read() throws IOException {
-    ensureOpen();
-    int count = read(singleByte, 0, 1);
-    return (count == -1 ? -1 : singleByte[0] & 0xFF);
-  }
-
-  @Override
-  public int read(byte[] b, int off, int len) throws IOException {
-    ensureOpen();
-    if ((off | len | (off + len) | (b.length - (off + len))) < 0)
-      throw new IndexOutOfBoundsException();
-
-    int totalRead = 0;
-
-    // Start with the current block in outBuf, and read and decompress any
-    // further blocks necessary. Instead of trying to decompress directly to b
-    // when b is large, we always use outBuf as an intermediate holding space
-    // in case GetPrimitiveArrayCritical decides to copy arrays instead of
-    // pinning them, which would cause b to be copied repeatedly into C-land.
-    while (len > 0) {
-      if (outPos == outSize) {
-        readNextBlock();
-        if (reachedEof)
-          return totalRead == 0 ? -1 : totalRead;
-      }
-      int amtToCopy = Math.min(outSize - outPos, len);
-      System.arraycopy(outBuf, outPos, b, off, amtToCopy);
-      off += amtToCopy;
-      len -= amtToCopy;
-      outPos += amtToCopy;
-      totalRead += amtToCopy;
-    }
-
-    return totalRead;
-  }
-
-  // Read len bytes from this.in to a buffer, stopping only if EOF is reached
-  private int readFully(byte[] b, int off, int len) throws IOException {
-    int totalRead = 0;
-    while (len > 0) {
-      int amt = in.read(b, off, len);
-      if (amt == -1)
-        break;
-      off += amt;
-      len -= amt;
-      totalRead += amt;
-    }
-    return totalRead;
-  }
-
-  // Read the next block from the underlying InputStream into outBuf,
-  // setting outPos and outSize, or set reachedEof if the stream ends.
-  private void readNextBlock() throws IOException {
-    // Read first 5 bytes of header
-    int count = readFully(inBuf, 0, 5);
-    if (count == 0) {
-      reachedEof = true;
-      return;
-    } else if (count < 5) {
-      throw new EOFException("Truncated LZF block header");
-    }
-
-    // Check magic bytes
-    if (inBuf[0] != 'Z' || inBuf[1] != 'V')
-      throw new IOException("Wrong magic bytes in LZF block header");
-
-    // Read the block
-    if (inBuf[2] == 0) {
-      // Uncompressed block - read directly to outBuf
-      int size = ((inBuf[3] & 0xFF) << 8) | (inBuf[4] & 0xFF);
-      if (readFully(outBuf, 0, size) != size)
-        throw new EOFException("EOF inside LZF block");
-      outPos = 0;
-      outSize = size;
-    } else if (inBuf[2] == 1) {
-      // Compressed block - read to inBuf and decompress
-      if (readFully(inBuf, 5, 2) != 2)
-        throw new EOFException("Truncated LZF block header");
-      int csize = ((inBuf[3] & 0xFF) << 8) | (inBuf[4] & 0xFF);
-      int usize = ((inBuf[5] & 0xFF) << 8) | (inBuf[6] & 0xFF);
-      if (readFully(inBuf, 7, csize) != csize)
-        throw new EOFException("Truncated LZF block");
-      if (LZF.decompress(inBuf, 7, csize, outBuf, 0, usize) != usize)
-        throw new IOException("Corrupt LZF data stream");
-      outPos = 0;
-      outSize = usize;
-    } else {
-      throw new IOException("Unknown block type in LZF block header");
-    }
-  }
-
-  /**
-   * Returns 0 after EOF has been reached, otherwise always return 1.
-   *
-   * Programs should not count on this method to return the actual number
-   * of bytes that could be read without blocking.
-   */
-  @Override
-  public int available() throws IOException {
-    ensureOpen();
-    return reachedEof ? 0 : 1;
-  }
-
-  // TODO: Skip complete chunks without decompressing them?
-  @Override
-  public long skip(long n) throws IOException {
-    ensureOpen();
-    if (n < 0)
-      throw new IllegalArgumentException("negative skip length");
-    byte[] buf = new byte[512];
-    long skipped = 0;
-    while (skipped < n) {
-      int len = (int) Math.min(n - skipped, buf.length);
-      len = read(buf, 0, len);
-      if (len == -1) {
-        reachedEof = true;
-        break;
-      }
-      skipped += len;
-    }
-    return skipped;
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (!closed) {
-      in.close();
-      closed = true;
-    }
-  }
-
-  @Override
-  public boolean markSupported() {
-    return false;
-  }
-
-  @Override
-  public void mark(int readLimit) {}
-
-  @Override
-  public void reset() throws IOException {
-    throw new IOException("mark/reset not supported");
-  }
-}
diff --git a/src/java/spark/compress/lzf/LZFOutputStream.java b/src/java/spark/compress/lzf/LZFOutputStream.java
deleted file mode 100644
index 5f65e95d2a..0000000000
--- a/src/java/spark/compress/lzf/LZFOutputStream.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package spark.compress.lzf;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class LZFOutputStream extends FilterOutputStream {
-  private static final int BLOCKSIZE = 1024 * 64 - 1;
-  private static final int MAX_HDR_SIZE = 7;
-
-  private byte[] inBuf;   // Holds input data to be compressed
-  private byte[] outBuf;  // Holds compressed data to be written
-  private int inPos;      // Current position in inBuf
-
-  public LZFOutputStream(OutputStream out) {
-    super(out);
-    inBuf = new byte[BLOCKSIZE + MAX_HDR_SIZE];
-    outBuf = new byte[BLOCKSIZE + MAX_HDR_SIZE];
-    inPos = MAX_HDR_SIZE;
-  }
-
-  @Override
-  public void write(int b) throws IOException {
-    inBuf[inPos++] = (byte) b;
-    if (inPos == inBuf.length)
-      compressAndSendBlock();
-  }
-  
-  @Override
-  public void write(byte[] b, int off, int len) throws IOException {
-    if ((off | len | (off + len) | (b.length - (off + len))) < 0)
-      throw new IndexOutOfBoundsException();
-
-    // If we're given a large array, copy it piece by piece into inBuf and
-    // write one BLOCKSIZE at a time. This is done to prevent the JNI code
-    // from copying the whole array repeatedly if GetPrimitiveArrayCritical
-    // decides to copy instead of pinning.
-    while (inPos + len >= inBuf.length) {
-      int amtToCopy = inBuf.length - inPos;
-      System.arraycopy(b, off, inBuf, inPos, amtToCopy);
-      inPos += amtToCopy;
-      compressAndSendBlock();
-      off += amtToCopy;
-      len -= amtToCopy;
-    }
-
-    // Copy the remaining (incomplete) block into inBuf
-    System.arraycopy(b, off, inBuf, inPos, len);
-    inPos += len;
-  }
-
-  @Override
-  public void flush() throws IOException {
-    if (inPos > MAX_HDR_SIZE)
-      compressAndSendBlock();
-    out.flush();
-  }
-
-  // Send the data in inBuf, and reset inPos to start writing a new block.
-  private void compressAndSendBlock() throws IOException {
-    int us = inPos - MAX_HDR_SIZE;
-    int maxcs = us > 4 ? us - 4 : us;
-    int cs = LZF.compress(inBuf, MAX_HDR_SIZE, us, outBuf, MAX_HDR_SIZE, maxcs);
-    if (cs != 0) {
-      // Compression made the data smaller; use type 1 header
-      outBuf[0] = 'Z';
-      outBuf[1] = 'V';
-      outBuf[2] = 1;
-      outBuf[3] = (byte) (cs >> 8);
-      outBuf[4] = (byte) (cs & 0xFF);
-      outBuf[5] = (byte) (us >> 8);
-      outBuf[6] = (byte) (us & 0xFF);
-      out.write(outBuf, 0, 7 + cs);
-    } else {
-      // Compression didn't help; use type 0 header and uncompressed data
-      inBuf[2] = 'Z';
-      inBuf[3] = 'V';
-      inBuf[4] = 0;
-      inBuf[5] = (byte) (us >> 8);
-      inBuf[6] = (byte) (us & 0xFF);
-      out.write(inBuf, 2, 5 + us);
-    }
-    inPos = MAX_HDR_SIZE;
-  }
-}
diff --git a/src/native/Makefile b/src/native/Makefile
deleted file mode 100644
index 6236e26f3d..0000000000
--- a/src/native/Makefile
+++ /dev/null
@@ -1,30 +0,0 @@
-CC = gcc
-#JAVA_HOME = /usr/lib/jvm/java-6-sun
-OS_NAME = linux
-
-CFLAGS = -fPIC -O3 -funroll-all-loops
-
-SPARK = ../..
-
-LZF = $(SPARK)/third_party/liblzf-3.5
-
-LIB = libspark_native.so
-
-all: $(LIB)
-
-spark_compress_lzf_LZF.h: $(SPARK)/build/classes/spark/compress/lzf/LZF.class
-ifeq ($(JAVA_HOME),)
-	$(error JAVA_HOME is not set)
-else
-	$(JAVA_HOME)/bin/javah -classpath $(SPARK)/build/classes spark.compress.lzf.LZF
-endif
-
-$(LIB): spark_compress_lzf_LZF.h spark_compress_lzf_LZF.c
-	$(CC) $(CFLAGS) -shared -o $@ spark_compress_lzf_LZF.c \
-	      -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/$(OS_NAME) \
-	      -I $(LZF) $(LZF)/lzf_c.c $(LZF)/lzf_d.c
-
-clean:
-	rm -f spark_compress_lzf_LZF.h $(LIB)
-
-.PHONY: all clean
diff --git a/src/native/spark_compress_lzf_LZF.c b/src/native/spark_compress_lzf_LZF.c
deleted file mode 100644
index c2a59def3e..0000000000
--- a/src/native/spark_compress_lzf_LZF.c
+++ /dev/null
@@ -1,90 +0,0 @@
-#include "spark_compress_lzf_LZF.h"
-#include <lzf.h>
-
-
-/* Helper function to throw an exception */
-static void throwException(JNIEnv *env, const char* className) {
-  jclass cls = (*env)->FindClass(env, className);
-  if (cls != 0) /* If cls is null, an exception was already thrown */
-    (*env)->ThrowNew(env, cls, "");
-}
-
-
-/* 
- * Since LZF.compress() and LZF.decompress() have the same signatures
- * and differ only in which lzf_ function they call, implement both in a
- * single function and pass it a pointer to the correct lzf_ function.
- */
-static jint callCompressionFunction
-  (unsigned int (*func)(const void *const, unsigned int, void *, unsigned int),
-   JNIEnv *env, jclass cls, jbyteArray inArray, jint inOff, jint inLen,
-   jbyteArray outArray, jint outOff, jint outLen)
-{
-  jint inCap;
-  jint outCap;
-  jbyte *inData = 0;
-  jbyte *outData = 0;
-  jint ret;
-  jint s;
-
-  if (!inArray || !outArray) {
-    throwException(env, "java/lang/NullPointerException");
-    goto cleanup;
-  }
-
-  inCap = (*env)->GetArrayLength(env, inArray);
-  outCap = (*env)->GetArrayLength(env, outArray);
-
-  // Check if any of the offset/length pairs is invalid; we do this by OR'ing
-  // things we don't want to be negative and seeing if the result is negative
-  s = inOff | inLen | (inOff + inLen) | (inCap - (inOff + inLen)) |
-      outOff | outLen | (outOff + outLen) | (outCap - (outOff + outLen));
-  if (s < 0) {
-    throwException(env, "java/lang/IndexOutOfBoundsException");
-    goto cleanup;
-  }
-
-  inData = (*env)->GetPrimitiveArrayCritical(env, inArray, 0);
-  outData = (*env)->GetPrimitiveArrayCritical(env, outArray, 0);
-
-  if (!inData || !outData) {
-    // Out of memory - JVM will throw OutOfMemoryError
-    goto cleanup;
-  }
-  
-  ret = func(inData + inOff, inLen, outData + outOff, outLen);
-
-cleanup:
-  if (inData)
-    (*env)->ReleasePrimitiveArrayCritical(env, inArray, inData, 0);
-  if (outData)
-    (*env)->ReleasePrimitiveArrayCritical(env, outArray, outData, 0);
-  
-  return ret;
-}
-
-/*
- * Class:     spark_compress_lzf_LZF
- * Method:    compress
- * Signature: ([B[B)I
- */
-JNIEXPORT jint JNICALL Java_spark_compress_lzf_LZF_compress
-  (JNIEnv *env, jclass cls, jbyteArray inArray, jint inOff, jint inLen,
-   jbyteArray outArray, jint outOff, jint outLen)
-{
-  return callCompressionFunction(lzf_compress, env, cls, 
-      inArray, inOff, inLen, outArray,outOff, outLen);
-}
-
-/*
- * Class:     spark_compress_lzf_LZF
- * Method:    decompress
- * Signature: ([B[B)I
- */
-JNIEXPORT jint JNICALL Java_spark_compress_lzf_LZF_decompress
-  (JNIEnv *env, jclass cls, jbyteArray inArray, jint inOff, jint inLen,
-   jbyteArray outArray, jint outOff, jint outLen)
-{
-  return callCompressionFunction(lzf_decompress, env, cls, 
-      inArray, inOff, inLen, outArray,outOff, outLen);
-}
diff --git a/src/scala/spark/Broadcast.scala b/src/scala/spark/Broadcast.scala
index 5089dca82e..2fdd960bdc 100644
--- a/src/scala/spark/Broadcast.scala
+++ b/src/scala/spark/Broadcast.scala
@@ -14,7 +14,7 @@ import scala.collection.mutable.Map
 import org.apache.hadoop.conf.Configuration
 import org.apache.hadoop.fs.{FileSystem, Path, RawLocalFileSystem}
 
-import spark.compress.lzf.{LZFInputStream, LZFOutputStream}
+import com.ning.compress.lzf.{LZFInputStream, LZFOutputStream}
 
 @serializable
 trait BroadcastRecipe {
diff --git a/third_party/compress-lzf-0.6.0/compress-lzf-0.6.0.jar b/third_party/compress-lzf-0.6.0/compress-lzf-0.6.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6cb5c4c92b9e21300c5203b8e4f4727028a68ad3
GIT binary patch
literal 14497
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
KR0{s_s{bF-p8@p%

literal 0
HcmV?d00001

-- 
GitLab