diff --git a/src/com/amazon/ion/benchmark/CountingOutputStream.java b/src/com/amazon/ion/benchmark/CountingOutputStream.java
new file mode 100644
index 0000000..7f63b43
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/CountingOutputStream.java
@@ -0,0 +1,39 @@
+package com.amazon.ion.benchmark;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class CountingOutputStream extends FilterOutputStream {
+ private long count;
+
+ /**
+ * Creates an output stream filter built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream to be assigned to
+ * the field this.out for later use, or
+ * null if this instance is to be
+ * created without an underlying stream.
+ */
+ public CountingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /** Returns the number of bytes written. */
+ public long getCount() {
+ return count;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ this.count += len;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ out.write(b);
+ this.count++;
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/DataConstructor.java b/src/com/amazon/ion/benchmark/DataConstructor.java
new file mode 100644
index 0000000..20ceae2
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/DataConstructor.java
@@ -0,0 +1,584 @@
+package com.amazon.ion.benchmark;
+
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import com.amazon.ion.IonList;
+import com.amazon.ion.IonReader;
+import com.amazon.ion.IonStruct;
+import com.amazon.ion.IonSystem;
+import com.amazon.ion.IonTimestamp;
+import com.amazon.ion.IonType;
+import com.amazon.ion.IonValue;
+import com.amazon.ion.IonWriter;
+import com.amazon.ion.Timestamp;
+import com.amazon.ion.benchmark.schema.ReparsedType;
+import com.amazon.ion.benchmark.schema.constraints.QuantifiableConstraints;
+import com.amazon.ion.benchmark.schema.constraints.Range;
+import com.amazon.ion.benchmark.schema.constraints.Regex;
+import com.amazon.ion.benchmark.schema.constraints.ReparsedConstraint;
+import com.amazon.ion.benchmark.schema.constraints.TimestampPrecision;
+import com.amazon.ion.benchmark.schema.constraints.ValidValues;
+import com.amazon.ion.system.IonBinaryWriterBuilder;
+import com.amazon.ion.system.IonReaderBuilder;
+import com.amazon.ion.system.IonSystemBuilder;
+import com.github.curiousoddman.rgxgen.RgxGen;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Generate specific scalar type of Ion data randomly, for some specific type, e.g. String, Decimal, Timestamp, users can put specifications on these types of Ion data.
+ */
+class DataConstructor {
+ // The constant defined below are used as placeholder in the method WriteRandomIonValues.writeRequestedSizeFile.
+ final static private IonSystem SYSTEM = IonSystemBuilder.standard().build();
+ final static private List DEFAULT_RANGE = Arrays.asList(0, 0x10FFFF);
+ final static public Timestamp.Precision[] PRECISIONS = Timestamp.Precision.values();
+ final static public IonStruct NO_CONSTRAINT_STRUCT = null;
+ final static private int DEFAULT_PRECISION = 20;
+ final static private int DEFAULT_SCALE_LOWER_BOUND = -20;
+ final static private int DEFAULT_SCALE_UPPER_BOUND = 20;
+ final static private Set VALID_STRING_SYMBOL_CONSTRAINTS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("regex", "codepoint_length")));
+ final static private Set VALID_DECIMAL_CONSTRAINTS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("precision", "scale")));
+ final static private Set VALID_TIMESTAMP_CONSTRAINTS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("timestamp_offset", "timestamp_precision")));
+ // Create a range which contains the default lower bound and upper bound values.
+ final static private Range DEFAULT_TIMESTAMP_IN_MILLIS_DECIMAL_RANGE = new Range(SYSTEM.newList( SYSTEM.newDecimal(62135769600000L), SYSTEM.newDecimal(253402300800000L)));
+
+ /**
+ * Use Ion-java parser to parse the data provided in the options which specify the range of data.
+ * @param range the range needed to be parsed, normally in the format of "[Integer, Integer]"
+ * @return a list of Integer which will be extracted in the following executions.
+ */
+ public static List parseRange(String range) {
+ IonReaderBuilder readerBuilder = IonReaderBuilder.standard();
+ IonReader reader = readerBuilder.build(range);
+ if (reader.next() != IonType.LIST) throw new IllegalStateException("Please provide a list type");
+ reader.stepIn();
+ List result = new ArrayList<>();
+ while (reader.next() != null) {
+ if (reader.getType() != IonType.INT) throw new IllegalStateException("Please put integer elements inside of the list");
+ int value = reader.intValue();
+ result.add(value);
+ }
+ reader.stepOut();
+
+ if (reader.next() != null) throw new IllegalStateException("Only one list is accepted");
+ if (result.get(0) > result.get(1)) throw new IllegalStateException("The value of the lower bound should be smaller than the upper bound");
+ if (result.size() != 2) throw new IllegalStateException("Please put two integers inside of the list");
+ return result;
+ }
+
+ /**
+ * Print the successfully generated data notification which includes the file path information.
+ * @param path identifies the output file path.
+ */
+ public static void printInfo(String path) {
+ String fileName = path.substring(path.lastIndexOf("/") + 1);
+ System.out.println(fileName + " generated successfully ! ");
+ if (fileName.equals(path)) {
+ System.out.println("Generated data is under the current directory");
+ } else {
+ System.out.println("File path: " + path);
+ }
+ }
+
+ /**
+ *This method is not available now
+ */
+ private static void writeListOfRandomFloats() throws Exception {
+ File file = new File("manyLargeListsOfRandomFloats.10n");
+ try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
+ IonWriter writer = IonBinaryWriterBuilder.standard().withFloatBinary32Enabled().build(out)) {
+ Random random = new Random();
+ for (int j = 0; j < 100; j++) {
+ writer.stepIn(IonType.LIST);
+ // Target about 1MB of data. Floats will average at 7 bytes, and we're writing 2
+ // per iteration.
+ for (int i = 0; i < (1_000_000 / 7 / 2); i++) {
+ writer.writeFloat(Double.longBitsToDouble(random.nextLong()));
+ writer.writeFloat(Float.intBitsToFloat(random.nextInt()));
+ }
+ writer.stepOut();
+ }
+ }
+ System.out.println("Finished writing floats. Verifying.");
+ try (IonReader reader = IonReaderBuilder.standard().build(new BufferedInputStream(new FileInputStream(file)))) {
+ while (reader.next() != null) {
+ reader.stepIn();
+ while (reader.next() != null) {
+ if (reader.getType() != IonType.FLOAT) {
+ throw new IllegalStateException("Found non-float");
+ }
+ double value = reader.doubleValue();
+ }
+ reader.stepOut();
+ }
+ }
+ System.out.println("Done. Size: " + file.length());
+ }
+
+ /**
+ * Generate random offset without any specification
+ * @param random is the random number generator.
+ * @return random offset [Z(+00:00) | -00:00 | random offset].
+ */
+ private static Integer localOffset(Random random) {
+ // Offsets are in minutes, [-23:59, 23:59], i.e. [-1439, 1439].
+ // The most common offset is Z (00:00), while unknown (-00:00) may also be common.
+ Integer offsetMinutes = random.nextInt(6000) - 2000;
+ if (offsetMinutes > 1439) {
+ // This means about 43% of timestamps will have offset Z (UTC).
+ offsetMinutes = 0;
+ } else if (offsetMinutes < -1439) {
+ // This means about 9% of timestamps will have unknown offset (-00:00).
+ offsetMinutes = null;
+ }
+ return offsetMinutes;
+ }
+
+ /**
+ * Generate random fractional second which the precision conforms with the precision of the second in template timestamp.
+ * @param random is the random number generator.
+ * @param scale is the scale of the decimal second in the current template timestamp.
+ * @return a random timestamp second in a fractional format which conforms with the current template timestamp.
+ */
+ private static BigDecimal randomSecondWithFraction(Random random, int scale) {
+ int second = random.nextInt(60);
+ if (scale != 0) {
+ StringBuilder coefficientStr = new StringBuilder();
+ for (int digit = 0; digit < scale; digit++) {
+ coefficientStr.append(random.nextInt(10));
+ }
+ BigDecimal fractional = new BigDecimal(coefficientStr.toString());
+ BigDecimal fractionalSecond = fractional.scaleByPowerOfTen(scale * (-1));
+ return fractionalSecond.add(BigDecimal.valueOf(second));
+ } else {
+ return BigDecimal.valueOf(second);
+ }
+ }
+
+ /**
+ * Constructing data which is conformed with provided type definition.
+ * @param parsedTypeDefinition is parsed from ion schema file as IonStruct format, it contains the top-level constraints.
+ * @return constructed ion data.
+ */
+ public static IonValue constructIonData(ReparsedType parsedTypeDefinition) {
+ // The first step is to check whether parsedTypeDefinition contains 'valid_values'. The reason we prioritize checking
+ // 'valid_values' is that the constraint 'type' might not be contained in the type definition, in that case we cannot trigger
+ // the following data constructing process.
+ // Assume if 'valid_values' provided in ISL file, constraint 'type' is optional, else constraint 'type' is required.
+ Map constraintMap = parsedTypeDefinition.getConstraintMap();
+ Map constraintMapClone = new HashMap<>();
+ constraintMapClone.putAll(constraintMap);
+ ValidValues validValues = (ValidValues) constraintMap.get("valid_values");
+ if (validValues != null && !validValues.isRange()) {
+ return getRandomValueFromList(validValues.getValidValues());
+ } else if (parsedTypeDefinition.getIonType() == null) {
+ throw new IllegalStateException("Constraint 'type' is required.");
+ } else {
+ IonType type = parsedTypeDefinition.getIonType();
+ switch (type) {
+ case FLOAT:
+ return SYSTEM.newFloat(constructFloat(constraintMapClone));
+ case SYMBOL:
+ return SYSTEM.newSymbol(constructString(constraintMapClone));
+ case INT:
+ return SYSTEM.newInt(constructInt(constraintMapClone));
+ case STRING:
+ return SYSTEM.newString(constructString(constraintMapClone));
+ case DECIMAL:
+ return SYSTEM.newDecimal(constructDecimal(constraintMapClone));
+ case TIMESTAMP:
+ return SYSTEM.newTimestamp(constructTimestamp(constraintMapClone));
+ case BLOB:
+ return SYSTEM.newBlob(constructLobs(constraintMapClone));
+ case CLOB:
+ return SYSTEM.newClob(constructLobs(constraintMapClone));
+ default:
+ throw new IllegalStateException(type + " is not supported.");
+ }
+ }
+ }
+
+ /**
+ * Get a random IonValue from IonList.
+ * @param values represents IonList.
+ * @return the randomly chosen IonValue.
+ */
+ public static IonValue getRandomValueFromList(IonList values) {
+ Random random = new Random();
+ int randomIndex = random.nextInt(values.size());
+ return values.get(randomIndex);
+ }
+
+ /**
+ * Construct string which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is constraint value in ReparsedConstraint format.
+ * @return constructed string.
+ */
+ public static String constructString(Map constraintMapClone) {
+ Random random = new Random();
+ Regex regex = (Regex) constraintMapClone.remove("regex");
+ QuantifiableConstraints codepoint_length = (QuantifiableConstraints) constraintMapClone.remove("codepoint_length");
+
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (regex != null && codepoint_length != null) {
+ throw new IllegalStateException ("Can only handle one of : " + VALID_STRING_SYMBOL_CONSTRAINTS);
+ } else if (regex != null) {
+ String pattern = regex.getPattern();
+ RgxGen rgxGen = new RgxGen(pattern);
+ return rgxGen.generate();
+ } else if (codepoint_length != null) {
+ int length = codepoint_length.getRange().getRandomQuantifiableValueFromRange().intValue();
+ return constructStringFromCodepointLength(length);
+ } else {
+ // If there is no constraints provided, a randomly constructed string with
+ // preset Unicode codepoints length will be generated.
+ return constructStringFromCodepointLength(random.nextInt(20));
+ }
+ }
+
+ /**
+ * Generate unicode codepoint randomly.
+ * @return generated codepoint.
+ */
+ private static int getCodePoint() {
+ Random random = new Random();
+ int type;
+ int codePoint;
+ do {
+ codePoint = random.nextInt(DEFAULT_RANGE.get(1) - DEFAULT_RANGE.get(0) + 1) + DEFAULT_RANGE.get(0);
+ type = Character.getType(codePoint);
+ } while (type == Character.PRIVATE_USE || type == Character.SURROGATE || type == Character.UNASSIGNED);
+ return codePoint;
+ }
+
+ /**
+ * Construct string which is conformed with the provided codepoint_length.
+ * @param codePointsLengthBound represents the exact number of Unicode codepoints in a string or symbol.
+ * @return the constructed string.
+ */
+ private static String constructStringFromCodepointLength(int codePointsLengthBound) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < codePointsLengthBound; i++) {
+ int codePoint = getCodePoint();
+ sb.appendCodePoint(codePoint);
+ }
+ String constructedString = sb.toString();
+ return constructedString;
+ }
+
+ /**
+ * Construct the float which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is constraint value in ReparsedConstraint format.
+ * @return the constructed double value.
+ */
+ public static Double constructFloat(Map constraintMapClone) {
+ // In the process of generating IonFloat, there is no type-specified constraints. For this step we
+ // only consider the general constraint 'valid_values'.
+ ValidValues validValues = (ValidValues) constraintMapClone.remove("valid_values");
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (validValues != null) {
+ return validValues.getRange().getRandomQuantifiableValueFromRange().doubleValue();
+ } else {
+ return ThreadLocalRandom.current().nextDouble();
+ }
+ }
+
+ /**
+ * Construct the decimal which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is constraint value in ReparsedConstraint format.
+ * @return the constructed decimal.
+ */
+ public static BigDecimal constructDecimal(Map constraintMapClone) {
+ Random random = new Random();
+ // If there is no constraints provided, assign scale and precision with default values.
+ int scaleValue = random.nextInt(DEFAULT_SCALE_UPPER_BOUND - DEFAULT_SCALE_LOWER_BOUND + 1) + DEFAULT_SCALE_LOWER_BOUND;
+ int precisionValue = random.nextInt(DEFAULT_PRECISION);
+ QuantifiableConstraints scale = (QuantifiableConstraints) constraintMapClone.remove("scale");
+ QuantifiableConstraints precision = (QuantifiableConstraints) constraintMapClone.remove("precision");
+ ValidValues validValues = (ValidValues) constraintMapClone.remove("valid_values");
+ StringBuilder rs = new StringBuilder();
+ rs.append(random.nextInt(9) + 1);
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (validValues == null) {
+ if (scale != null) {
+ scaleValue = scale.getRange().getRandomQuantifiableValueFromRange().intValue();
+ }
+ if (precision != null) {
+ precisionValue = precision.getRange().getRandomQuantifiableValueFromRange().intValue();
+ }
+ for (int digit = 1; digit < precisionValue; digit++) {
+ rs.append(random.nextInt(10));
+ }
+ BigInteger unscaledValue = new BigInteger(rs.toString());
+ return new BigDecimal(unscaledValue, scaleValue);
+ } else {
+ if (scale != null || precision != null) {
+ throw new IllegalStateException("Cannot handle 'valid_values' and constraint from " + VALID_DECIMAL_CONSTRAINTS + "at the same time.");
+ } else {
+ return validValues.getRange().getRandomQuantifiableValueFromRange();
+ }
+ }
+ }
+
+ /**
+ * Generate random integers which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is in ReparsedConstraint format.
+ * @return the constructed int.
+ */
+ public static long constructInt(Map constraintMapClone) {
+ // In the process of generating IonInt, there is no type-specified constraints. For this step we
+ // only consider the general constraints 'valid_values'.
+ ValidValues validValues = (ValidValues) constraintMapClone.remove("valid_values");
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (validValues != null) {
+ // The generated data is conformed with the provided 'valid_values' range.
+ return validValues.getRange().getRandomQuantifiableValueFromRange().longValue();
+ } else {
+ // If there is no constraint provided, the generator will construct a random value.
+ return ThreadLocalRandom.current().nextLong();
+ }
+ }
+
+ /**
+ * Construct timestamp which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is in ReparsedConstraint format.
+ * @return the constructed timestamp.
+ */
+ public static Timestamp constructTimestamp(Map constraintMapClone) {
+ Random random = new Random();
+ Range range = DEFAULT_TIMESTAMP_IN_MILLIS_DECIMAL_RANGE;
+ // Preset the local offset.
+ Integer localOffset = localOffset(random);
+ // Preset the default precision.
+ Timestamp.Precision precision = PRECISIONS[random.nextInt(PRECISIONS.length)];
+ TimestampPrecision timestampPrecision = (TimestampPrecision) constraintMapClone.remove("timestamp_precision");
+ ValidValues validValues = (ValidValues) constraintMapClone.remove("valid_values");
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (validValues == null) {
+ if (timestampPrecision != null) {
+ precision = TimestampPrecision.getRandomTimestampPrecision(timestampPrecision.getRange());
+ }
+ } else {
+ if (timestampPrecision != null) {
+ throw new IllegalStateException("Cannot handle 'valid_values' and constraint from " + VALID_TIMESTAMP_CONSTRAINTS + "at the same time.");
+ } else {
+ range = validValues.getRange();
+ IonTimestamp upperBound = range.upperBound(IonTimestamp.class);
+ localOffset = upperBound.getLocalOffset();
+ precision = upperBound.timestampValue().getPrecision();
+ }
+ }
+ // Generate a random millisecond within the provided range.
+ BigDecimal randomMillis = range.getRandomQuantifiableValueFromRange();
+ // Generate timestamp based on the provided millisecond value and precision.
+ Timestamp regeneratedTimestamp = Timestamp.forMillis(randomMillis, localOffset);
+
+ int year = regeneratedTimestamp.getYear();
+ int month = regeneratedTimestamp.getMonth();
+ int day = regeneratedTimestamp.getDay();
+ int minute = regeneratedTimestamp.getMinute();
+ int hour = regeneratedTimestamp.getHour();
+ int seconds = regeneratedTimestamp.getSecond();
+ BigDecimal fracSecond = regeneratedTimestamp.getDecimalSecond().subtract(BigDecimal.valueOf(seconds));
+ return Timestamp.createFromUtcFields(precision, year, month, day, hour, minute, seconds, fracSecond, localOffset);
+ }
+
+ /**
+ * Construct clob/blob which is conformed with the constraints provided in ISL.
+ * @param constraintMapClone collects the constraints from ISL file, the key represents the name of constraints,
+ * and the value is in ReparsedConstraint format.
+ * @return the constructed bytes.
+ */
+ public static byte[] constructLobs( Map constraintMapClone) {
+ int byte_length;
+ Random random = new Random();
+ QuantifiableConstraints byteLength = (QuantifiableConstraints) constraintMapClone.remove("byte_length");
+ if (!constraintMapClone.isEmpty()) {
+ throw new IllegalStateException ("Found unhandled constraints : " + constraintMapClone.values());
+ }
+ if (byteLength != null) {
+ byte_length = byteLength.getRange().getRandomQuantifiableValueFromRange().intValue();
+ } else {
+ byte_length = random.nextInt(512);
+ }
+ byte[] randomBytes = new byte[byte_length];
+ random.nextBytes(randomBytes);
+ return randomBytes;
+ }
+
+ /**
+ * Construct and write Ion structs based on the provided constraints.
+ * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
+ * @param writer writes Ion struct data.
+ * @throws IOException if errors occur when writing data.
+ */
+// public static void constructAndWriteIonStruct(IonStruct constraintStruct, IonWriter writer) throws Exception {
+// Random random = new Random();
+// IonList annotations = IonSchemaUtilities.getAnnotation(constraintStruct);
+// IonStruct fields = (IonStruct) constraintStruct.get(IonSchemaUtilities.KEYWORD_FIELDS);
+// try (IonReader reader = IonReaderBuilder.standard().build(fields)) {
+// reader.next();
+// reader.stepIn();
+// for (int i = 0; i < annotations.size(); i++) {
+// writer.addTypeAnnotation(annotations.get(i).toString());
+// }
+// writer.stepIn(IonType.STRUCT);
+// while (reader.next() != null) {
+// String fieldName = reader.getFieldName();
+// IonValue struct = SYSTEM.newValue(reader);
+// IonStruct value = (IonStruct) struct;
+// // If the value of "occurs" is optional, the integer represents this value is 1 or 0.
+// int occurTime = IonSchemaUtilities.parseConstraints(value, IonSchemaUtilities.KEYWORD_OCCURS);
+// if (occurTime == 0) {
+// continue;
+// }
+// writer.setFieldName(fieldName);
+// IonType type = IonType.valueOf(value.get(IonSchemaUtilities.KEYWORD_TYPE).toString().toUpperCase());
+// switch (type) {
+// // If more types of Ion data are available, the logic should be added below.
+// case STRING:
+// writer.writeString(WriteRandomIonValues.constructString(value));
+// break;
+// case TIMESTAMP:
+// writer.writeTimestamp(WriteRandomIonValues.constructTimestamp(value));
+// break;
+// case LIST:
+// WriteRandomIonValues.constructAndWriteIonList(writer, value);
+// break;
+// default:
+// throw new IllegalStateException(type + " is not supported when generating Ion Struct based on Ion Schema.");
+// }
+// }
+// writer.stepOut();
+// }
+// }
+
+ /**
+ * Construct Ion List based on the constraints provided by Ion Schema.
+ * @param writer is Ion Writer.
+ * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
+ * @throws Exception if errors occur when reading or writing data.
+ */
+// public static void constructAndWriteIonList(IonWriter writer, IonStruct constraintStruct) throws Exception {
+// // When there's only one required element in Ion List and the length of generated Ion List is not specified, we set the default length as a integer smaller than 20.
+// Integer containerLength = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_CONTAINER_LENGTH);
+// IonList annotations = IonSchemaUtilities.getAnnotation(constraintStruct);
+// int occurrences;
+// try (IonReader reader = IonReaderBuilder.standard().build(constraintStruct)) {
+// reader.next();
+// reader.stepIn();
+// while (reader.next() != null) {
+// if (annotations != null) {
+// for (int i = 0; i < annotations.size(); i++) {
+// writer.addTypeAnnotation(annotations.get(i).toString());
+// }
+// }
+// writer.stepIn(IonType.LIST);
+// // If constraint name is 'element', only one type of Ion Data is specified.
+// if (constraintStruct.get(IonSchemaUtilities.KEYWORD_ELEMENT) != null && containerLength != null) {
+// IonType type = IonType.valueOf(constraintStruct.get(IonSchemaUtilities.KEYWORD_ELEMENT).toString().toUpperCase());
+// for (int i = 0; i < containerLength; i++) {
+// occurrences = 1;
+// WriteRandomIonValues.constructScalarTypeData(type, writer, occurrences, constraintStruct);
+// }
+// break;
+// } else if (constraintStruct.get(IonSchemaUtilities.KEYWORD_ORDERED_ELEMENTS) != null) {
+// IonList orderedElement = (IonList) constraintStruct.get(IonSchemaUtilities.KEYWORD_ORDERED_ELEMENTS);
+// for (int index = 0; index < orderedElement.size(); index++) {
+// IonType elementType = orderedElement.get(index).getType();
+// IonType valueType;
+// switch (elementType) {
+// case SYMBOL:
+// occurrences = 1;
+// valueType = IonType.valueOf(orderedElement.get(index).toString().toUpperCase());
+// WriteRandomIonValues.constructScalarTypeData(valueType, writer, occurrences, NO_CONSTRAINT_STRUCT);
+// break;
+// case STRUCT:
+// IonStruct constraintsStruct = (IonStruct) orderedElement.get(index);
+// occurrences = IonSchemaUtilities.parseConstraints(constraintsStruct, IonSchemaUtilities.KEYWORD_OCCURS);
+// if(occurrences == 0) {
+// break;
+// }
+// valueType = IonType.valueOf(constraintsStruct.get(IonSchemaUtilities.KEYWORD_TYPE).toString().toUpperCase());
+// WriteRandomIonValues.constructScalarTypeData(valueType, writer, occurrences, constraintsStruct);
+// break;
+// }
+// }
+// writer.stepOut();
+// return;
+// }
+// }
+// writer.stepOut();
+// }
+// }
+
+ /**
+ * Construct scalar type Ion data based on the occurrence time. This method is mainly reused during the process of generating Ion List which will specify the occurrence time.
+ * @param valueType is IonType of the data needed to be written in Ion List.
+ * @param writer is IonWriter.
+ * @param occurTime is the occurrence time of the element in Ion List.
+ * @throws IOException if errors occur when writing data.
+ */
+// public static void constructScalarTypeData(IonType valueType, IonWriter writer, int occurTime, IonStruct constraintStruct) throws Exception {
+// for (int i = 0; i < occurTime; i++) {
+// switch (valueType) {
+// // If more scalar types of Ion data are supported, this is the point to add more cases.
+// case STRING:
+// writer.writeString(WriteRandomIonValues.constructString(constraintStruct));
+// break;
+// case INT:
+// writer.writeInt(WriteRandomIonValues.constructInt(constraintStruct));
+// break;
+// default:
+// throw new IllegalStateException(valueType + " is not supported when generating Ion List based on Ion Schema.");
+// }
+// }
+// }
+}
diff --git a/src/com/amazon/ion/benchmark/GeneratorOptions.java b/src/com/amazon/ion/benchmark/GeneratorOptions.java
index decb18b..5e63f95 100644
--- a/src/com/amazon/ion/benchmark/GeneratorOptions.java
+++ b/src/com/amazon/ion/benchmark/GeneratorOptions.java
@@ -1,5 +1,6 @@
package com.amazon.ion.benchmark;
+import com.amazon.ionschema.Schema;
import java.util.List;
import java.util.Map;
@@ -19,8 +20,8 @@ public static void executeGenerator(Map optionsMap) throws Excep
String format = ((List) optionsMap.get("--format")).get(0);
String path = optionsMap.get("").toString();
String inputFilePath = optionsMap.get("--input-ion-schema").toString();
- // Check whether the input schema file is valid.
- IonSchemaUtilities.checkValidationOfSchema(inputFilePath);
- ReadGeneralConstraints.readIonSchemaAndGenerate(size, inputFilePath, format, path);
+ // Check whether the input schema file is valid and get the loaded schema.
+ Schema schema = IonSchemaUtilities.loadSchemaDefinition(inputFilePath);
+ ReadGeneralConstraints.constructAndWriteIonData(size, schema, format, path);
}
}
diff --git a/src/com/amazon/ion/benchmark/IonSchemaUtilities.java b/src/com/amazon/ion/benchmark/IonSchemaUtilities.java
index bd41825..2f7358e 100644
--- a/src/com/amazon/ion/benchmark/IonSchemaUtilities.java
+++ b/src/com/amazon/ion/benchmark/IonSchemaUtilities.java
@@ -15,8 +15,10 @@
import com.amazon.ionschema.InvalidSchemaException;
import com.amazon.ionschema.IonSchemaSystem;
import com.amazon.ionschema.IonSchemaSystemBuilder;
+import com.amazon.ionschema.Schema;
import java.io.IOException;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -52,18 +54,23 @@ public class IonSchemaUtilities {
private static final IonLoader LOADER = SYSTEM.newLoader();
/**
- * Check the validation of input ion schema file and will throw InvalidSchemaException message when an invalid schema definition is encountered.
+ * Load schema definition and check the validation of input ion schema file.
+ * If an invalid schema definition is encountered, this method will throw InvalidSchemaException message.
* @param inputFile represents the file path of the ion schema file.
- * @throws Exception if an error occur when creating FileInputStream.
+ * @return schema loaded from input ISL file.
*/
- public static void checkValidationOfSchema(String inputFile) throws Exception {
+ public static Schema loadSchemaDefinition(String inputFile) {
+ // Build ion schema system from input ISL file.
IonSchemaSystem ISS = buildIonSchemaSystem(inputFile);
- String schemaID = inputFile.substring(inputFile.lastIndexOf('/') + 1);
+ // Get the name of ISL file as schema ID.
+ String schemaID = Paths.get(inputFile).toFile().getName();
+ // If the input ISL file is not validated by ion schema kotlin, it will throw an error.
+ // If the input ISL file is valid, the loaded schema will be returned.
try {
- ISS.loadSchema(schemaID);
+ return ISS.loadSchema(schemaID);
} catch (InvalidSchemaException e) {
System.out.println(e.getMessage());
- throw new Exception("The provided ion schema file is not valid");
+ throw new IllegalStateException("The provided ion schema file is not valid");
}
}
diff --git a/src/com/amazon/ion/benchmark/ReadGeneralConstraints.java b/src/com/amazon/ion/benchmark/ReadGeneralConstraints.java
index 6df9011..e8a9f83 100644
--- a/src/com/amazon/ion/benchmark/ReadGeneralConstraints.java
+++ b/src/com/amazon/ion/benchmark/ReadGeneralConstraints.java
@@ -1,52 +1,83 @@
package com.amazon.ion.benchmark;
-import com.amazon.ion.IonDatagram;
import com.amazon.ion.IonLoader;
-import com.amazon.ion.IonReader;
-import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
-import com.amazon.ion.IonType;
import com.amazon.ion.IonValue;
import com.amazon.ion.IonWriter;
-import com.amazon.ion.system.IonReaderBuilder;
+import com.amazon.ion.benchmark.schema.ReparsedType;
+import com.amazon.ion.system.IonBinaryWriterBuilder;
import com.amazon.ion.system.IonSystemBuilder;
+import com.amazon.ion.system.IonTextWriterBuilder;
+import com.amazon.ionschema.Schema;
+import com.amazon.ionschema.Type;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
/**
- * Parse Ion Schema file and get the general constraints in the file then pass the constraints to the Ion data generator.
+ * Parse Ion Schema file and extract the type definition as ReparsedType object then pass the re-parsed type definition to the Ion data generator.
*/
public class ReadGeneralConstraints {
public static final IonSystem SYSTEM = IonSystemBuilder.standard().build();
public static final IonLoader LOADER = SYSTEM.newLoader();
/**
- * Get general constraints of Ion Schema and call the relevant generator method based on the type.
+ * Getting the constructed data which is conformed with ISL and writing data to the output file.
* @param size is the size of the output file.
- * @param path is the path of the Ion Schema file.
+ * @param schema an Ion Schema loaded by ion-schema-kotlin.
* @param format is the format of the generated file, select from set (ion_text | ion_binary).
* @param outputFile is the path of the generated file.
- * @throws Exception if errors occur when reading and writing data.
+ * @throws Exception if errors occur when writing data.
*/
- public static void readIonSchemaAndGenerate(int size, String path, String format, String outputFile) throws Exception {
- try (IonReader reader = IonReaderBuilder.standard().build(new BufferedInputStream(new FileInputStream(path)))) {
- IonDatagram schema = LOADER.load(reader);
- for (int i = 0; i < schema.size(); i++) {
- IonValue schemaValue = schema.get(i);
- // Assume there's only one constraint between schema_header and schema_footer, if more constraints added, here is the point where developers should start.
- if (schemaValue.getType().equals(IonType.STRUCT) && schemaValue.getTypeAnnotations()[0].equals(IonSchemaUtilities.KEYWORD_TYPE)) {
- IonStruct constraintStruct = (IonStruct) schemaValue;
- //Construct the writer and pass the constraints to the following writing data to files process.
- File file = new File(outputFile);
- try (IonWriter writer = WriteRandomIonValues.formatWriter(format, file)) {
- WriteRandomIonValues.writeRequestedSizeFile(size, writer, file, constraintStruct);
- }
- // Print the successfully generated data notification which includes the file path information.
- WriteRandomIonValues.printInfo(outputFile);
+ public static void constructAndWriteIonData(int size, Schema schema, String format, String outputFile) throws Exception {
+ // Assume there's only one type definition between schema_header and schema_footer.
+ // If more constraints added, here is the point where developers should start.
+ Type schemaType = schema.getTypes().next();
+ ReparsedType parsedTypeDefinition = new ReparsedType(schemaType);
+ CountingOutputStream outputStreamCounter = new CountingOutputStream(new FileOutputStream(outputFile));
+ try (IonWriter writer = formatWriter(format, outputStreamCounter)) {
+ int count = 0;
+ long currentSize = 0;
+ // Determine how many values should be written before the writer.flush(), and this process aims to reduce the execution time of writer.flush().
+ while (currentSize <= 0.05 * size) {
+ IonValue constructedData = DataConstructor.constructIonData(parsedTypeDefinition);
+ constructedData.writeTo(writer);
+ count ++;
+ writer.flush();
+ currentSize = outputStreamCounter.getCount();
+ }
+ while (currentSize <= size) {
+ for (int i = 0; i < count; i++) {
+ IonValue constructedData = DataConstructor.constructIonData(parsedTypeDefinition);
+ constructedData.writeTo(writer);
}
+ writer.flush();
+ currentSize = outputStreamCounter.getCount();
}
}
+ // Print the successfully generated data notification which includes the file path information.
+ DataConstructor.printInfo(outputFile);
+ }
+
+ /**
+ * Construct the writer based on the provided format (ion_text|ion_binary).
+ * @param format decides which writer should be constructed.
+ * @param outputStream represents the bytes stream which will be written into the output file.
+ * @return the writer which conforms with the required format.
+ */
+ public static IonWriter formatWriter(String format, OutputStream outputStream) {
+ IonWriter writer;
+ Format formatName = Format.valueOf(format.toUpperCase());
+ switch (formatName) {
+ case ION_BINARY:
+ writer = IonBinaryWriterBuilder.standard().withLocalSymbolTableAppendEnabled().build(outputStream);
+ break;
+ case ION_TEXT:
+ writer = IonTextWriterBuilder.standard().build(outputStream);
+ break;
+ default:
+ throw new IllegalStateException("Please input the format ion_text or ion_binary");
+ }
+ return writer;
}
}
diff --git a/src/com/amazon/ion/benchmark/WriteRandomIonValues.java b/src/com/amazon/ion/benchmark/WriteRandomIonValues.java
deleted file mode 100644
index 0e2d624..0000000
--- a/src/com/amazon/ion/benchmark/WriteRandomIonValues.java
+++ /dev/null
@@ -1,689 +0,0 @@
-package com.amazon.ion.benchmark;
-
-/*
- * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-import com.amazon.ion.IonList;
-import com.amazon.ion.IonReader;
-import com.amazon.ion.IonStruct;
-import com.amazon.ion.IonSystem;
-import com.amazon.ion.IonType;
-import com.amazon.ion.IonValue;
-import com.amazon.ion.IonWriter;
-import com.amazon.ion.Timestamp;
-import com.amazon.ion.system.IonBinaryWriterBuilder;
-import com.amazon.ion.system.IonReaderBuilder;
-import com.amazon.ion.system.IonSystemBuilder;
-import com.amazon.ion.system.IonTextWriterBuilder;
-import com.github.curiousoddman.rgxgen.RgxGen;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
-
-/**
- * Generate specific scalar type of Ion data randomly, for some specific type, e.g. String, Decimal, Timestamp, users can put specifications on these types of Ion data.
- */
-class WriteRandomIonValues {
- // The constant defined below are used as placeholder in the method WriteRandomIonValues.writeRequestedSizeFile.
- final static private IonSystem SYSTEM = IonSystemBuilder.standard().build();
- final static private List DEFAULT_RANGE = Arrays.asList(0, 0x10FFFF);
- final static public Timestamp.Precision[] PRECISIONS = Timestamp.Precision.values();
- final static public IonStruct NO_CONSTRAINT_STRUCT = null;
- final static private int DEFAULT_PRECISION = 20;
- final static private int DEFAULT_SCALE_LOWER_BOUND = -20;
- final static private int DEFAULT_SCALE_UPPER_BOUND = 20;
-
- /**
- * Build up the writer based on the provided format (ion_text|ion_binary)
- * @param format the option to decide which writer to be constructed.
- * @param file the generated file which contains specified Ion data.
- * @return the writer which conforms with the required format.
- * @throws Exception if an error occurs while creating a file output stream.
- */
- public static IonWriter formatWriter(String format, File file) throws Exception {
- IonWriter writer;
- OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
- Format formatName = Format.valueOf(format.toUpperCase());
- switch (formatName) {
- case ION_BINARY:
- writer = IonBinaryWriterBuilder.standard().withLocalSymbolTableAppendEnabled().build(out);
- break;
- case ION_TEXT:
- writer = IonTextWriterBuilder.standard().build(out);
- break;
- default:
- throw new IllegalStateException("Please input the format ion_text or ion_binary");
- }
- return writer;
- }
-
- /**
- * Use Ion-java parser to parse the data provided in the options which specify the range of data.
- * @param range the range needed to be parsed, normally in the format of "[Integer, Integer]"
- * @return a list of Integer which will be extracted in the following executions.
- */
- public static List parseRange(String range) {
- IonReaderBuilder readerBuilder = IonReaderBuilder.standard();
- IonReader reader = readerBuilder.build(range);
- if (reader.next() != IonType.LIST) throw new IllegalStateException("Please provide a list type");
- reader.stepIn();
- List result = new ArrayList<>();
- while (reader.next() != null) {
- if (reader.getType() != IonType.INT) throw new IllegalStateException("Please put integer elements inside of the list");
- int value = reader.intValue();
- result.add(value);
- }
- reader.stepOut();
-
- if (reader.next() != null) throw new IllegalStateException("Only one list is accepted");
- if (result.get(0) > result.get(1)) throw new IllegalStateException("The value of the lower bound should be smaller than the upper bound");
- if (result.size() != 2) throw new IllegalStateException("Please put two integers inside of the list");
- return result;
- }
-
- /**
- * Print the successfully generated data notification which includes the file path information.
- * @param path identifies the output file path.
- */
- public static void printInfo(String path) {
- String fileName = path.substring(path.lastIndexOf("/") + 1);
- System.out.println(fileName + " generated successfully ! ");
- if (fileName.equals(path)) {
- System.out.println("Generated data is under the current directory");
- } else {
- System.out.println("File path: " + path);
- }
- }
-
- /**
- *This method is not available now
- */
- private static void writeListOfRandomFloats() throws Exception {
- File file = new File("manyLargeListsOfRandomFloats.10n");
- try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
- IonWriter writer = IonBinaryWriterBuilder.standard().withFloatBinary32Enabled().build(out)) {
- Random random = new Random();
- for (int j = 0; j < 100; j++) {
- writer.stepIn(IonType.LIST);
- // Target about 1MB of data. Floats will average at 7 bytes, and we're writing 2
- // per iteration.
- for (int i = 0; i < (1_000_000 / 7 / 2); i++) {
- writer.writeFloat(Double.longBitsToDouble(random.nextLong()));
- writer.writeFloat(Float.intBitsToFloat(random.nextInt()));
- }
- writer.stepOut();
- }
- }
- System.out.println("Finished writing floats. Verifying.");
- try (IonReader reader = IonReaderBuilder.standard().build(new BufferedInputStream(new FileInputStream(file)))) {
- while (reader.next() != null) {
- reader.stepIn();
- while (reader.next() != null) {
- if (reader.getType() != IonType.FLOAT) {
- throw new IllegalStateException("Found non-float");
- }
- double value = reader.doubleValue();
- }
- reader.stepOut();
- }
- }
- System.out.println("Done. Size: " + file.length());
- }
-
- /**
- * Generate random offset without any specification
- * @param random is the random number generator.
- * @return random offset [Z(+00:00) | -00:00 | random offset].
- */
- private static Integer localOffset(Random random) {
- // Offsets are in minutes, [-23:59, 23:59], i.e. [-1439, 1439].
- // The most common offset is Z (00:00), while unknown (-00:00) may also be common.
- Integer offsetMinutes = random.nextInt(6000) - 2000;
- if (offsetMinutes > 1439) {
- // This means about 43% of timestamps will have offset Z (UTC).
- offsetMinutes = 0;
- } else if (offsetMinutes < -1439) {
- // This means about 9% of timestamps will have unknown offset (-00:00).
- offsetMinutes = null;
- }
- return offsetMinutes;
- }
-
- /**
- * Generate random fractional second which the precision conforms with the precision of the second in template timestamp.
- * @param random is the random number generator.
- * @param scale is the scale of the decimal second in the current template timestamp.
- * @return a random timestamp second in a fractional format which conforms with the current template timestamp.
- */
- private static BigDecimal randomSecondWithFraction(Random random, int scale) {
- int second = random.nextInt(60);
- if (scale != 0) {
- StringBuilder coefficientStr = new StringBuilder();
- for (int digit = 0; digit < scale; digit++) {
- coefficientStr.append(random.nextInt(10));
- }
- BigDecimal fractional = new BigDecimal(coefficientStr.toString());
- BigDecimal fractionalSecond = fractional.scaleByPowerOfTen(scale * (-1));
- return fractionalSecond.add(BigDecimal.valueOf(second));
- } else {
- return BigDecimal.valueOf(second);
- }
- }
-
- /**
- * This method is not available now.
- * @throws Exception
- */
- private static void writeRandomAnnotatedFloats() throws Exception {
- File file = new File("randomAnnotatedFloats.10n");
- List annotations = new ArrayList<>(500);
- Random random = new Random();
- for (int i = 0; i < 500; i++) {
- int length = random.nextInt(20);
- StringBuilder sb = new StringBuilder();
- for (int j = 0; j < length; j++) {
- int codePoint;
- int type;
- do {
- codePoint = random.nextInt(Character.MAX_CODE_POINT);
- type = Character.getType(codePoint);
- } while (type == Character.PRIVATE_USE || type == Character.SURROGATE || type == Character.UNASSIGNED);
- sb.appendCodePoint(codePoint);
- }
- annotations.add(sb.toString());
- }
- try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
- IonWriter writer = IonBinaryWriterBuilder.standard().build(out)) {
- // Target about 100MB of data. Annotated floats will average around 14 bytes.
- for (int i = 0; i < (100_000_000 / 14); i++) {
- // 60% of values will have 1 annotation; 40% will have 2 or 3.
- int numberOfAnnotations = random.nextInt(5) + 1;
- if (numberOfAnnotations > 3) {
- numberOfAnnotations = 1;
- }
- for (int j = 0; j < numberOfAnnotations; j++) {
- writer.addTypeAnnotation(annotations.get(random.nextInt(500)));
- }
- writer.writeFloat(Double.longBitsToDouble(random.nextLong()));
- }
- }
- }
-
- /**
- * This method is not available now. Refactor required.
- * @param size specifies the size in bytes of the generated file.
- * @param format the format of output file (ion_binary | ion_text).
- * @param path the destination of the generated file.
- * @throws Exception if an error occurs when building up the writer.
- */
- public static void writeRandomSymbolValues(int size, String format, String path) throws Exception {
- File file = new File(path);
- try (IonWriter writer = WriteRandomIonValues.formatWriter(format, file)) {
- List symbols = new ArrayList<>(500);
- Random random = new Random();
- for (int i = 0; i < 500; i++) {
- int length = random.nextInt(20);
- StringBuilder sb = new StringBuilder();
- for (int j = 0; j < length; j++) {
- int codePoint;
- int charactereType;
- do {
- codePoint = random.nextInt(Character.MAX_CODE_POINT);
- charactereType = Character.getType(codePoint);
- } while (charactereType == Character.PRIVATE_USE || charactereType == Character.SURROGATE || charactereType == Character.UNASSIGNED);
- sb.appendCodePoint(codePoint);
- }
- symbols.add(sb.toString());
- }
- for (int i = 0; i < size / 2; i++) {
- writer.writeSymbol(symbols.get(random.nextInt(500)));
- }
- }
- WriteRandomIonValues.printInfo(path);
- }
-
- /**
- * This method is used for generating requested size file by comparing the current file size and the target size.
- * @param size specifies the size in bytes of the generated file.
- * @param writer writer is IonWriter.
- * @param file the generated file which contains specified Ion data.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @throws IOException if an error occur when writing generated data.
- */
- public static void writeRequestedSizeFile(int size, IonWriter writer, File file, IonStruct constraintStruct) throws Exception {
- int currentSize = 0;
- int count = 0;
- // Determine how many values should be written before the writer.flush(), and this process aims to reduce the execution time of writer.flush().
- while (currentSize <= 0.05 * size) {
- WriteRandomIonValues.writeDataToFile(writer, constraintStruct);
- count += 1;
- writer.flush();
- currentSize = (int) file.length();
- }
- while (currentSize <= size) {
- for (int i = 0; i < count; i++) {
- WriteRandomIonValues.writeDataToFile(writer, constraintStruct);
- }
- writer.flush();
- currentSize = (int) file.length();
- }
- }
-
- /**
- * This method will be reused by different data generator
- * @param writer writer is IonWriter.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @throws IOException if an error occur during the data writing process.
- */
- private static void writeDataToFile(IonWriter writer, IonStruct constraintStruct) throws Exception {
- IonType type = IonType.valueOf(constraintStruct.get(IonSchemaUtilities.KEYWORD_TYPE).toString().toUpperCase());
- IonValue value = null;
- // Check whether the 'valid_values' constraints provided in the top level constraint struct.
- // If a list of 'valid_values' provided, the generated value should be selected randomly from the provided 'valid_values' list every iteration.
- // Constraint 'valid_values' has three formats. >, > and [ ... ]. This step only check the format [ ... ].
- // If the annotation 'range' has been detected, it will be processed in the constructing data steps.
- if (constraintStruct != null) {
- value = IonSchemaUtilities.parseValidValues(constraintStruct);
- }
- if (value != null && IonSchemaUtilities.getConstraintValueAsRange(constraintStruct, IonSchemaUtilities.KEYWORD_VALID_VALUES) == null) {
- value.writeTo(writer);
- } else {
- switch (type) {
- case FLOAT:
- writer.writeFloat(WriteRandomIonValues.constructFloat(constraintStruct));
- break;
- case SYMBOL:
- writer.writeSymbol(WriteRandomIonValues.constructString(constraintStruct));
- break;
- case INT:
- writer.writeInt(WriteRandomIonValues.constructInt(constraintStruct));
- break;
- case STRING:
- writer.writeString(WriteRandomIonValues.constructString(constraintStruct));
- break;
- case DECIMAL:
- writer.writeDecimal(WriteRandomIonValues.constructDecimal(constraintStruct));
- break;
- case TIMESTAMP:
- writer.writeTimestamp(WriteRandomIonValues.constructTimestamp(constraintStruct));
- break;
- case STRUCT:
- WriteRandomIonValues.constructAndWriteIonStruct(constraintStruct, writer);
- break;
- case LIST:
- WriteRandomIonValues.constructAndWriteIonList(writer, constraintStruct);
- break;
- case BLOB:
- writer.writeBlob(WriteRandomIonValues.constructLobs(constraintStruct));
- break;
- case CLOB:
- writer.writeClob(WriteRandomIonValues.constructLobs(constraintStruct));
- break;
- default:
- throw new IllegalStateException(type + " is not supported.");
- }
- }
- }
-
- /**
- * Construct string which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @return constructed string.
- * @throws Exception if error occurs when parsing the constraints.
- */
- public static String constructString(IonStruct constraintStruct) throws Exception {
- Random random = new Random();
- String constructedString;
- String regexPattern = IonSchemaUtilities.parseTextConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_REGEX);
- Integer codePointsLengthBound = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_CODE_POINT_LENGTH);
- // For now, if there are potentially-conflicting constraints detected, an exception statement will be thrown.
- // For more information: https://github.com/amzn/ion-java-benchmark-cli/issues/33
- if (regexPattern != null && codePointsLengthBound != null) {
- throw new IllegalStateException("This constraints combination can not be processed in Ion Data Generator.");
- } else if (regexPattern == null && codePointsLengthBound != null) {
- // Construct string with the specified Unicode codepoints length.
- constructedString = constructStringFromCodepointLength(codePointsLengthBound);
- } else if (regexPattern != null && codePointsLengthBound == null) {
- RgxGen rgxGen = new RgxGen(regexPattern);
- constructedString = rgxGen.generate();
- } else {
- // Preset the Unicode codepoints length as average number 20;
- codePointsLengthBound = random.nextInt(20);
- constructedString = constructStringFromCodepointLength(codePointsLengthBound);
- }
- return constructedString;
- }
-
- /**
- * Generate unicode codepoint randomly.
- * @return generated codepoint.
- */
- private static int getCodePoint() {
- Random random = new Random();
- int type;
- int codePoint;
- do {
- codePoint = random.nextInt(DEFAULT_RANGE.get(1) - DEFAULT_RANGE.get(0) + 1) + DEFAULT_RANGE.get(0);
- type = Character.getType(codePoint);
- } while (type == Character.PRIVATE_USE || type == Character.SURROGATE || type == Character.UNASSIGNED);
- return codePoint;
- }
-
- /**
- * Construct string which is conformed with the provided codepoint_length.
- * @param codePointsLengthBound represents the exact number of Unicode codepoints in a string or symbol.
- * @return the constructed string.
- */
- private static String constructStringFromCodepointLength(int codePointsLengthBound) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < codePointsLengthBound; i++) {
- int codePoint = getCodePoint();
- sb.appendCodePoint(codePoint);
- }
- String constructedString = sb.toString();
- return constructedString;
- }
-
- /**
- * Construct the float which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @return the constructed double value.
- * @throws Exception if error occurs when getting the constraints value.
- */
- public static double constructFloat(IonStruct constraintStruct) throws Exception {
- double randomDouble;
- IonList range = IonSchemaUtilities.getConstraintValueAsRange(constraintStruct, IonSchemaUtilities.KEYWORD_VALID_VALUES);
- if (range != null) {
- // Extract the value of 'valid_values:range:: [lowerBound, upperBound]' and convert IonValue to double.
- double lowerBound = Double.valueOf(range.get(0).toString());
- double upperBound = Double.valueOf(range.get(1).toString());
- randomDouble = ThreadLocalRandom.current().nextDouble(lowerBound, upperBound);
- } else {
- randomDouble = ThreadLocalRandom.current().nextDouble();
- }
- return randomDouble;
- }
-
- /**
- * Construct the decimal which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @return the constructed decimal.
- * @throws Exception if error occurs when parsing the constraints.
- */
- public static BigDecimal constructDecimal(IonStruct constraintStruct) throws Exception {
- Random random = new Random();
- // precision represents the minimum/maximum range indicating the number of digits in the unscaled value of a decimal. The minimum precision must be greater than or equal to 1.
- Integer precision = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_PRECISION);
- if (precision == null) {
- precision = random.nextInt(DEFAULT_PRECISION);
- }
- // scale represents the minimum/maximum range indicating the number of digits to the right of the decimal point. The minimum scale must be greater than or equal to 0.
- Integer scale = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_SCALE);
- if (scale == null) {
- scale = random.nextInt(DEFAULT_SCALE_UPPER_BOUND - DEFAULT_SCALE_LOWER_BOUND + 1) + DEFAULT_SCALE_LOWER_BOUND;
- }
- StringBuilder rs = new StringBuilder();
- rs.append(random.nextInt(9) + 1);
- for (int digit = 1; digit < precision; digit++) {
- rs.append(random.nextInt(10));
- }
- BigInteger unscaledValue = new BigInteger(rs.toString());
- BigDecimal bigDecimal = new BigDecimal(unscaledValue, scale);
- return bigDecimal;
- }
-
- /**
- * Generate random integers which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @return constructed integers
- * @throws Exception if error occurs when parsing the constraint 'valid_values'.
- */
- public static long constructInt(IonStruct constraintStruct) throws Exception {
- long longValue;
- IonList range = IonSchemaUtilities.getConstraintValueAsRange(constraintStruct, IonSchemaUtilities.KEYWORD_VALID_VALUES);
- if (range != null) {
- // Convert IonValue to long
- long lowerBound = Long.valueOf(range.get(0).toString());
- long upperBound = Long.valueOf(range.get(1).toString());
- longValue = ThreadLocalRandom.current().nextLong(lowerBound, upperBound);
- } else {
- longValue = ThreadLocalRandom.current().nextLong();
- }
- return longValue;
- }
-
- /**
- * Construct timestamp which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @return Constructed timestamp
- * @throws Exception if error occurs when parsing the constraints.
- */
- public static Timestamp constructTimestamp(IonStruct constraintStruct) throws Exception {
- Timestamp timestamp;
- Random random = new Random();
- int randomIndex = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_TIMESTAMP_PRECISION);
- Timestamp.Precision precision = PRECISIONS[randomIndex];
- switch (precision) {
- case YEAR:
- timestamp = Timestamp.forYear(random.nextInt(9998) + 1);
- break;
- case MONTH:
- timestamp = Timestamp.forMonth(random.nextInt(9998) + 1, random.nextInt(12) + 1);
- break;
- case DAY:
- timestamp = Timestamp.forDay(
- random.nextInt(9998) + 1,
- random.nextInt(12) + 1,
- random.nextInt(28) + 1 // Use max 28 for simplicity. Not including up to 31 is not going to affect the measurement.
- );
- break;
- case MINUTE:
- timestamp = Timestamp.forMinute(
- random.nextInt(9998) + 1,
- random.nextInt(12) + 1,
- random.nextInt(28) + 1, // Use max 28 for simplicity. Not including up to 31 is not going to affect the measurement.
- random.nextInt(24),
- random.nextInt(60),
- localOffset(random)
- );
- break;
- case SECOND:
- timestamp = Timestamp.forSecond(
- random.nextInt(9998) + 1,
- random.nextInt(12) + 1,
- random.nextInt(28) + 1, // Use max 28 for simplicity. Not including up to 31 is not going to affect the measurement.
- random.nextInt(24),
- random.nextInt(60),
- random.nextInt(60),
- localOffset(random)
- );
- break;
- case FRACTION:
- int scale = random.nextInt(20);
- timestamp = Timestamp.forSecond(
- random.nextInt(9998) + 1,
- random.nextInt(12) + 1,
- random.nextInt(28) + 1, // Use max 28 for simplicity. Not including up to 31 is not going to affect the measurement.
- random.nextInt(24),
- random.nextInt(60),
- randomSecondWithFraction(random,scale),
- localOffset(random)
- );
- break;
- default:
- throw new IllegalStateException();
- }
- return timestamp;
- }
-
- /**
- * Construct clob/blob which is conformed with the constraints provided in ISL.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @throws Exception if an error occurs when parsing constraints.
- */
- public static byte[] constructLobs(IonStruct constraintStruct) throws Exception {
- int byte_length;
- Random random = new Random();
- if (constraintStruct != null) {
- byte_length = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_BYTE_LENGTH);
- } else {
- byte_length = random.nextInt(512);
- }
- byte[] randomBytes = new byte[byte_length];
- random.nextBytes(randomBytes);
- return randomBytes;
- }
-
- /**
- * Construct and write Ion structs based on the provided constraints.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @param writer writes Ion struct data.
- * @throws IOException if errors occur when writing data.
- */
- public static void constructAndWriteIonStruct(IonStruct constraintStruct, IonWriter writer) throws Exception {
- Random random = new Random();
- IonList annotations = IonSchemaUtilities.getAnnotation(constraintStruct);
- IonStruct fields = (IonStruct) constraintStruct.get(IonSchemaUtilities.KEYWORD_FIELDS);
- try (IonReader reader = IonReaderBuilder.standard().build(fields)) {
- reader.next();
- reader.stepIn();
- for (int i = 0; i < annotations.size(); i++) {
- writer.addTypeAnnotation(annotations.get(i).toString());
- }
- writer.stepIn(IonType.STRUCT);
- while (reader.next() != null) {
- String fieldName = reader.getFieldName();
- IonValue struct = SYSTEM.newValue(reader);
- IonStruct value = (IonStruct) struct;
- // If the value of "occurs" is optional, the integer represents this value is 1 or 0.
- int occurTime = IonSchemaUtilities.parseConstraints(value, IonSchemaUtilities.KEYWORD_OCCURS);
- if (occurTime == 0) {
- continue;
- }
- writer.setFieldName(fieldName);
- IonType type = IonType.valueOf(value.get(IonSchemaUtilities.KEYWORD_TYPE).toString().toUpperCase());
- switch (type) {
- // If more types of Ion data are available, the logic should be added below.
- case STRING:
- writer.writeString(WriteRandomIonValues.constructString(value));
- break;
- case TIMESTAMP:
- writer.writeTimestamp(WriteRandomIonValues.constructTimestamp(value));
- break;
- case LIST:
- WriteRandomIonValues.constructAndWriteIonList(writer, value);
- break;
- default:
- throw new IllegalStateException(type + " is not supported when generating Ion Struct based on Ion Schema.");
- }
- }
- writer.stepOut();
- }
- }
-
- /**
- * Construct Ion List based on the constraints provided by Ion Schema.
- * @param writer is Ion Writer.
- * @param constraintStruct is an IonStruct which contains the top-level constraints in Ion Schema.
- * @throws Exception if errors occur when reading or writing data.
- */
- public static void constructAndWriteIonList(IonWriter writer, IonStruct constraintStruct) throws Exception {
- // When there's only one required element in Ion List and the length of generated Ion List is not specified, we set the default length as a integer smaller than 20.
- Integer containerLength = IonSchemaUtilities.parseConstraints(constraintStruct, IonSchemaUtilities.KEYWORD_CONTAINER_LENGTH);
- IonList annotations = IonSchemaUtilities.getAnnotation(constraintStruct);
- int occurrences;
- try (IonReader reader = IonReaderBuilder.standard().build(constraintStruct)) {
- reader.next();
- reader.stepIn();
- while (reader.next() != null) {
- if (annotations != null) {
- for (int i = 0; i < annotations.size(); i++) {
- writer.addTypeAnnotation(annotations.get(i).toString());
- }
- }
- writer.stepIn(IonType.LIST);
- // If constraint name is 'element', only one type of Ion Data is specified.
- if (constraintStruct.get(IonSchemaUtilities.KEYWORD_ELEMENT) != null && containerLength != null) {
- IonType type = IonType.valueOf(constraintStruct.get(IonSchemaUtilities.KEYWORD_ELEMENT).toString().toUpperCase());
- for (int i = 0; i < containerLength; i++) {
- occurrences = 1;
- WriteRandomIonValues.constructScalarTypeData(type, writer, occurrences, constraintStruct);
- }
- break;
- } else if (constraintStruct.get(IonSchemaUtilities.KEYWORD_ORDERED_ELEMENTS) != null) {
- IonList orderedElement = (IonList) constraintStruct.get(IonSchemaUtilities.KEYWORD_ORDERED_ELEMENTS);
- for (int index = 0; index < orderedElement.size(); index++) {
- IonType elementType = orderedElement.get(index).getType();
- IonType valueType;
- switch (elementType) {
- case SYMBOL:
- occurrences = 1;
- valueType = IonType.valueOf(orderedElement.get(index).toString().toUpperCase());
- WriteRandomIonValues.constructScalarTypeData(valueType, writer, occurrences, NO_CONSTRAINT_STRUCT);
- break;
- case STRUCT:
- IonStruct constraintsStruct = (IonStruct) orderedElement.get(index);
- occurrences = IonSchemaUtilities.parseConstraints(constraintsStruct, IonSchemaUtilities.KEYWORD_OCCURS);
- if(occurrences == 0) {
- break;
- }
- valueType = IonType.valueOf(constraintsStruct.get(IonSchemaUtilities.KEYWORD_TYPE).toString().toUpperCase());
- WriteRandomIonValues.constructScalarTypeData(valueType, writer, occurrences, constraintsStruct);
- break;
- }
- }
- writer.stepOut();
- return;
- }
- }
- writer.stepOut();
- }
- }
-
- /**
- * Construct scalar type Ion data based on the occurrence time. This method is mainly reused during the process of generating Ion List which will specify the occurrence time.
- * @param valueType is IonType of the data needed to be written in Ion List.
- * @param writer is IonWriter.
- * @param occurTime is the occurrence time of the element in Ion List.
- * @throws IOException if errors occur when writing data.
- */
- public static void constructScalarTypeData(IonType valueType, IonWriter writer, int occurTime, IonStruct constraintStruct) throws Exception {
- for (int i = 0; i < occurTime; i++) {
- switch (valueType) {
- // If more scalar types of Ion data are supported, this is the point to add more cases.
- case STRING:
- writer.writeString(WriteRandomIonValues.constructString(constraintStruct));
- break;
- case INT:
- writer.writeInt(WriteRandomIonValues.constructInt(constraintStruct));
- break;
- default:
- throw new IllegalStateException(valueType + " is not supported when generating Ion List based on Ion Schema.");
- }
- }
- }
-}
diff --git a/src/com/amazon/ion/benchmark/schema/ReparsedType.java b/src/com/amazon/ion/benchmark/schema/ReparsedType.java
new file mode 100644
index 0000000..e58653d
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/ReparsedType.java
@@ -0,0 +1,113 @@
+package com.amazon.ion.benchmark.schema;
+
+import com.amazon.ion.IonStruct;
+import com.amazon.ion.IonType;
+import com.amazon.ion.IonValue;
+import com.amazon.ion.benchmark.schema.constraints.*;
+import com.amazon.ionschema.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// Parsing the type definition in ISL file into ReparsedType format which allows getting constraints information directly.
+public class ReparsedType {
+ public final Type type;
+ private static final String KEYWORD_TIMESTAMP_PRECISION = "timestamp_precision";
+ private static final String KEYWORD_TYPE = "type";
+ private static final String KEYWORD_CODE_POINT_LENGTH = "codepoint_length";
+ private static final String KEYWORD_REGEX = "regex";
+ private static final String KEYWORD_CONTAINER_LENGTH = "container_length";
+ private static final String KEYWORD_BYTE_LENGTH = "byte_length";
+ private static final String KEYWORD_SCALE = "scale";
+ private static final String KEYWORD_PRECISION = "precision";
+ private static final String KEYWORD_VALID_VALUES = "valid_values";
+ private static final String KEYWORD_NAME = "name";
+ // Using map to avoid processing the multiple repeat constraints situation.
+ private final Map constraintMap;
+
+ /**
+ * Initializing the newly created ReparsedType object.
+ * @param type represents type definition of ISL file.
+ */
+ public ReparsedType(Type type) {
+ this.type = type;
+ constraintMap = new HashMap<>();
+ getIsl().forEach(this::handleField);
+ }
+
+ /**
+ * Get the name of type definition.
+ * @return the name of type definition.
+ */
+ public String getName() {
+ return type.getName();
+ }
+
+ /**
+ * Handling the fields which are not used for specifying generated data.
+ * @param field represents the field contained by the type definition.
+ */
+ private void handleField(IonValue field) {
+ switch (field.getFieldName()) {
+ case KEYWORD_NAME:
+ case KEYWORD_TYPE:
+ return;
+ default:
+ constraintMap.put(field.getFieldName(), toConstraint(field));
+ }
+ }
+
+ /**
+ * Redefining the getIsl method to convert type definition to IonStruct format.
+ * @return an IonStruct which contains constraints in type definition.
+ */
+ public IonStruct getIsl() {
+ return (IonStruct) type.getIsl();
+ }
+
+ /**
+ * Get the value of constraint 'type' in IonType format.
+ * @return the value of 'type' in IonType format.
+ */
+ public IonType getIonType() {
+ return IonType.valueOf(getIsl().get(KEYWORD_TYPE).toString().toUpperCase());
+ }
+
+ /**
+ * Get the constraintMap.
+ * The keys in constraintMap represent constraint name, and the values represents the ReparsedConstraint.
+ * @return constraintMap.
+ */
+ public Map getConstraintMap() {
+ return constraintMap;
+ }
+
+ //TODO: Constraints come in two flavors - container and scalar?
+ /**
+ * This method helps to categorize constraints based on the data type that they represent.
+ * @param field represents the field contained in type definition.
+ * @return ReparsedConstraints which are processed based on the provided constraint 'type'.
+ */
+ private static ReparsedConstraint toConstraint(IonValue field) {
+ switch (field.getFieldName()) {
+ //TODO: Add cases of constraints 'annotation' and 'occurs'.
+ //TODO: Add container type constraints: 'element', 'ordered_element', 'fields', these might cover some of the implemented constraints.
+ case KEYWORD_BYTE_LENGTH:
+ case KEYWORD_PRECISION:
+ case KEYWORD_SCALE:
+ case KEYWORD_CODE_POINT_LENGTH:
+ case KEYWORD_CONTAINER_LENGTH:
+ return QuantifiableConstraints.of(field);
+ case KEYWORD_VALID_VALUES:
+ return ValidValues.of(field);
+ case KEYWORD_REGEX:
+ return Regex.of(field);
+ case KEYWORD_TIMESTAMP_PRECISION:
+ return TimestampPrecision.of(field);
+ default:
+ // For now, Ion Data Generator doesn't support processing 'open' content.
+ // If the constraint 'content' included in the ISL , the data generator will throw an exception.
+ throw new IllegalArgumentException("This field is not understood: " + field);
+ }
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/QuantifiableConstraints.java b/src/com/amazon/ion/benchmark/schema/constraints/QuantifiableConstraints.java
new file mode 100644
index 0000000..7f9b1a7
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/QuantifiableConstraints.java
@@ -0,0 +1,34 @@
+package com.amazon.ion.benchmark.schema.constraints;
+
+import com.amazon.ion.IonValue;
+
+// This class is used for processing the constraints [codepoint_length | byte_length | precision | scale | container_length].
+// These constraints have two formats of value [ | >].
+public class QuantifiableConstraints implements ReparsedConstraint {
+ private final Range range;
+
+ /**
+ * Initializing the newly created QuantifiableConstraint object.
+ * @param value represents one of [codepoint_length | byte_length | precision | scale | container_length] field value.
+ */
+ public QuantifiableConstraints(IonValue value) {
+ this.range = Range.of(value);
+ }
+
+ /**
+ * Parsing constraint field into QuantifiableConstraints.
+ * @param field represents the value of constraint.
+ * @return newly created QuantifiableConstraints object.
+ */
+ public static QuantifiableConstraints of(IonValue field) {
+ return new QuantifiableConstraints(field);
+ }
+
+ /**
+ * Getting the range value if the constraint value contains annotation 'range'.
+ * @return object Range which represents constraint value.
+ */
+ public Range getRange() {
+ return range;
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/Range.java b/src/com/amazon/ion/benchmark/schema/constraints/Range.java
new file mode 100644
index 0000000..525848c
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/Range.java
@@ -0,0 +1,91 @@
+package com.amazon.ion.benchmark.schema.constraints;
+
+import com.amazon.ion.IonList;
+import com.amazon.ion.IonSequence;
+import com.amazon.ion.IonTimestamp;
+import com.amazon.ion.IonType;
+import com.amazon.ion.IonValue;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Random;
+
+// Processing the constraint value which contains 'range' annotation.
+public class Range {
+ private static final String KEYWORD_RANGE = "range";
+ private final IonSequence sequence;
+
+ /**
+ * Initializing the newly created Range object.
+ * @param sequence represents the range value in IonSequence format.
+ */
+ public Range(IonSequence sequence) {
+ this.sequence = sequence;
+ }
+
+ /**
+ * Helping to access the private variable sequence.
+ * @return IonSequence which represents the range value.
+ */
+ public IonSequence getSequence() {
+ return this.sequence;
+ }
+
+ /**
+ * Getting the lower bound value from range.
+ * @param klass represent the Class object of different data types.
+ * @param represents different ion data types.
+ * @return parameterized type data which extends IonValue.
+ */
+ public T lowerBound(Class klass) {
+ return klass.cast(sequence.get(0));
+ }
+
+ /**
+ * Getting the upper bound value from range.
+ * @param klass represent the Class object of different data types.
+ * @param represents different ion data types.
+ * @return parameterized type data which extends IonValue.
+ */
+ public T upperBound(Class klass) {
+ return klass.cast(sequence.get(1));
+ }
+
+ /**
+ * Parsing the provided IonValue into Range.
+ * @param value represents the value of provided constraint.
+ * @return an object of Range.
+ */
+ public static Range of(IonValue value) {
+ IonSequence sequence;
+ if (!(value instanceof IonList)) {
+ sequence = value.getSystem().newList(value.clone(), value.clone());
+ sequence.addTypeAnnotation(KEYWORD_RANGE);
+ } else {
+ sequence = (IonSequence) value;
+ }
+ return new Range(sequence);
+ }
+
+ /**
+ * Checking whether the value contains annotation 'range'.
+ * @param value represents the constraint value.
+ * @return the result in the boolean format.
+ */
+ public static boolean isRange(IonValue value) {
+ return Arrays.stream(value.getTypeAnnotations()).anyMatch(KEYWORD_RANGE::equals);
+ }
+
+ /**
+ * Getting a random quantifiable value within the range. This method will be used when the range value is in '>' format.
+ * @return a BigDecimal which is within the provided range. This value would be cast into different data types as needed.
+ */
+ public BigDecimal getRandomQuantifiableValueFromRange() {
+ Random random = new Random();
+ IonValue lowerBound = sequence.get(0);
+ IonValue upperBound = sequence.get(1);
+ BigDecimal lowerBoundBigDecimal = lowerBound.getType().equals(IonType.TIMESTAMP) ? ((IonTimestamp)lowerBound).getDecimalMillis() : new BigDecimal(lowerBound.toString());
+ BigDecimal upperBoundBigDecimal = upperBound.getType().equals(IonType.TIMESTAMP) ? ((IonTimestamp)upperBound).getDecimalMillis() : new BigDecimal(upperBound.toString());
+ return lowerBoundBigDecimal.add(new BigDecimal(random.nextDouble()).multiply(upperBoundBigDecimal.subtract(lowerBoundBigDecimal)));
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/Regex.java b/src/com/amazon/ion/benchmark/schema/constraints/Regex.java
new file mode 100644
index 0000000..c2423fd
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/Regex.java
@@ -0,0 +1,32 @@
+package com.amazon.ion.benchmark.schema.constraints;
+
+import com.amazon.ion.IonValue;
+
+public class Regex implements ReparsedConstraint{
+ private final String pattern;
+
+ /**
+ * Initializing the newly created object.
+ * @param pattern represents the value of constraint 'regex'.
+ */
+ public Regex(IonValue pattern) {
+ this.pattern = pattern.toString().replace("\"","");
+ }
+
+ /**
+ * Getting the 'regex' value.
+ * @return a String to represent the value of 'regex'.
+ */
+ public String getPattern() {
+ return this.pattern;
+ }
+
+ /**
+ * Parsing constraint field into Regex.
+ * @param value represents the value of constraint 'regex'.
+ * @return newly created Scale object.
+ */
+ public static Regex of(IonValue value) {
+ return new Regex(value);
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/ReparsedConstraint.java b/src/com/amazon/ion/benchmark/schema/constraints/ReparsedConstraint.java
new file mode 100644
index 0000000..ef08444
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/ReparsedConstraint.java
@@ -0,0 +1,9 @@
+package com.amazon.ion.benchmark.schema.constraints;
+/*
+This interface is the abstraction of all constraints. It will be implemented by different constraint classes which have different domain knowledge.
+After parsing the type definition, all constraints will be packed into a HashMap.
+The ReparsedConstraint will be cast into specific constraint based on which instance it represents.
+*/
+public interface ReparsedConstraint {
+
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/TimestampPrecision.java b/src/com/amazon/ion/benchmark/schema/constraints/TimestampPrecision.java
new file mode 100644
index 0000000..43add21
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/TimestampPrecision.java
@@ -0,0 +1,43 @@
+package com.amazon.ion.benchmark.schema.constraints;
+
+import com.amazon.ion.*;
+import com.amazon.ion.benchmark.IonSchemaUtilities;
+
+import java.util.Random;
+
+public class TimestampPrecision extends QuantifiableConstraints{
+
+ /**
+ * Initializing the newly created TimestampPrecision object.
+ * @param value represent the value of constraint 'timestamp_precision'.
+ */
+ private TimestampPrecision(IonValue value) {
+ super(value);
+ }
+
+ /**
+ * Parsing the constraint 'timestamp_precision' into TimestampPrecision.
+ * @param field represent the value of constraint 'timestamp_precision'.
+ * @return the object of TimestampPrecision.
+ */
+ public static TimestampPrecision of(IonValue field) {
+ return new TimestampPrecision(field);
+ }
+
+ /**
+ * Getting the timestamp precision randomly from the provided timestamp precision range.
+ * @param range represents the range of timestamp precision.
+ * @return randomly generated Timestamp.Precision.
+ */
+ public static Timestamp.Precision getRandomTimestampPrecision(Range range) {
+ Random random = new Random();
+ IonSequence constraintSequence = range.getSequence();
+ Timestamp.Precision[] precisions = Timestamp.Precision.values();
+ String lowerBound = constraintSequence.get(0).toString();
+ String upperBound = constraintSequence.get(1).toString();
+ int lowerBoundOrdinal = lowerBound.equals(IonSchemaUtilities.KEYWORD_MIN) ? 0 : Timestamp.Precision.valueOf(lowerBound.toUpperCase()).ordinal();
+ int upperBoundOrdinal = upperBound.equals(IonSchemaUtilities.KEYWORD_MAX) ? precisions.length : Timestamp.Precision.valueOf(upperBound.toUpperCase()).ordinal();
+ int randomIndex = random.nextInt(upperBoundOrdinal - lowerBoundOrdinal + 1) + lowerBoundOrdinal;
+ return precisions[randomIndex];
+ }
+}
diff --git a/src/com/amazon/ion/benchmark/schema/constraints/ValidValues.java b/src/com/amazon/ion/benchmark/schema/constraints/ValidValues.java
new file mode 100644
index 0000000..0f1e967
--- /dev/null
+++ b/src/com/amazon/ion/benchmark/schema/constraints/ValidValues.java
@@ -0,0 +1,60 @@
+package com.amazon.ion.benchmark.schema.constraints;
+
+import com.amazon.ion.IonList;
+import com.amazon.ion.IonValue;
+
+// This class is used for parsing constraint 'valid_values' and providing the utilities of processing the value of constraint.
+// valid_values: [ ... ]
+// valid_values: >
+// valid_values: >
+public class ValidValues implements ReparsedConstraint {
+ // TODO: Handling min and max value
+ final private IonList validValues;
+ final private Range range;
+ final private boolean isRange;
+
+ /**
+ * Initializing the newly created ValidValues object.
+ * @param validValues represents the value of constraint 'valid_values'.
+ * @param isRange is a boolean value to represent the format of 'valid_values'.
+ */
+ public ValidValues(IonList validValues, boolean isRange) {
+ this.validValues = isRange ? null : validValues;
+ this.range = isRange ? Range.of(validValues) : null;
+ this.isRange = isRange;
+ }
+
+ /**
+ * Getting the value of constraint 'valid_values' in IonList format.
+ * @return an IonList which represents the value of constraint 'valid_values'.
+ */
+ public IonList getValidValues() {
+ return validValues;
+ }
+
+ /**
+ * Checking whether constraint 'valid_values' contains range.
+ * @return a boolean value to represent whether 'valid_values' contains range.
+ */
+ public boolean isRange() {
+ return isRange;
+ }
+
+ /**
+ * Getting the range value of constraint 'valid_values' if its format is one of [> | >]
+ * @return a Range object.
+ */
+ public Range getRange() {
+ return range;
+ }
+
+ /**
+ * Parsing constraint field into ValidValues format.
+ * @param field represents constraint field 'valid_values'.
+ * @return the newly created ValidValues object.
+ */
+ public static ValidValues of(IonValue field) {
+ boolean isRange = Range.isRange(field);
+ return new ValidValues((IonList) field, isRange);
+ }
+}
diff --git a/tst/com/amazon/ion/benchmark/DataGeneratorTest.java b/tst/com/amazon/ion/benchmark/DataGeneratorTest.java
index 5b3a2d2..d6dba5b 100644
--- a/tst/com/amazon/ion/benchmark/DataGeneratorTest.java
+++ b/tst/com/amazon/ion/benchmark/DataGeneratorTest.java
@@ -157,28 +157,28 @@ public void testSizeOfGeneratedData() throws Exception {
* Test if there's violation when generating Ion Struct based on Ion Schema.
* @throws Exception if error occurs during the violation detecting process.
*/
- @Test
- public void testViolationOfIonStruct() throws Exception {
- DataGeneratorTest.violationDetect(INPUT_ION_STRUCT_FILE_PATH);
- }
+// @Test
+// public void testViolationOfIonStruct() throws Exception {
+// DataGeneratorTest.violationDetect(INPUT_ION_STRUCT_FILE_PATH);
+// }
/**
* Test if there's violation when generating Ion List based on Ion Schema.
* @throws Exception if error occurs during the violation detecting process.
*/
- @Test
- public void testViolationOfIonList() throws Exception {
- DataGeneratorTest.violationDetect(INPUT_ION_LIST_FILE_PATH);
- }
+// @Test
+// public void testViolationOfIonList() throws Exception {
+// DataGeneratorTest.violationDetect(INPUT_ION_LIST_FILE_PATH);
+// }
/**
* Test if there's violation when generating nested Ion Struct based on Ion Schema.
* @throws Exception if error occurs during the violation detecting process.
*/
- @Test
- public void testViolationOfNestedIonStruct() throws Exception {
- DataGeneratorTest.violationDetect(INPUT_NESTED_ION_STRUCT_PATH);
- }
+// @Test
+// public void testViolationOfNestedIonStruct() throws Exception {
+// DataGeneratorTest.violationDetect(INPUT_NESTED_ION_STRUCT_PATH);
+// }
/**
* Test if there's violation when generating Ion Timestamp based on Ion Schema.