2 *******************************************************************************
3 * Copyright (C) 2002-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.dev.test.util;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.HashSet;
12 import java.util.Random;
15 import com.ibm.icu.text.UTF16;
16 import com.ibm.icu.text.UnicodeSet;
18 abstract public class Pick {
19 private static boolean DEBUG = false;
21 // for using to get strings
25 private Random random;
26 private Quoter quoter;
28 public static Target make(Pick pick, Random random, Quoter quoter) {
29 Target result = new Target();
31 result.random = random;
32 result.quoter = quoter;
35 public String next() {
41 return quoter.toString();
43 private void copyState(Target other) {
44 random = other.random;
46 private void clear() {
49 /*private int length() {
50 return quoter.length();
52 private Target append(int codepoint) {
53 quoter.append(codepoint);
56 private Target append(String s) {
60 // must return value between 0 (inc) and 1 (exc)
61 private double nextDouble() {
62 return random.nextDouble();
68 public Pick replace(String toReplace, Pick replacement) {
69 Replacer visitor = new Replacer(toReplace, replacement);
70 return visit(visitor);
73 public Pick name(String nameStr) {
78 static public Pick.Sequence makeSequence() {
79 return new Sequence();
81 static public Pick.Alternation makeAlternation() {
82 return new Alternation();
85 static public Pick.Sequence and(Object item) {
86 return new Sequence().and2(item);
88 static public Pick.Sequence and(Object[] items) {
89 return new Sequence().and2(items);
91 static public Pick.Alternation or(int itemWeight, Object item) {
92 return new Alternation().or2(itemWeight, item);
94 static public Pick.Alternation or(Object[] items) {
95 return new Alternation().or2(1, items);
97 static public Pick.Alternation or(int itemWeight, Object[] items) {
98 return new Alternation().or2(itemWeight, items);
100 static public Pick.Alternation or(int[] itemWeights, Object[] items) {
101 return new Alternation().or2(itemWeights, items);
104 static public Pick maybe(int percent, Object item) {
105 return new Repeat(0, 1, new int[]{100-percent, percent}, item);
106 //return Pick.or(1.0-percent, NOTHING).or2(percent, item);
108 static public Pick repeat(int minCount, int maxCount, int itemWeights, Object item) {
109 return new Repeat(minCount, maxCount, itemWeights, item);
112 static public Pick codePoint(String source) {
113 return new CodePoint(new UnicodeSet(source));
117 static public Pick repeat(int minCount, int maxCount, int[] itemWeights, Pick item) {
118 return new Repeat(minCount, maxCount, itemWeights, item);
121 static public Pick codePoint(UnicodeSet source) {
122 return new CodePoint(source);
124 static public Pick string(String source) {
125 return new Literal(source);
128 static public Pick unquoted(String source) {
129 return new Literal(source);
131 static public Pick string(int minLength, int maxLength, Pick item) {
132 return new Morph(item, minLength, maxLength);
136 public abstract String getInternal(int depth, Set alreadySeen);
139 protected String name;
141 protected abstract void addTo(Target target);
142 protected abstract boolean match(String input, Position p);
144 public static class Sequence extends ListPick {
145 public Sequence and2 (Pick item) {
146 addInternal(new Pick[] {item}); // we don't care about perf
147 return this; // for chaining
149 public Sequence and2 (Pick[] itemArray) {
150 addInternal(itemArray);
151 return this; // for chaining
153 protected void addTo(Target target) {
154 for (int i = 0; i < items.length; ++i) {
155 items[i].addTo(target);
158 public String getInternal(int depth, Set alreadySeen) {
159 String result = checkName(name, alreadySeen);
160 if (result.startsWith("$")) return result;
161 result = indent(depth) + result + "SEQ(";
162 for (int i = 0; i < items.length; ++i) {
163 if (i != 0) result += ", ";
164 result += items[i].getInternal(depth+1, alreadySeen);
170 private Sequence() {}
171 protected boolean match(String input, Position p) {
172 int originalIndex = p.index;
173 for (int i = 0; i < items.length; ++i) {
174 if (!items[i].match(input, p)) {
175 p.index = originalIndex;
183 String checkName(String nameStr, Set alreadySeen) {
184 if (nameStr == null) return "";
185 if (alreadySeen.contains(nameStr)) return nameStr;
186 alreadySeen.add(nameStr);
187 return "{" + nameStr + "=}";
190 public static class Alternation extends ListPick {
191 private WeightedIndex weightedIndex = new WeightedIndex(0);
193 public Alternation or2 (Pick[] newItems) {
194 return or2(1, newItems);
196 public Alternation or2 (int itemWeight, Pick item) {
197 return or2(itemWeight, new Pick[] {item}); // we don't care about perf
199 public Alternation or2 (int itemWeight, Pick[] newItems) {
200 int[] itemWeights = new int[newItems.length];
201 Arrays.fill(itemWeights,itemWeight);
202 return or2(itemWeights, newItems); // we don't care about perf
204 public Alternation or2 (int[] itemWeights, Pick[] newItems) {
205 if (newItems.length != itemWeights.length) {
206 throw new ArrayIndexOutOfBoundsException(
207 "or lengths must be equal: " + newItems.length + " != " + itemWeights.length);
209 // int lastLen = this.items.length;
210 addInternal(newItems);
211 weightedIndex.add(itemWeights);
212 return this; // for chaining
214 protected void addTo(Target target) {
215 items[weightedIndex.toIndex(target.nextDouble())].addTo(target);
218 public String getInternal(int depth, Set alreadySeen) {
219 String result = checkName(name, alreadySeen);
220 if (result.startsWith("$")) return result;
221 result = indent(depth) + result + "OR(";
222 for (int i = 0; i < items.length; ++i) {
223 if (i != 0) result += ", ";
224 result += items[i].getInternal(depth+1, alreadySeen) + "/" + weightedIndex.weights[i];
229 private Alternation() {}
230 // take first matching option
231 protected boolean match(String input, Position p) {
232 for (int i = 0; i < weightedIndex.weights.length; ++i) {
233 if (p.isFailure(this,i)) continue;
234 if (items[i].match(input, p)) return true;
235 p.setFailure(this, i);
241 private static String indent(int depth) {
242 String result = "\r\n";
243 for (int i = 0; i < depth; ++i) {
249 private static class Repeat extends ItemPick {
250 WeightedIndex weightedIndex;
253 private Repeat(int minCount, int maxCount, int[] itemWeights, Pick item) {
255 weightedIndex = new WeightedIndex(minCount).add(maxCount-minCount+1, itemWeights);
257 /*private Repeat(int minCount, int maxCount, int itemWeight, Pick item) {
259 weightedIndex = new WeightedIndex(minCount).add(maxCount-minCount+1, itemWeight);
262 private Repeat(int minCount, int maxCount, Object item) {
263 this.item = convert(item);
264 weightedIndex = new WeightedIndex(minCount).add(maxCount-minCount+1, 1);
267 protected void addTo(Target target) {
269 for (int i = weightedIndex.toIndex(target.nextDouble()); i > 0; --i) {
273 public String getInternal(int depth, Set alreadySeen) {
274 String result = checkName(name, alreadySeen);
275 if (result.startsWith("$")) return result;
276 result = indent(depth) + result + "REPEAT(" + weightedIndex
277 + "; "+ item.getInternal(depth+1, alreadySeen)
282 // match longest, e.g. up to just before a failure
283 protected boolean match(String input, Position p) {
284 //int bestMatch = p.index;
286 for (int i = 0; i < weightedIndex.weights.length; ++i) {
287 if (p.isFailure(this,i)) break;
288 if (!item.match(input, p)) {
289 p.setFailure(this,i);
292 //bestMatch = p.index;
295 if (count >= minCount) {
303 private static class CodePoint extends FinalPick {
304 private UnicodeSet source;
306 private CodePoint(UnicodeSet source) {
307 this.source = source;
309 protected void addTo(Target target) {
310 target.append(source.charAt(pick(target.random,0,source.size()-1)));
312 protected boolean match(String s, Position p) {
313 int cp = UTF16.charAt(s, p.index);
314 if (source.contains(cp)) {
315 p.index += UTF16.getCharCount(cp);
318 p.setMax("codePoint");
321 public String getInternal(int depth, Set alreadySeen) {
322 String result = checkName(name, alreadySeen);
323 if (result.startsWith("$")) return result;
324 return source.toString();
328 static class Morph extends ItemPick {
333 private String lastValue = null;
334 private Target addBuffer = Target.make(this, null, new Quoter.RuleQuoter());
335 private StringBuffer mergeBuffer = new StringBuffer();
337 private static final int COPY_NEW = 0, COPY_BOTH = 1, COPY_LAST = 3, SKIP = 4,
339 // give weights to the above. make sure we delete about the same as we insert
340 private static final WeightedIndex choice = new WeightedIndex(0)
341 .add(new int[] {10, 10, 100, 10});
343 protected void addTo(Target target) {
344 // get contents into separate buffer
345 addBuffer.copyState(target);
347 item.addTo(addBuffer);
348 String newValue = addBuffer.get();
349 if (DEBUG) System.out.println("Old: " + lastValue + ", New:" + newValue);
351 // if not first one, merge with old
352 if (lastValue != null) {
353 mergeBuffer.setLength(0);
356 // the new length is a random value between old and new.
357 int newLenLimit = (int) pick(target.random, lastValue.length(), newValue.length());
359 while (mergeBuffer.length() < newLenLimit
360 && newIndex < newValue.length()
361 && lastIndex < lastValue.length()) {
362 int c = choice.toIndex(target.nextDouble());
363 if (c == COPY_NEW || c == COPY_BOTH || c == SKIP) {
364 newIndex = getChar(newValue, newIndex, mergeBuffer, c < LEAST_SKIP);
365 if (mergeBuffer.length() >= newLenLimit) break;
367 if (c == COPY_LAST || c == COPY_BOTH || c == SKIP) {
368 lastIndex = getChar(lastValue, lastIndex, mergeBuffer, c < LEAST_SKIP);
371 newValue = mergeBuffer.toString();
373 lastValue = newValue;
374 target.append(newValue);
375 if (DEBUG) System.out.println("Result: " + newValue);
378 public String getInternal(int depth, Set alreadySeen) {
379 String result = checkName(name, alreadySeen);
380 if (result.startsWith("$")) return result;
381 return indent(depth) + result + "MORPH("
382 + item.getInternal(depth+1, alreadySeen)
387 * @see Pick#match(java.lang.String, Pick.Position)
389 protected boolean match(String input, Position p) {
390 // TODO Auto-generated method stub
395 /* Add character if we can
397 static int getChar(String newValue, int newIndex, StringBuffer mergeBuffer, boolean copy) {
398 if (newIndex >= newValue.length()) return newIndex;
399 int cp = UTF16.charAt(newValue,newIndex);
400 if (copy) UTF16.append(mergeBuffer, cp);
401 return newIndex + UTF16.getCharCount(cp);
406 appendQuoted(target, addBuffer.toString(), quoteBuffer);
408 StringBuffer swapTemp = addBuffer;
416 static class Quote extends ItemPick {
420 protected void addTo(Target target) {
421 target.quoter.setQuoting(true);
423 target.quoter.setQuoting(false);
426 protected boolean match(String s, Position p) {
430 public String getInternal(int depth, Set alreadySeen) {
431 String result = checkName(name, alreadySeen);
432 if (result.startsWith("$")) return result;
433 return indent(depth) + result + "QUOTE(" + item.getInternal(depth+1, alreadySeen)
438 private static class Literal extends FinalPick {
439 public String toString() {
442 private Literal(String source) {
445 protected void addTo(Target target) {
448 protected boolean match(String input, Position p) {
449 int len = name.length();
450 if (input.regionMatches(p.index, name, 0, len)) {
457 public String getInternal(int depth, Set alreadySeen) {
458 return "'" + name + "'";
462 public static class Position {
463 public ArrayList failures = new ArrayList();
466 public String maxType;
467 public void setMax(String type) {
468 if (index >= maxInt) {
472 public String toString() {
473 return "index; " + index
474 + ", maxInt:" + maxInt
475 + ", maxType: " + maxType;
477 /*private static final Object BAD = new Object();
478 private static final Object GOOD = new Object();*/
480 public boolean isFailure(Pick pick, int item) {
481 ArrayList val = (ArrayList)failures.get(index);
482 if (val == null) return false;
483 Set set = (Set)val.get(item);
484 if (set == null) return false;
485 return !set.contains(pick);
487 public void setFailure(Pick pick, int item) {
488 ArrayList val = (ArrayList)failures.get(index);
490 val = new ArrayList();
491 failures.set(index, val);
493 Set set = (Set)val.get(item);
503 public static final Pick NOTHING = new Nothing();
506 private static class Nothing extends FinalPick {
507 protected void addTo(Target target) {}
508 protected boolean match(String input, Position p) {
511 public String getInternal(int depth, Set alreadySeen) {
512 return indent(depth) + "\u00F8";
519 abstract static class Visitor {
520 Set already = new HashSet();
521 // Note: each visitor should return the Pick that will replace a (or a itself)
522 abstract Pick handle(Pick a);
523 boolean alreadyEntered(Pick item) {
524 boolean result = already.contains(item);
533 protected abstract Pick visit(Visitor visitor);
535 static class Replacer extends Visitor {
538 Replacer(String toReplace, Pick replacement) {
539 this.toReplace = toReplace;
540 this.replacement = replacement;
542 public Pick handle(Pick a) {
543 if (toReplace.equals(a.name)) {
550 abstract private static class FinalPick extends Pick {
551 public Pick visit(Visitor visitor) {
552 return visitor.handle(this);
556 private abstract static class ItemPick extends Pick {
559 ItemPick (Pick item) {
563 public Pick visit(Visitor visitor) {
564 Pick result = visitor.handle(this);
565 if (visitor.alreadyEntered(this)) return result;
566 if (item != null) item = item.visit(visitor);
571 private abstract static class ListPick extends Pick {
572 protected Pick[] items = new Pick[0];
575 if (items.length > 1) return this;
576 if (items.length == 1) return items[0];
585 return items[items.length-1];
588 void setLast(Pick newOne) {
589 items[items.length-1] = newOne;
592 protected void addInternal(Pick[] objs) {
593 int lastLen = items.length;
594 items = realloc(items, items.length + objs.length);
595 for (int i = 0; i < objs.length; ++i) {
596 items[lastLen + i] = objs[i];
600 public Pick visit(Visitor visitor) {
601 Pick result = visitor.handle(this);
602 if (visitor.alreadyEntered(this)) return result;
603 for (int i = 0; i < items.length; ++i) {
604 items[i] = items[i].visit(visitor);
611 * Simple class to distribute a number between 0 (inclusive) and 1 (exclusive) among
612 * a number of indices, where each index is weighted.
613 * Item weights may be zero, but cannot be negative.
616 // As in other case, we use an array for runtime speed; don't care about buildspeed.
617 public static class WeightedIndex {
618 private int[] weights = new int[0];
619 private int minCount = 0;
620 private double total;
622 public WeightedIndex(int minCount) {
623 this.minCount = minCount;
626 public WeightedIndex add(int count, int itemWeights) {
628 int[] newWeights = new int[count];
629 if (itemWeights < 1) itemWeights = 1;
630 Arrays.fill(newWeights, 0, count, itemWeights);
633 return this; // for chaining
636 public WeightedIndex add(int[] newWeights) {
637 return add(newWeights.length, newWeights);
640 public WeightedIndex add(int maxCount, int[] newWeights) {
641 if (newWeights == null) newWeights = new int[]{1};
642 int oldLen = weights.length;
643 if (maxCount < newWeights.length) maxCount = newWeights.length;
644 weights = (int[]) realloc(weights, weights.length + maxCount);
645 System.arraycopy(newWeights, 0, weights, oldLen, newWeights.length);
646 int lastWeight = weights[oldLen + newWeights.length-1];
647 for (int i = oldLen + newWeights.length; i < maxCount; ++i) {
648 weights[i] = lastWeight;
651 for (int i = 0; i < weights.length; ++i) {
652 if (weights[i] < 0) {
653 throw new RuntimeException("only positive weights: " + i);
657 return this; // for chaining
660 // TODO, make this more efficient
661 public int toIndex(double zeroToOne) {
662 double weight = zeroToOne*total;
664 for (i = 0; i < weights.length; ++i) {
665 weight -= weights[i];
666 if (weight <= 0) break;
670 public String toString() {
672 for (int i = 0; i < minCount; ++i) {
673 if (result.length() != 0) result += ",";
676 for (int i = 0; i < weights.length; ++i) {
677 if (result.length() != 0) result += ",";
678 result += weights[i];
684 private static Pick convert(Object obj) {
685 if (obj instanceof Pick) return (Pick)obj;
686 return new Literal(obj.toString(), false);
691 static public int pick(Random random, int start, int end) {
692 return start + (int)(random.nextDouble() * (end + 1 - start));
695 static public double pick(Random random, double start, double end) {
696 return start + (random.nextDouble() * (end + 1 - start));
699 static public boolean pick(Random random, double percent) {
700 return random.nextDouble() <= percent;
703 static public int pick(Random random, UnicodeSet s) {
704 return s.charAt(pick(random, 0,s.size()-1));
707 static public String pick(Random random, String[] source) {
708 return source[pick(random, 0, source.length-1)];
711 // these utilities really ought to be in Java
713 public static double[] realloc(double[] source, int newSize) {
714 double[] temp = new double[newSize];
715 if (newSize > source.length) newSize = source.length;
716 if (newSize != 0) System.arraycopy(source,0,temp,0,newSize);
720 public static int[] realloc(int[] source, int newSize) {
721 int[] temp = new int[newSize];
722 if (newSize > source.length) newSize = source.length;
723 if (newSize != 0) System.arraycopy(source,0,temp,0,newSize);
727 public static Pick[] realloc(Pick[] source, int newSize) {
728 Pick[] temp = new Pick[newSize];
729 if (newSize > source.length) newSize = source.length;
730 if (newSize != 0) System.arraycopy(source,0,temp,0,newSize);
735 /*private static void append(StringBuffer target, String toAdd, StringBuffer quoteBuffer) {
736 Utility.appendToRule(target, (int)-1, true, false, quoteBuffer); // close previous quote
737 if (DEBUG) System.out.println("\"" + toAdd + "\"");
738 target.append(toAdd);
741 private static void appendQuoted(StringBuffer target, String toAdd, StringBuffer quoteBuffer) {
742 if (DEBUG) System.out.println("\"" + toAdd + "\"");
743 Utility.appendToRule(target, toAdd, false, false, quoteBuffer);
747 public static abstract class MatchHandler {
748 public abstract void handleString(String source, int start, int limit);
749 public abstract void handleSequence(String source, int start, int limit);
750 public abstract void handleAlternation(String source, int start, int limit);
755 // redistributes random value
756 // values are still between 0 and 1, but with a different distribution
757 public interface Spread {
758 public double spread(double value);
761 // give the weight for the high end.
762 // values are linearly scaled according to the weight.
763 static public class SimpleSpread implements Spread {
764 static final Spread FLAT = new SimpleSpread(1.0);
765 boolean flat = false;
767 public SimpleSpread(double maxWeight) {
768 if (maxWeight > 0.999 && maxWeight < 1.001) {
771 double q = (maxWeight - 1.0);
777 public double spread(double value) {
778 if (flat) return value;
779 value = aa + Math.sqrt(bb + cc*value);
780 if (value < 0.0) return 0.0; // catch math gorp
781 if (value >= 1.0) return 1.0;
785 static public int pick(Spread spread, Random random, int start, int end) {
786 return start + (int)(spread.spread(random.nextDouble()) * (end + 1 - start));