/*
QuiXProc: efficient evaluation of XProc Pipelines.
Copyright (C) 2011-2015 Innovimax
All rights reserved.
This program 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.
This program 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package innovimax.quixproc.datamodel.generator;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import innovimax.quixproc.datamodel.QuiXException;
import innovimax.quixproc.datamodel.event.AQuiXEvent;
import innovimax.quixproc.datamodel.event.IQuiXEventStreamReader;
import innovimax.quixproc.datamodel.generator.AReversibleRandom.SimpleReversibleRandom;
import innovimax.quixproc.datamodel.stream.IQuiXStreamReader;
public abstract class AGenerator {
public enum FileExtension {
XML, HTML, JSON, YAML, RDF, CSV
}
public enum Variation {
NO_VARIATION, SEQUENTIAL, RANDOM
}
public enum Encoding {
SEVEN_BITS, // 1 byte (minus the upper bit) is one char
EIGHT_BITS, // 1 byte (including upper bit) is one char
TWO_BYTES, // 2 bytes per char
UNICODE
}
public enum FileProperty {
REPEATABLE/*
* repeated part of the content makes it potentially
* infinite
*/, SYMMETRIC/*
* the file is symmetric to its middle, usually
* you must provide a length
*/, CONTENT_VARIATION/*
* content is varying from
* one occurrence to the
* other
*/,
}
// 7BIT
protected Charset currentCharset = StandardCharsets.US_ASCII;
protected byte[] s2b(String s) {
return s.getBytes(this.currentCharset);
}
protected final AReversibleRandom random;
private final long seed;
public enum BaseUnit {
BYTE, EVENT
}
public enum Unit {
BYTE(1, "B", BaseUnit.BYTE), KBYTE(1000, "KB", BaseUnit.BYTE), MBYTE(1000000, "MB", BaseUnit.BYTE), GBYTE(
1000000000, "GB",
BaseUnit.BYTE), TBYTE(1000000000000L, "TB", BaseUnit.BYTE), EVENT(1, "Ev", BaseUnit.BYTE), KEVENT(1000,
"KEv", BaseUnit.BYTE), MEVENT(1000000, "MEv", BaseUnit.BYTE), GEVENT(1000000000, "GEv",
BaseUnit.BYTE), TEVENT(1000000000000L, "TEv", BaseUnit.BYTE);
private final long value;
private final String display;
private final BaseUnit base;
Unit(long value, String display, BaseUnit base) {
this.value = value;
this.display = display;
this.base = base;
}
public long value() {
return this.value;
}
public BaseUnit getBase() {
return this.base;
}
public String display() {
return this.display;
}
}
/*
* protected static AGenerator instance(FileExtension type) { switch (type)
* { case HTML: break; case JSON: break; case XML: return new
* AXMLGenerator(); break; case YAML: break; default: break;
*
* } return null; }
*/
public void generate(File output, long size) throws IOException {
generate(output, size, Unit.BYTE, Variation.NO_VARIATION);
}
public void generate(File output, long size, Unit unit) throws IOException {
generate(output, size, unit, Variation.NO_VARIATION);
}
public void generate(File output, long size, Unit unit, Variation variation) throws IOException {
output.getParentFile().mkdirs();
final long total = size * unit.value();
try (FileOutputStream fos = new FileOutputStream(output)) {
try (BufferedOutputStream bos = new BufferedOutputStream(fos, 1000 * 1000)) {
final byte[] start = getStart();
final byte[][] patterns = getPatterns();
// ensure that at minimum the size is start+end
long current_size = start.length + getEnd().length;
// write the start pattern
bos.write(start);
// System.out.println(display(start));
int current_pattern = -1;
while (notFinished(current_size, current_pattern, total)) {
// move to next pattern
current_pattern = updatePattern(current_pattern);
// System.out.println(current_size);
// write the alternate pattern
byte[] toWrite = applyVariation(variation, patterns, current_pattern);
// System.out.println(display(toWrite));
bos.write(toWrite);
// update the size
current_size = updateSize(current_size, current_pattern);
}
// write the end pattern
bos.write(getEnd());
// System.out.println(display(getEnd()));
bos.flush();
bos.close();
fos.close();
}
}
}
public InputStream getInputStream(long size, Unit unit, Variation variation) {
return new GeneratorInputStream(size, unit, variation);
}
public final IQuiXEventStreamReader getQuiXEventStreamReader(long size, Unit unit, Variation variation) {
return new GeneratorQuiXEventStreamReader(size, unit, variation);
}
public abstract IQuiXStreamReader getQuiXStreamReader();
protected abstract byte[] applyVariation(Variation variation, byte[][] bs, int pos);
protected abstract boolean notFinished(long current_size, int current_pattern, long total);
protected abstract boolean notFinishedEvent(long current_size, int current_pattern, long total);
protected abstract int updatePattern(int current_pattern);
protected abstract long updateSize(long current_size, int current_pattern);
protected abstract byte[] getEnd();
protected abstract byte[][] getPatterns();
protected abstract byte[] getStart();
protected abstract AQuiXEvent[] getEndEvent();
protected abstract AQuiXEvent[][] getPatternsEvent();
protected abstract AQuiXEvent[] getStartEvent();
private enum QuiXEventStreamReaderState {
START, CURRENT, END
}
public class GeneratorQuiXEventStreamReader implements IQuiXEventStreamReader {
final long total;
final Variation variation;
QuiXEventStreamReaderState state;
AQuiXEvent[] buffer;
int position;
int current_pattern;
long current_evsize = getStartEvent().length + getEndEvent().length;
public GeneratorQuiXEventStreamReader(long size, Unit unit, Variation variation) {
this.state = QuiXEventStreamReaderState.START;
this.total = size * unit.value();
this.variation = variation;
this.buffer = getStartEvent();
this.position = 0;
}
@Override
public boolean hasNext() throws QuiXException {
return this.state != QuiXEventStreamReaderState.END;
}
@Override
public AQuiXEvent next() throws QuiXException {
if (this.position < this.buffer.length) {
return this.buffer[this.position++];
}
switch (this.state) {
case START:
if (notFinishedEvent(this.current_evsize, this.current_pattern, this.total)) {
// TODO
}
break;
case END:
break;
default:
break;
}
return null;
}
@Override
public void close() {
// TODO Auto-generated method stub
}
}
private enum InputStreamState {
START,
// CURRENT,
END
}
public class GeneratorInputStream extends InputStream {
final byte[] start = getStart();
final byte[][] patterns = getPatterns();
// final byte[] end = getEnd();
// ensure that at minimum the size is start+end
long current_size = this.start.length + getEnd().length;
int current_pattern = -1;
int offset = -1;
byte[] buffer = null;
InputStreamState state;
final long total;
final Variation variation;
public GeneratorInputStream(long size, Unit unit, Variation variation) {
this.state = InputStreamState.START;
this.buffer = this.start;
// System.out.println("START : length : "+this.buffer.length+" :
// "+display(this.buffer));
this.total = size * unit.value();
this.variation = variation;
}
@Override
public int read(byte[] b, int off, int len) {
// System.out.println("READ : off : "+off+" ; len : "+len+" :
// "+display(b));
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return 0;
}
if (this.buffer == null)
return -1;
if (this.offset + 1 == this.buffer.length) {
// System.out.println("offset : "+this.offset);
// System.out.println("length : "+len);
update();
// System.out.println("offset : "+this.offset);
// System.out.println("length : "+len);
if (this.buffer == null)
return -1;
}
int total = 0;
do {
// System.out.println("offset : "+this.offset);
// System.out.println("length : "+len);
int length = Math.min(this.buffer.length - (this.offset + 1), len - total);
// System.out.println("length : "+length);
System.arraycopy(this.buffer, this.offset + 1, b, off + total, length);
total += length;
// System.out.println("total : "+total);
this.offset = this.offset + 1 + length - 1;
// System.out.println("offset : "+this.offset);
if (this.offset == this.buffer.length - 1)
update();
if (total == len) {
// System.out.println("offset : "+ this.offset+"; length :
// "+len+" : "+display(b));
return len;
}
if (this.buffer == null) {
// System.out.println("offset : "+ this.offset+"; length :
// "+total+" : "+display(b));
return total;
}
} while (true);
}
private void update() {
// System.out.println(this.state);
// offset == buffer.length
switch (this.state) {
case START:
// System.out.println("NotFinished");
if (notFinished(this.current_size, this.current_pattern, this.total)) {
// System.out.println("NotFinished : no");
// move to next pattern
this.current_pattern = updatePattern(this.current_pattern);
// System.out.println(current_size);
// write the alternate pattern
this.buffer = applyVariation(this.variation, this.patterns, this.current_pattern);
// update the size
this.current_size = updateSize(this.current_size, this.current_pattern);
// System.out.println("Currentsize : "+this.current_size);
this.offset = -1;
return;
}
// this.state = InputStreamState.CURRENT;
// //$FALL-THROUGH$
// case CURRENT:
this.buffer = getEnd();
this.offset = -1;
this.state = InputStreamState.END;
return;
case END:
this.buffer = null;
break;
default:
}
}
@Override
public int read() {
if (this.buffer == null)
return -1;
this.offset++;
if (this.offset < this.buffer.length) {
int c = this.buffer[this.offset];
// System.out.println("read : "+display((byte) (c & 0xFF)));
return c;
}
update();
if (this.buffer == null)
return -1;
if (this.buffer.length == 0)
return -1;
this.offset++;
int c = this.buffer[this.offset];
// System.out.println("read : "+display((byte) (c & 0xFF)));
return c;
}
}
public static String display(byte b) {
return Integer.toHexString(b & 0xFF) + "(" + Character.toString((char) (b & 0xFF)) + ")";
}
protected static String display(byte[] bytea) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < bytea.length; i++) {
if (i > 0)
sb.append(", ");
sb.append(display(bytea[i]));
}
sb.append("]");
return sb.toString();
}
protected AGenerator() {
this.seed = System.nanoTime();
this.random = new SimpleReversibleRandom(this.seed, 3, 5);
}
protected void resetRandom() {
this.random.setSeed(this.seed);
}
public static String display(int c) {
return display((byte) (c & 0XFF));
}
}