/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.zip.Deflater;
/** Mutable; implements a growable array of bytes.
*
* <p> This class is more efficient than Java's ByteArrayOutputStream when writing large amount of data,
* because ByteArrayOutputStream will resize and copy entire existing contents every time the array needs to grow,
* whereas this class maintains a linked list of arrays (so when capacity is expanded we don't need to copy old data)
*/
public final class ByteBuffer {
/** The size per chunk. */
private static final int SIZE = 65536;
/** The list of chunks allocated so far; always has at least one chunk; every chunk is always exactly of size SIZE. */
private final LinkedList<byte[]> list = new LinkedList<byte[]>();
/** The number of bytes stored in the latest chunk; every chunk before that is always fully filled. */
private int n = 0;
/** Construct an empty byte buffer. */
public ByteBuffer() { list.add(new byte[SIZE]); }
/** Write the given byte into this byte buffer. */
private ByteBuffer w(int b) {
if (n==SIZE) { list.add(new byte[SIZE]); n=0; }
byte[] array = list.getLast();
array[n] = (byte)b;
n++;
return this;
}
/** Write the given array of bytes into this byte buffer. */
private ByteBuffer write(byte[] b, int offset, int len) {
if (b==null || len<=0) return this; else if (n==SIZE) { list.add(new byte[SIZE]); n=0; }
while(true) { // loop invariant: len>0 and SIZE>n
byte[] array = list.getLast();
if (len <= (SIZE-n)) { System.arraycopy(b, offset, array, n, len); n += len; return this; }
System.arraycopy(b, offset, array, n, SIZE-n);
offset += (SIZE-n);
len -= (SIZE-n);
n = 0;
list.add(new byte[SIZE]);
}
}
/** Write the given String into this byte buffer (by converting the String into its UTF-8 representation) */
public ByteBuffer write(String string) {
if (string.length() == 0) return this;
byte[] b;
try { b = string.getBytes("UTF-8"); } catch(UnsupportedEncodingException ex) { return this; } // exception not possible
return write(b, 0, b.length);
}
/** Write the given number into this byte buffer, followed by a space. */
public ByteBuffer writes(long x) {
return write(Long.toString(x)).w(' ');
}
/** Write the given number into this byte buffer (truncated to the range -32767..+32767), followed by a space. */
public strictfp ByteBuffer writes(double x) {
// These extreme values shouldn't happen, but we want to protect against them
if (Double.isNaN(x)) return write("0 "); else if (x>32767) return write("32767 "); else if (x<-32767) return write("-32767 ");
long num = (long)(x * 1000000);
if (num>=32767000000L) return write("32767 "); else if (num<=(-32767000000L)) return write("-32767 ");
// Now, regular doubles... let's allow up to 6 digits after the decimal point
if (num<0) { w('-'); num = -num; }
String str = Long.toString(num);
int len = str.length();
if (len<=6) {
w('.');
while(len<6) { w('0'); len++; }
return write(str).w(' ');
}
return write(str.substring(0, str.length()-6)).w('.').write(str.substring(str.length()-6)).w(' ');
}
/** Write the entire content into the given file using Flate compression (see RFC1951) then return the number of bytes written. */
public long dumpFlate(RandomAccessFile os) throws IOException {
Deflater zip = new Deflater(Deflater.BEST_COMPRESSION);
byte[] output = new byte[8192];
Iterator<byte[]> it = list.iterator(); // when null, that means we have told the Deflater that no more input would be coming
long ans = 0; // the number of bytes written out so far
while(true) {
if (it!=null && zip.needsInput() && it.hasNext()) {
byte[] in = it.next();
if (in == list.getLast()) { zip.setInput(in, 0, n); it=null; zip.finish(); } else { zip.setInput(in, 0, SIZE); }
}
if (it==null && zip.finished()) break;
int count = zip.deflate(output);
if (count > 0) {
ans = ans + count;
os.write(output, 0, count);
if (ans < 0) throw new IOException("Data too large to be written to the output file.");
}
}
return ans;
}
/** Write the entire content into the given file as-is, then return the number of bytes written. */
public long dump(RandomAccessFile os) throws IOException {
if (list.size() >= (Long.MAX_VALUE / SIZE)) throw new IOException("Data too large to be written to the output file.");
byte[] last = list.getLast();
for(byte[] x: list) if (x!=last) os.write(x);
if (n>0) os.write(last, 0, n);
return ((long)(list.size()-1)) * SIZE + n;
}
}