+/*******************************************************************************\r
+ * Copyright (c) 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.binding.util;
+
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Random;\r
+import java.util.WeakHashMap;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.Datatypes;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.binding.ArrayBinding;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.Binding.Visitor;\r
+import org.simantics.databoard.binding.BooleanBinding;\r
+import org.simantics.databoard.binding.ByteBinding;\r
+import org.simantics.databoard.binding.DoubleBinding;\r
+import org.simantics.databoard.binding.FloatBinding;\r
+import org.simantics.databoard.binding.IntegerBinding;\r
+import org.simantics.databoard.binding.LongBinding;\r
+import org.simantics.databoard.binding.MapBinding;\r
+import org.simantics.databoard.binding.OptionalBinding;\r
+import org.simantics.databoard.binding.RecordBinding;\r
+import org.simantics.databoard.binding.StringBinding;\r
+import org.simantics.databoard.binding.UnionBinding;\r
+import org.simantics.databoard.binding.VariantBinding;\r
+import org.simantics.databoard.binding.error.BindingConstructionException;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingConstructionException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.factory.BindingScheme;\r
+import org.simantics.databoard.binding.factory.MutableBindingFactory;\r
+import org.simantics.databoard.type.ArrayType;\r
+import org.simantics.databoard.type.BooleanType;\r
+import org.simantics.databoard.type.ByteType;\r
+import org.simantics.databoard.type.Component;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.type.DoubleType;\r
+import org.simantics.databoard.type.FloatType;\r
+import org.simantics.databoard.type.IntegerType;\r
+import org.simantics.databoard.type.LongType;\r
+import org.simantics.databoard.type.MapType;\r
+import org.simantics.databoard.type.OptionalType;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.type.StringType;\r
+import org.simantics.databoard.type.UnionType;\r
+import org.simantics.databoard.type.VariantType;\r
+import org.simantics.databoard.util.Limit;\r
+import org.simantics.databoard.util.Range;\r
+
+/**
+ * Visitor that creates a instance with random value.
+ * This visitor may throw RuntimeBindingException.
+ *
+ * Type Value
+ * ------------------------------------------------------
+ * Boolean false/true
+ * Byte, Integer, Long value between limits
+ * Float, Double 0..1 if no range, otherwise a valid value in range
+ * String random string of length [0..1024]
+ * Optional novalue / random value
+ * Union random tag / random value
+ * Record each field with random value
+ * Array random elements between 0..1024 unless lower bound is higher
+ * Map 0..1024 random entries with random keys and value
+ * Variant random type (excluding variant) with random value
+ *
+ * TODO Create String according to the pattern
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public class RandomValue implements Visitor<Object> {
+
+ public boolean refereableRecords = true;
+
+ public Random random;
+
+ /** Map of default values already created. Used to link back to recursive records */
+ Map<Binding, Object> map = new WeakHashMap<Binding, Object>(1);\r
+ \r
+ BindingScheme scheme = new MutableBindingFactory( new HashMap<Datatype, Binding>() );
+
+ static String CHARS = "abcdefghijklmnopqrstuvwxyz ,.-'/-+ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!\"#�%&/()=\n\\\\r'";
+
+ public RandomValue() {
+ this.random = new Random();
+ }
+
+ public RandomValue(Random random) {
+ this.random = random;
+ }
+
+ public RandomValue(int seed) {
+ this.random = new Random(seed);
+ }
+
+ public Random getRandom() {
+ return random;
+ }
+
+ @Override
+ public Object visit(ArrayBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+
+ ArrayType at = b.type();
+
+ long min = at.minLength();
+ long max = Math.max(min, Math.min(32, at.maxLength()));
+ int c = (int) (min + nextRandom(max-min+1));
+
+ Binding componentBinding = b.getComponentBinding();
+ Object[] array = new Object[c];
+ for (int i=0; i<array.length; i++) {
+ array[i] = componentBinding.accept(this);
+ }
+ result = b.createUnchecked(array);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(BooleanBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ result = b.createUnchecked( random.nextBoolean() );
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(DoubleBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ DoubleType type = b.type();
+ Range range = type.getRange();
+ double min = type.minValue();
+ double max = type.maxValue();
+ double value = (range==null) ? random.nextDouble() : min+random.nextDouble() * (max-min);
+ result = b.createUnchecked(value);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(FloatBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ FloatType type = b.type();
+ Range range = type.getRange();
+ double min = type.minValue();
+ double max = type.maxValue();
+ double value = (range==null) ? random.nextDouble() : min+random.nextDouble() * (max-min);
+ result = b.createUnchecked(value);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(IntegerBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ IntegerType type = b.type();
+ Range range = type.getRange();
+ long min = type.minValue();
+ long max = type.maxValue();
+ long value = (range==null) ? random.nextInt() : min + Math.abs(random.nextLong() % (max-min));
+ result = b.createUnchecked(value);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(ByteBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ ByteType type = b.type();
+ Range range = type.getRange();
+ int min = type.minValue();
+ int max = type.maxValue();
+ int value = (range==null) ? random.nextInt(256)-128 : min + random.nextInt(max-min+1);
+ result = b.createUnchecked(value);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(LongBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ LongType type = b.type();
+ Range range = type.getRange();
+ long min = type.minValue();
+ long max = type.maxValue();
+ long value = (range==null) ? random.nextLong() : min + Math.abs(random.nextLong() % (max-min));
+ result = b.createUnchecked(value);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(OptionalBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ Binding componentBinding = b.getComponentBinding();
+ result = random.nextBoolean() ? b.createNoValueUnchecked() : b.createValueUnchecked( componentBinding.accept(this) );
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(RecordBinding b) {
+ try {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+
+ Object[] values = new Object[ b.getComponentCount() ];
+
+ if (b.type().isReferable()) {
+ result = b.createPartial();
+ map.put(b, result);
+ for (int i=0; i<values.length; i++) {
+ Binding cb = b.getComponentBinding(i);
+ values[i] = cb.accept(this);
+ }
+ b.setComponents(result, values);
+ } else {
+
+ for (int i=0; i<values.length; i++) {
+ Binding cb = b.getComponentBinding(i);
+ values[i] = cb.accept(this);
+ }
+ result = b.create(values);
+ map.put(b, result);
+ }
+ return result;
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public Object visit(StringBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ StringType st = b.type();
+ int min = st.minLength();
+ int max = Math.max(min, Math.min(64, st.maxLength()));
+ int c = (int) ( min + nextRandom(max-min+1) );
+
+ StringBuilder sb = new StringBuilder(c);
+ for (int i=0; i<c; i++)
+ sb.append( CHARS.charAt( random.nextInt(CHARS.length()) ) );
+
+ result = b.createUnchecked(sb.toString());
+
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(UnionBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+ UnionType ut = b.type();
+
+ int tag = random.nextInt( ut.getComponentCount() );
+
+ Binding componentBinding = b.getComponentBinding( tag );
+ Object randomValue = componentBinding.accept(this);
+ result = b.createUnchecked(tag, randomValue);
+ map.put(b, result);
+ return result;
+ }
+
+ @Override
+ public Object visit(VariantBinding b) {
+ try {\r
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+
+ int maxDepth = random.nextInt(3)+1;
+ Datatype randomType = randomType(0, maxDepth);
+ Binding randomBinding;\r
+ randomBinding = scheme.getBinding( randomType );\r
+ Object randomValue = randomBinding.accept(this);
+
+ result = b.createUnchecked(randomBinding, randomValue);
+ map.put(b, result);
+ return result;
+ } catch (BindingConstructionException e) {\r
+ throw new RuntimeBindingConstructionException(e);\r
+ }\r
+ }
+\r
+ boolean isKeyShortEnough(Binding binding, Object value) {\r
+ try {\r
+ String key;\r
+ key = (String) Bindings.adapt(value, binding, Bindings.STR_VARIANT);\r
+ return key.length()<=200;\r
+ } catch (AdaptException e) {\r
+ throw new RuntimeException(e);\r
+ } \r
+ }\r
+
+ @Override
+ public Object visit(MapBinding b) {
+ Object result = pickCached() ? map.get(b) : null;
+ if (result!=null) return result;
+
+ int c = random.nextInt(32);
+
+ Binding keyBinding = b.getKeyBinding();
+ Binding valueBinding = b.getValueBinding();
+ Object[] keys = new Object[c];
+ Object[] values = new Object[c];
+ for (int i=0; i<c; i++) {\r
+ \r
+ \r
+ Object key = null;\r
+ if (keyBinding.type().equals(Datatypes.VARIANT)) {\r
+ do {\r
+ key = keyBinding.accept(this);\r
+ } while( !keyBinding.type().equals(Datatypes.VARIANT) || !isKeyShortEnough(keyBinding, key) ); \r
+ } else {\r
+ key = keyBinding.accept(this); \r
+ }\r
+
+ keys[i] = key;\r
+
+ values[i] = valueBinding.accept(this);
+ }
+
+ result = b.createUnchecked(keys, values);
+ map.put(b, result);
+ return result;
+ }
+
+ public Datatype randomType(int depth, int maxDepth) {
+ // Random until non variant
+ int tag = 0;
+ if (depth<maxDepth) {
+ tag = random.nextInt( 12 );
+ if (random.nextInt(500)==0) tag=12;
+ } else {
+ tag = random.nextInt( 7 );
+ }
+
+ if (tag==0) {
+ return new BooleanType();
+ }
+ if (tag==1) {
+ Limit lowerLimit = Limit.exclusive((random.nextInt() & 255) - 128);
+ Limit upperLimit = Limit.exclusive((random.nextInt() & 255) - 128);
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ ByteType result = new ByteType(null, range);
+ if (result.minValue()>result.maxValue()) result.setRange( (Range) null );
+ return result;
+ }
+
+ if (tag==2) {
+ Limit lowerLimit = Limit.inclusive(0);
+ Limit upperLimit = Limit.exclusive(random.nextInt());
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ IntegerType result = new IntegerType(null, range);
+ if (result.minValue()>result.maxValue()) result.setRange( (Range) null );
+ return result;
+ }
+
+ if (tag==3) {
+ Limit lowerLimit = Limit.inclusive(0);
+ Limit upperLimit = Limit.exclusive(random.nextLong());
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ LongType result = new LongType(null, range);
+ if (result.minValue()>result.maxValue()) result.setRange( (Range) null );
+ return result;
+ }
+
+ if (tag==4) {
+ Limit lowerLimit = Limit.inclusive(0);
+ Limit upperLimit = Limit.exclusive(random.nextDouble() * random.nextInt());
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ FloatType result = new FloatType(null, range);
+ if (result.minValue()>result.maxValue()) result.setRange( (Range) null );
+ return result;
+ }
+
+ if (tag==5) {
+ Limit lowerLimit = Limit.inclusive(0);
+ Limit upperLimit = Limit.exclusive(random.nextDouble() * random.nextInt());
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ DoubleType result = new DoubleType(null, range);
+ if (result.minValue()>result.maxValue()) result.setRange( (Range) null );
+ return result;
+ }
+
+ if (tag==6) {
+ Limit lowerLimit = Limit.inclusive(0);
+ Limit upperLimit = Limit.exclusive(random.nextInt(1024));
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ StringType result = new StringType(null, null, range);
+ if (result.minLength()>result.maxLength()) result.setLength( (Range) null );
+ return result;
+ }
+
+ if (tag==7) {
+ int c = random.nextInt(16);
+ Component[] components = new Component[c];
+ String[] names = randomUniqueNames(c);
+ for (int i=0; i<c; i++) {
+ components[i] = new Component(names[i], randomType(depth+1, maxDepth));
+ }
+ return new RecordType(refereableRecords ? random.nextBoolean() : false, components);
+ }
+
+ if (tag==8) {
+ Limit lowerLimit = Limit.inclusive(random.nextInt(16));
+ Limit upperLimit = Limit.exclusive(random.nextInt(16));
+ Range range = random.nextBoolean() ? Range.between(lowerLimit, upperLimit) : null;
+ ArrayType result = new ArrayType(randomType(depth+1, maxDepth), range);
+ if (result.minLength()>result.maxLength()) result.setLength( (Range) null );
+ return result;
+ }
+
+ if (tag==9) {
+ return new MapType(randomType(depth+1, maxDepth), randomType(depth+1, maxDepth));
+ }
+
+ if (tag==10) {
+ return new OptionalType( randomType(depth+1, maxDepth) );
+ }
+
+ if (tag==11) {
+ int c = random.nextInt(16)+1;
+ Component[] components = new Component[c];
+ String[] names = randomUniqueNames(c);
+ for (int i=0; i<c; i++) {
+ components[i] = new Component(names[i], randomType(depth+1, maxDepth));
+ }
+ return new UnionType(components);
+ }
+
+ if (tag==12) {
+ return new VariantType();
+ }
+
+ return null;
+ }
+
+ /**
+ * Answers to the question weather we should pick a cached value.
+ * There is 10% if referable records is enabled.
+ *
+ * @return
+ */
+ boolean pickCached() {
+ if (!refereableRecords) return false;
+ return refereableRecords && random.nextInt(10)==0;
+ }
+
+ String randomName() {
+ int nameLength = random.nextInt(32)+1;
+ StringBuilder sb = new StringBuilder(nameLength);
+ for (int j=0; j<nameLength; j++)
+ sb.append( CHARS.charAt( random.nextInt(CHARS.length()) ) );
+ return sb.toString();
+ }
+
+ /**
+ * Create <code>count</code> unique random names.
+ *
+ * @param count
+ * @return
+ */
+ String[] randomUniqueNames(int count) {
+ HashSet<String> result = new HashSet<String>(count);
+ for (int i=0; i<count; i++) {
+ String name = null;
+ do {
+ name = randomName();
+ } while (result.contains(name));
+ result.add(name);
+ }
+ return result.toArray(new String[count]);
+ }
+
+ long nextRandom(long n) {
+ long v = random.nextLong();
+ v = Math.abs(v);
+ return v % n;
+ }
+
+}
+