/*
* Copyright 2012 NGDATA nv
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package org.lilyproject.tools.tester;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.codehaus.jackson.JsonNode;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.lilyproject.bytes.api.ByteArray;
import org.lilyproject.repository.api.Blob;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.HierarchyPath;
import org.lilyproject.repository.api.LRepository;
import org.lilyproject.repository.api.LTable;
import org.lilyproject.repository.api.Link;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordException;
import org.lilyproject.repository.api.ValueType;
import org.lilyproject.testclientfw.Words;
import org.lilyproject.util.json.JsonUtil;
public class TestFieldType {
private static final Random random = new Random();
private final LTable table;
private final LRepository repository;
FieldType fieldType;
private String linkedRecordTypeName;
private String linkedRecordSource;
private final JsonNode properties;
public TestFieldType(FieldType fieldType, LTable table, LRepository repository, JsonNode properties) {
this.fieldType = fieldType;
this.table = table;
this.repository = repository;
this.properties = properties;
if (properties != null) {
this.linkedRecordTypeName = JsonUtil.getString(properties, "recordType", null);
this.linkedRecordSource = JsonUtil.getString(properties, "recordSource", null);
}
}
public FieldType getFieldType() {
return fieldType;
}
public String getLinkedRecordTypeName(){
return linkedRecordTypeName;
}
public String getLinkedRecordSource() {
return linkedRecordSource;
}
public ActionResult generateValue(TestAction testAction) {
return generateValue(testAction, fieldType.getValueType());
}
private ActionResult generateList(TestAction testAction, ValueType valueType) {
int size = (int)Math.ceil(Math.random() * 2);
List<Object> values = new ArrayList<Object>();
long duration = 0;
for (int i = 0; i < size; i++) {
ActionResult result = generateValue(testAction, valueType);
duration += result.duration;
if (result.success) {
values.add(result.object);
} else {
return new ActionResult(false, null, duration);
}
}
return new ActionResult(true, values, duration);
}
private ActionResult generatePath(TestAction testAction, ValueType valueType) {
int size = (int)Math.ceil(Math.random() * 3);
Object[] elements = new Object[size];
long duration = 0;
for (int i = 0; i < size; i++) {
ActionResult result = generateValue(testAction, valueType);
duration += result.duration;
if (result.success) {
elements[i] = result.object;
} else {
return new ActionResult(false, null, duration);
}
}
return new ActionResult(true, new HierarchyPath(elements), duration);
}
private ActionResult generateValue(TestAction testAction, ValueType valueType) {
String name = valueType.getBaseName();
if (name.equals("LIST")) {
return generateList(testAction, valueType.getNestedValueType());
} else if (name.equals("PATH")) {
return generatePath(testAction, valueType.getNestedValueType());
} else if (name.equals("STRING")) {
return new ActionResult(true, generateString(), 0);
} else if (name.equals("INTEGER")) {
return new ActionResult(true, generateInt(), 0);
} else if (name.equals("LONG")) {
return new ActionResult(true, generateLong(), 0);
} else if (name.equals("DOUBLE")) {
return new ActionResult(true, generateDouble(), 0);
} else if (name.equals("BOOLEAN")) {
return new ActionResult(true, generateBoolean(), 0);
} else if (name.equals("DATE")) {
return new ActionResult(true, generateLocalDate(), 0);
} else if (name.equals("DATETIME")) {
return new ActionResult(true, generateDateTime(), 0);
} else if (name.equals("BLOB")) {
return new ActionResult(true, generateBlob(), 0);
} else if (name.equals("BYTEARRAY")) {
return new ActionResult(true, generateByteArray(), 0);
} else if (name.equals("LINK")) {
return testAction.linkFieldAction(this, null);
} else if (name.equals("RECORD")) {
try {
return new ActionResult(true, generateRecord(testAction, valueType), 0);
} catch (RecordException e) {
throw new RuntimeException("Error generating record value (CFT)", e);
}
} else {
throw new RuntimeException("Unsupported value type: " + name);
}
}
private String generateString() {
String value = null;
// Default
if (properties == null) {
value = Words.get(Words.WordList.BIG_LIST, (int)Math.floor(Math.random() * 100));
} else {
int wordCount = JsonUtil.getInt(properties, "wordCount", 1);
String wordString = JsonUtil.getString(properties, "enum", null);
if (wordString != null) {
String[] words = wordString.split(",");
StringBuilder stringBuilder = new StringBuilder(20 * wordCount);
for (int i = 0; i < wordCount; i++) {
if (i > 0) {
stringBuilder.append(' ');
}
int index = (int) (Math.random() * words.length);
stringBuilder.append(words[index]);
}
value = stringBuilder.toString();
} else {
value = Words.get(Words.WordList.BIG_LIST,wordCount);
}
}
return value;
}
private ByteArray generateByteArray() {
ByteArray value = null;
// Default
if (properties == null) {
byte[] bytes = new byte[random.nextInt(100)];
random.nextBytes(bytes);
value = new ByteArray(bytes);
} else {
int length = JsonUtil.getInt(properties, "length", 100);
byte[] bytes = new byte[length];
random.nextBytes(bytes);
value = new ByteArray(bytes);
}
return value;
}
private int generateInt() {
// Default
int value = 0;
if (properties == null) {
value = random.nextInt();
} else {
String numberString = JsonUtil.getString(properties, "enum", null);
if (numberString != null) {
String[] numbers = numberString.split(",");
int index = (int) (Math.random() * numbers.length);
value = Integer.valueOf(numbers[index]);
} else {
int min = JsonUtil.getInt(properties, "min", Integer.MIN_VALUE);
int max = JsonUtil.getInt(properties, "max", Integer.MAX_VALUE);
value = min + (int)(Math.random() * ((max - min) + 1));
}
}
return value;
}
private long generateLong() {
// Default
long value = 0;
if (properties == null) {
value = random.nextLong();
} else {
String numberString = JsonUtil.getString(properties, "enum", null);
if (numberString != null) {
String[] numbers = numberString.split(",");
int index = (int) (Math.random() * numbers.length);
value = Long.valueOf(numbers[index]);
} else {
long min = JsonUtil.getLong(properties, "min", Long.MIN_VALUE);
long max = JsonUtil.getLong(properties, "max", Long.MAX_VALUE);
value = min + (long)(Math.random() * ((max - min) + 1));
}
}
return value;
}
private double generateDouble() {
// Default
double value = 0;
if (properties == null) {
value = random.nextDouble();
} else {
String numberString = JsonUtil.getString(properties, "enum", null);
if (numberString != null) {
String[] numbers = numberString.split(",");
int index = (int) (Math.random() * numbers.length);
value = Double.valueOf(numbers[index]);
} else {
double min = JsonUtil.getDouble(properties, "min", Double.MIN_VALUE);
double max = JsonUtil.getDouble(properties, "max", Double.MAX_VALUE);
value = min + (double)(Math.random() * ((max - min) + 1));
}
}
return value;
}
private Record generateRecord(TestAction testAction, ValueType valueType) throws RecordException {
String valueTypeName = valueType.getName();
String recordTypeName = valueTypeName.substring(valueTypeName.indexOf("<") + 1, valueTypeName.length()-1);
TestActionContext context = testAction.getContext();
TestRecordType testRecordType = context.recordTypes.get(QName.fromString(recordTypeName));
Record record = context.repository.getRecordFactory().newRecord();
record.setRecordType(testRecordType.getRecordType().getName());
List<TestFieldType> testFieldTypes = testRecordType.getFieldTypes();
for (TestFieldType testFieldType : testFieldTypes) {
ActionResult actionResult = testFieldType.generateValue(testAction);
record.setField(testFieldType.getFieldType().getName(), actionResult.object);
}
return record;
}
private boolean generateBoolean() {
return random.nextBoolean();
}
private LocalDate generateLocalDate() {
int year = 1950 + (int)(Math.random() * 100);
int month = (int)Math.ceil(Math.random() * 12);
int day = (int)Math.ceil(Math.random() * 25);
return new LocalDate(year, month, day);
}
private DateTime generateDateTime() {
int fail = 0;
while (true) {
int year = 1950 + (int)(Math.random() * 100);
int month = (int)Math.ceil(Math.random() * 12);
int day = (int)Math.ceil(Math.random() * 25);
int hour = (int)Math.floor(Math.random() * 24);
int minute = (int)Math.floor(Math.random() * 60);
int second = (int)Math.floor(Math.random() * 60);
try {
return new DateTime(year, month, day, hour, minute, second, 0);
} catch (IllegalArgumentException e) {
// We can get exceptions here of the kind:
// "Illegal instant due to time zone offset transition"
// This can occur if we happen to generate a time which falls in daylight
// saving.
if (fail > 10) {
throw new RuntimeException("Strange: did not succeed to generate a valid date after "
+ fail + " tries.", e);
}
fail++;
}
}
}
private Blob generateBlob() {
// Generate a blob that should be cleaned up by BlobIncubator
actualGenerateBlob();
return actualGenerateBlob();
}
private Blob actualGenerateBlob() {
//4K, 150K, 200MB
int[] sizes = new int[]{
4000, 150000, 200000000
};
// long start = System.currentTimeMillis();
int min = 1;
int max = sizes[0 + (int)(Math.random() * ((2 - 0) + 1))];
int size = min + (int)(Math.random() * ((max - min) + 1));
byte[] bytes = new byte[size];
random.nextBytes(bytes);
Blob blob = new Blob("tester", (long)bytes.length, "test"+size);
try {
OutputStream outputStream = table.getOutputStream(blob);
outputStream.write(bytes);
outputStream.close();
// System.out.println("created blob of size "+size+" in "+ (System.currentTimeMillis()-start) +" ms");
return blob;
} catch (Exception e) {
throw new RuntimeException("Failed to generate blob", e);
}
}
public Link generateLink() {
return new Link(repository.getIdGenerator().newRecordId());
}
public ActionResult updateValue(TestAction testAction, Record record) {
if (linkedRecordTypeName == null && linkedRecordSource == null) {
return generateValue(null); // The value will not be a link field, so we can give null here
} else {
Object value = record.getField(fieldType.getName());
return updateLinkValue(testAction, value, fieldType.getValueType());
}
}
private ActionResult updateLinkValue(TestAction testAction, Object value, ValueType valueType) {
if (valueType.getBaseName().equals("LIST")) {
List<Object> values = (List<Object>) value;
int index = (int) (Math.random() * values.size());
ActionResult result = updateLinkValue(testAction, values.get(index), valueType.getNestedValueType());
if (result.success && result.object != null) {
values.add(index, result.object);
return new ActionResult(true, values, result.duration);
}
return result;
} else if (valueType.getBaseName().equals("PATH")) {
HierarchyPath path = (HierarchyPath) value;
Object[] values = path.getElements();
int index = (int) (Math.random() * values.length);
// LinkedRecordTypeName should only be given in case of link fields
ActionResult result = updateLinkValue(testAction, values[index], valueType.getNestedValueType());
if (result.success && result.object != null) {
values[index] = result.object;
return new ActionResult(true, values, result.duration);
}
return result;
} else {
return updateLink(testAction, (Link) value);
}
}
private ActionResult updateLink(TestAction testAction, Link link) {
return testAction.linkFieldAction(this, link.getMasterRecordId());
}
}