xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/Pick.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 /*
2  *******************************************************************************
3  * Copyright (C) 2002-2012, International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package org.unicode.cldr.util;
8 
9 import com.google.common.collect.LinkedHashMultiset;
10 import com.google.common.collect.Multiset;
11 import com.ibm.icu.text.UTF16;
12 import com.ibm.icu.text.UnicodeSet;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashSet;
16 import java.util.Random;
17 import java.util.Set;
18 
19 public abstract class Pick {
20     private static boolean DEBUG = false;
21 
22     // for using to get strings
23 
24     static class Target {
25 
26         static int MAX_COUNT = 5;
27 
28         private Pick pick;
29         private Random random;
30         private Quoter quoter;
31         private Multiset<Pick> stack = LinkedHashMultiset.create();
32 
make(Pick pick, Random random, Quoter quoter)33         public static Target make(Pick pick, Random random, Quoter quoter) {
34             Target result = new Target();
35             result.pick = pick;
36             result.random = random;
37             result.quoter = quoter;
38             return result;
39         }
40 
next()41         public String next() {
42             quoter.clear();
43             stack.clear();
44             while (true) {
45                 try {
46                     pick.addTo(this);
47                     return get();
48                 } catch (DepthExceededException e) {
49                     for (Pick pick : e.target.stack.elementSet()) {
50                         System.out.println(pick.name + ": " + e.target.stack.count(pick));
51                     }
52                     int debug = 0;
53                 }
54             }
55         }
56 
get()57         public String get() {
58             return quoter.toString();
59         }
60 
copyState(Target other)61         private void copyState(Target other) {
62             random = other.random;
63         }
64 
clear()65         private void clear() {
66             quoter.clear();
67         }
68 
69         /*private int length() {
70             return quoter.length();
71         }*/
append(int codepoint)72         private Target append(int codepoint) {
73             if (codepoint == '-') {
74                 int debug = 0;
75             }
76             quoter.append(codepoint);
77             return this;
78         }
79 
append(String s)80         private Target append(String s) {
81             if (s.contains("-")) {
82                 int debug = 0;
83             }
84             quoter.append(s);
85             return this;
86         }
87 
88         // must return value between 0 (inc) and 1 (exc)
nextDouble()89         private double nextDouble() {
90             return random.nextDouble();
91         }
92 
exitStack(Pick pick)93         public void exitStack(Pick pick) {
94             stack.remove(pick);
95         }
96 
enterStack(Pick pick)97         public void enterStack(Pick pick) {
98             int count = stack.count(pick);
99             if (count > MAX_COUNT) {
100                 throw new DepthExceededException(this, pick);
101             }
102             stack.add(pick);
103         }
104     }
105 
106     // for Building
107 
replace(String toReplace, Pick replacement)108     public Pick replace(String toReplace, Pick replacement) {
109         Replacer visitor = new Replacer(toReplace, replacement);
110         return visit(visitor);
111     }
112 
name(String nameStr)113     public Pick name(String nameStr) {
114         name = nameStr;
115         return this;
116     }
117 
makeSequence()118     public static Pick.Sequence makeSequence() {
119         return new Sequence();
120     }
121 
makeAlternation()122     public static Pick.Alternation makeAlternation() {
123         return new Alternation();
124     }
125     /*
126     static public Pick.Sequence and(Object item) {
127         return new Sequence().and2(item);
128     }
129     static public Pick.Sequence and(Object[] items) {
130         return new Sequence().and2(items);
131     }
132     static public Pick.Alternation or(int itemWeight, Object item) {
133         return new Alternation().or2(itemWeight, item);
134     }
135     static public Pick.Alternation or(Object[] items) {
136         return new Alternation().or2(1, items);
137     }
138     static public Pick.Alternation or(int itemWeight, Object[] items) {
139         return new Alternation().or2(itemWeight, items);
140     }
141     static public Pick.Alternation or(int[] itemWeights, Object[] items) {
142         return new Alternation().or2(itemWeights, items);
143     }
144 
145     static public Pick maybe(int percent, Object item) {
146         return new Repeat(0, 1, new int[]{100-percent, percent}, item);
147         //return Pick.or(1.0-percent, NOTHING).or2(percent, item);
148     }
149     static public Pick repeat(int minCount, int maxCount, int itemWeights, Object item) {
150         return new Repeat(minCount, maxCount, itemWeights, item);
151     }
152 
153     static public Pick codePoint(String source) {
154         return new CodePoint(new UnicodeSet(source));
155     }
156      */
157 
repeat(int minCount, int maxCount, int[] itemWeights, Pick item)158     public static Pick repeat(int minCount, int maxCount, int[] itemWeights, Pick item) {
159         return new Repeat(minCount, maxCount, itemWeights, item);
160     }
161 
codePoint(UnicodeSet source)162     public static Pick codePoint(UnicodeSet source) {
163         return new CodePoint(source);
164     }
165 
string(String source)166     public static Pick string(String source) {
167         return new Literal(source);
168     }
169     /*
170     static public Pick unquoted(String source) {
171         return new Literal(source);
172     }
173     static public Pick string(int minLength, int maxLength, Pick item) {
174         return new Morph(item, minLength, maxLength);
175     }
176      */
177 
getInternal(int depth, Set alreadySeen)178     public abstract String getInternal(int depth, Set alreadySeen);
179     // Internals
180 
181     protected String name;
182 
addTo(Target target)183     protected abstract void addTo(Target target);
184 
match(String input, Position p)185     public abstract boolean match(String input, Position p);
186 
187     static class DepthExceededException extends RuntimeException {
188         private static final long serialVersionUID = -2478735802169169979L;
189         private final Target target;
190         private final Pick pick;
191 
DepthExceededException(Target target, Pick pick)192         public DepthExceededException(Target target, Pick pick) {
193             this.target = target;
194             this.pick = pick;
195         }
196     }
197 
198     public static class Sequence extends ListPick {
and2(Pick item)199         public Sequence and2(Pick item) {
200             addInternal(new Pick[] {item}); // we don't care about perf
201             return this; // for chaining
202         }
203 
and2(Pick[] itemArray)204         public Sequence and2(Pick[] itemArray) {
205             addInternal(itemArray);
206             return this; // for chaining
207         }
208 
209         @Override
addTo(Target target)210         protected void addTo(Target target) {
211             for (int i = 0; i < items.length; ++i) {
212                 items[i].addTo(target);
213             }
214         }
215 
216         @Override
getInternal(int depth, Set alreadySeen)217         public String getInternal(int depth, Set alreadySeen) {
218             String result = checkName(name, alreadySeen);
219             if (result.startsWith("$")) return result;
220             result = indent(depth) + result + "SEQ(";
221             for (int i = 0; i < items.length; ++i) {
222                 if (i != 0) result += ", ";
223                 result += items[i].getInternal(depth + 1, alreadySeen);
224             }
225             result += ")";
226             return result;
227         }
228 
229         // keep private
Sequence()230         private Sequence() {}
231 
232         @Override
match(String input, Position p)233         public boolean match(String input, Position p) {
234             int originalIndex = p.index;
235             for (int i = 0; i < items.length; ++i) {
236                 if (!items[i].match(input, p)) {
237                     p.index = originalIndex;
238                     return false;
239                 }
240             }
241             return true;
242         }
243     }
244 
checkName(String nameStr, Set alreadySeen)245     String checkName(String nameStr, Set alreadySeen) {
246         if (nameStr == null) return "";
247         if (alreadySeen.contains(nameStr)) return nameStr;
248         alreadySeen.add(nameStr);
249         return "{" + nameStr + "=}";
250     }
251 
252     public static class Alternation extends ListPick {
253         private WeightedIndex weightedIndex = new WeightedIndex(0);
254 
or2(Pick[] newItems)255         public Alternation or2(Pick[] newItems) {
256             return or2(1, newItems);
257         }
258 
or2(int itemWeight, Pick item)259         public Alternation or2(int itemWeight, Pick item) {
260             return or2(itemWeight, new Pick[] {item}); // we don't care about perf
261         }
262 
or2(int itemWeight, Pick[] newItems)263         public Alternation or2(int itemWeight, Pick[] newItems) {
264             int[] itemWeights = new int[newItems.length];
265             Arrays.fill(itemWeights, itemWeight);
266             return or2(itemWeights, newItems); // we don't care about perf
267         }
268 
or2(int[] itemWeights, Pick[] newItems)269         public Alternation or2(int[] itemWeights, Pick[] newItems) {
270             if (newItems.length != itemWeights.length) {
271                 throw new ArrayIndexOutOfBoundsException(
272                         "or lengths must be equal: "
273                                 + newItems.length
274                                 + " != "
275                                 + itemWeights.length);
276             }
277             // int lastLen = this.items.length;
278             addInternal(newItems);
279             weightedIndex.add(itemWeights);
280             return this; // for chaining
281         }
282 
283         @Override
addTo(Target target)284         protected void addTo(Target target) {
285             final int index = weightedIndex.toIndex(target.nextDouble());
286             int last = index - 1;
287             if (last < weightedIndex.minCount) {
288                 last -= weightedIndex.minCount;
289                 last += weightedIndex.weights.length;
290             }
291             for (int i = index; ; ) {
292                 try {
293                     target.enterStack(this);
294                     items[index].addTo(target); // may cause exception if stack overflows
295                     // no exception, continue normally
296                     target.exitStack(this);
297                     return;
298                 } catch (DepthExceededException e) {
299                     target.exitStack(this);
300                     if (i == last) {
301                         throw e; // we tried all the options, and none of them work.
302                     }
303                     i++;
304                     if (i >= weightedIndex.weights.length) {
305                         i -= weightedIndex.weights.length - weightedIndex.minCount;
306                     }
307                 }
308             }
309         }
310 
311         @Override
getInternal(int depth, Set alreadySeen)312         public String getInternal(int depth, Set alreadySeen) {
313             String result = checkName(name, alreadySeen);
314             if (result.startsWith("$")) return result;
315             result = indent(depth) + result + "OR(";
316             for (int i = 0; i < items.length; ++i) {
317                 if (i != 0) result += ", ";
318                 result +=
319                         items[i].getInternal(depth + 1, alreadySeen)
320                                 + "/"
321                                 + weightedIndex.weights[i];
322             }
323             return result + ")";
324         }
325 
326         // keep private
Alternation()327         private Alternation() {}
328 
329         // take first matching option
330         @Override
match(String input, Position p)331         public boolean match(String input, Position p) {
332             for (int i = 0; i < weightedIndex.weights.length; ++i) {
333                 if (p.isFailure(this, i)) continue;
334                 if (items[i].match(input, p)) return true;
335                 p.setFailure(this, i);
336             }
337             return false;
338         }
339     }
340 
indent(int depth)341     private static String indent(int depth) {
342         String result = "\r\n";
343         for (int i = 0; i < depth; ++i) {
344             result += " ";
345         }
346         return result;
347     }
348 
349     private static class Repeat extends ItemPick {
350         WeightedIndex weightedIndex;
351         int minCount = 0;
352 
Repeat(int minCount, int maxCount, int[] itemWeights, Pick item)353         private Repeat(int minCount, int maxCount, int[] itemWeights, Pick item) {
354             super(item);
355             weightedIndex = new WeightedIndex(minCount).add(maxCount - minCount + 1, itemWeights);
356         }
357 
358         /*private Repeat(int minCount, int maxCount, int itemWeight, Pick item) {
359             super(item);
360             weightedIndex = new WeightedIndex(minCount).add(maxCount-minCount+1, itemWeight);
361         }*/
362         /*
363         private Repeat(int minCount, int maxCount, Object item) {
364             this.item = convert(item);
365             weightedIndex = new WeightedIndex(minCount).add(maxCount-minCount+1, 1);
366         }
367          */
368         @Override
addTo(Target target)369         protected void addTo(Target target) {
370             // int count ;
371             final int count = weightedIndex.toIndex(target.nextDouble());
372             for (int i = count; i > 0; --i) {
373                 item.addTo(target);
374             }
375         }
376 
377         @Override
getInternal(int depth, Set alreadySeen)378         public String getInternal(int depth, Set alreadySeen) {
379             String result = checkName(name, alreadySeen);
380             if (result.startsWith("$")) return result;
381             result =
382                     indent(depth)
383                             + result
384                             + "REPEAT("
385                             + weightedIndex
386                             + "; "
387                             + item.getInternal(depth + 1, alreadySeen)
388                             + ")";
389             return result;
390         }
391 
392         // match longest, e.g. up to just before a failure
393         @Override
match(String input, Position p)394         public boolean match(String input, Position p) {
395             // int bestMatch = p.index;
396             int count = 0;
397             for (int i = 0; i < weightedIndex.weights.length; ++i) {
398                 if (p.isFailure(this, i)) break;
399                 if (!item.match(input, p)) {
400                     p.setFailure(this, i);
401                     break;
402                 }
403                 // bestMatch = p.index;
404                 count++;
405             }
406             if (count >= minCount) {
407                 return true;
408             }
409             // TODO fix failure
410             return false;
411         }
412     }
413 
414     private static class CodePoint extends FinalPick {
415         private UnicodeSet source;
416 
CodePoint(UnicodeSet source)417         private CodePoint(UnicodeSet source) {
418             this.source = source;
419         }
420 
421         @Override
addTo(Target target)422         protected void addTo(Target target) {
423             target.append(source.charAt(pick(target.random, 0, source.size() - 1)));
424         }
425 
426         @Override
match(String s, Position p)427         public boolean match(String s, Position p) {
428             int cp = UTF16.charAt(s, p.index);
429             if (source.contains(cp)) {
430                 p.index += UTF16.getCharCount(cp);
431                 return true;
432             }
433             p.setMax("codePoint");
434             return false;
435         }
436 
437         @Override
getInternal(int depth, Set alreadySeen)438         public String getInternal(int depth, Set alreadySeen) {
439             String result = checkName(name, alreadySeen);
440             if (result.startsWith("$")) return result;
441             return source.toString();
442         }
443     }
444 
445     static class Morph extends ItemPick {
Morph(Pick item)446         Morph(Pick item) {
447             super(item);
448         }
449 
450         private String lastValue = null;
451         private Target addBuffer = Target.make(this, null, new Quoter.RuleQuoter());
452         private StringBuffer mergeBuffer = new StringBuffer();
453 
454         private static final int COPY_NEW = 0,
455                 COPY_BOTH = 1,
456                 COPY_LAST = 3,
457                 SKIP = 4,
458                 LEAST_SKIP = 4;
459         // give weights to the above. make sure we delete about the same as we insert
460         private static final WeightedIndex choice =
461                 new WeightedIndex(0).add(new int[] {10, 10, 100, 10});
462 
463         @Override
addTo(Target target)464         protected void addTo(Target target) {
465             // get contents into separate buffer
466             addBuffer.copyState(target);
467             addBuffer.clear();
468             item.addTo(addBuffer);
469             String newValue = addBuffer.get();
470             if (DEBUG) System.out.println("Old: " + lastValue + ", New:" + newValue);
471 
472             // if not first one, merge with old
473             if (lastValue != null) {
474                 mergeBuffer.setLength(0);
475                 int lastIndex = 0;
476                 int newIndex = 0;
477                 // the new length is a random value between old and new.
478                 int newLenLimit = pick(target.random, lastValue.length(), newValue.length());
479 
480                 while (mergeBuffer.length() < newLenLimit
481                         && newIndex < newValue.length()
482                         && lastIndex < lastValue.length()) {
483                     int c = choice.toIndex(target.nextDouble());
484                     if (c == COPY_NEW || c == COPY_BOTH || c == SKIP) {
485                         newIndex = getChar(newValue, newIndex, mergeBuffer, c < LEAST_SKIP);
486                         if (mergeBuffer.length() >= newLenLimit) break;
487                     }
488                     if (c == COPY_LAST || c == COPY_BOTH || c == SKIP) {
489                         lastIndex = getChar(lastValue, lastIndex, mergeBuffer, c < LEAST_SKIP);
490                     }
491                 }
492                 newValue = mergeBuffer.toString();
493             }
494             lastValue = newValue;
495             target.append(newValue);
496             if (DEBUG) System.out.println("Result: " + newValue);
497         }
498 
499         @Override
getInternal(int depth, Set alreadySeen)500         public String getInternal(int depth, Set alreadySeen) {
501             String result = checkName(name, alreadySeen);
502             if (result.startsWith("$")) return result;
503             return indent(depth)
504                     + result
505                     + "MORPH("
506                     + item.getInternal(depth + 1, alreadySeen)
507                     + ")";
508         }
509 
510         /* (non-Javadoc)
511          * @see Pick#match(java.lang.String, Pick.Position)
512          */
513         @Override
match(String input, Position p)514         public boolean match(String input, Position p) {
515             // TODO Auto-generated method stub
516             return false;
517         }
518     }
519 
520     /* Add character if we can
521      */
getChar(String newValue, int newIndex, StringBuffer mergeBuffer, boolean copy)522     static int getChar(String newValue, int newIndex, StringBuffer mergeBuffer, boolean copy) {
523         if (newIndex >= newValue.length()) return newIndex;
524         int cp = UTF16.charAt(newValue, newIndex);
525         if (copy) UTF16.append(mergeBuffer, cp);
526         return newIndex + UTF16.getCharCount(cp);
527     }
528 
529     /*
530             // quoted add
531             appendQuoted(target, addBuffer.toString(), quoteBuffer);
532             // fix buffers
533             StringBuffer swapTemp = addBuffer;
534             addBuffer = source;
535             source = swapTemp;
536         }
537     }
538      */
539 
540     static class Quote extends ItemPick {
Quote(Pick item)541         Quote(Pick item) {
542             super(item);
543         }
544 
545         @Override
addTo(Target target)546         protected void addTo(Target target) {
547             target.quoter.setQuoting(true);
548             item.addTo(target);
549             target.quoter.setQuoting(false);
550         }
551 
552         @Override
match(String s, Position p)553         public boolean match(String s, Position p) {
554             return false;
555         }
556 
557         @Override
getInternal(int depth, Set alreadySeen)558         public String getInternal(int depth, Set alreadySeen) {
559             String result = checkName(name, alreadySeen);
560             if (result.startsWith("$")) return result;
561             return indent(depth)
562                     + result
563                     + "QUOTE("
564                     + item.getInternal(depth + 1, alreadySeen)
565                     + ")";
566         }
567     }
568 
569     private static class Literal extends FinalPick {
570         @Override
toString()571         public String toString() {
572             return name;
573         }
574 
Literal(String source)575         private Literal(String source) {
576             this.name = source;
577         }
578 
579         @Override
addTo(Target target)580         protected void addTo(Target target) {
581             target.append(name);
582         }
583 
584         @Override
match(String input, Position p)585         public boolean match(String input, Position p) {
586             int len = name.length();
587             if (input.regionMatches(p.index, name, 0, len)) {
588                 p.index += len;
589                 return true;
590             }
591             p.setMax("literal");
592             return false;
593         }
594 
595         @Override
getInternal(int depth, Set alreadySeen)596         public String getInternal(int depth, Set alreadySeen) {
597             return "'" + name + "'";
598         }
599     }
600 
601     public static class Position {
602         public ArrayList failures = new ArrayList();
603         public int index;
604         public int maxInt;
605         public String maxType;
606 
setMax(String type)607         public void setMax(String type) {
608             if (index >= maxInt) {
609                 maxType = type;
610             }
611         }
612 
613         @Override
toString()614         public String toString() {
615             return "index; " + index + ", maxInt:" + maxInt + ", maxType: " + maxType;
616         }
617         /*private static final Object BAD = new Object();
618         private static final Object GOOD = new Object();*/
619 
isFailure(Pick pick, int item)620         public boolean isFailure(Pick pick, int item) {
621             ArrayList val = (ArrayList) failures.get(index);
622             if (val == null) return false;
623             Set set = (Set) val.get(item);
624             if (set == null) return false;
625             return !set.contains(pick);
626         }
627 
setFailure(Pick pick, int item)628         public void setFailure(Pick pick, int item) {
629             ArrayList val = (ArrayList) failures.get(index);
630             if (val == null) {
631                 val = new ArrayList();
632                 failures.set(index, val);
633             }
634             Set set = (Set) val.get(item);
635             if (set == null) {
636                 set = new HashSet();
637                 val.set(item, set);
638             }
639             set.add(pick);
640         }
641     }
642 
643     /*
644     public static final Pick NOTHING = new Nothing();
645 
646 
647     private static class Nothing extends FinalPick {
648         protected void addTo(Target target) {}
649         protected boolean match(String input, Position p) {
650             return true;
651         }
652         public String getInternal(int depth, Set alreadySeen) {
653             return indent(depth) + "\u00F8";
654         }
655     }
656      */
657 
658     // intermediates
659 
660     abstract static class Visitor {
661         Set already = new HashSet();
662 
663         // Note: each visitor should return the Pick that will replace a (or a itself)
handle(Pick a)664         abstract Pick handle(Pick a);
665 
alreadyEntered(Pick item)666         boolean alreadyEntered(Pick item) {
667             boolean result = already.contains(item);
668             already.add(item);
669             return result;
670         }
671 
reset()672         void reset() {
673             already.clear();
674         }
675     }
676 
visit(Visitor visitor)677     protected abstract Pick visit(Visitor visitor);
678 
679     static class Replacer extends Visitor {
680         String toReplace;
681         Pick replacement;
682 
Replacer(String toReplace, Pick replacement)683         Replacer(String toReplace, Pick replacement) {
684             this.toReplace = toReplace;
685             this.replacement = replacement;
686         }
687 
688         @Override
handle(Pick a)689         public Pick handle(Pick a) {
690             if (toReplace.equals(a.name)) {
691                 a = replacement;
692             }
693             return a;
694         }
695     }
696 
697     private abstract static class FinalPick extends Pick {
698         @Override
visit(Visitor visitor)699         public Pick visit(Visitor visitor) {
700             return visitor.handle(this);
701         }
702     }
703 
704     private abstract static class ItemPick extends Pick {
705         protected Pick item;
706 
ItemPick(Pick item)707         ItemPick(Pick item) {
708             this.item = item;
709         }
710 
711         @Override
visit(Visitor visitor)712         public Pick visit(Visitor visitor) {
713             Pick result = visitor.handle(this);
714             if (visitor.alreadyEntered(this)) return result;
715             if (item != null) item = item.visit(visitor);
716             return result;
717         }
718     }
719 
720     private abstract static class ListPick extends Pick {
721         protected Pick[] items = new Pick[0];
722 
simplify()723         Pick simplify() {
724             if (items.length > 1) return this;
725             if (items.length == 1) return items[0];
726             return null;
727         }
728 
size()729         int size() {
730             return items.length;
731         }
732 
getLast()733         Pick getLast() {
734             return items[items.length - 1];
735         }
736 
setLast(Pick newOne)737         void setLast(Pick newOne) {
738             items[items.length - 1] = newOne;
739         }
740 
addInternal(Pick[] objs)741         protected void addInternal(Pick[] objs) {
742             int lastLen = items.length;
743             items = realloc(items, items.length + objs.length);
744             for (int i = 0; i < objs.length; ++i) {
745                 items[lastLen + i] = objs[i];
746             }
747         }
748 
749         @Override
visit(Visitor visitor)750         public Pick visit(Visitor visitor) {
751             Pick result = visitor.handle(this);
752             if (visitor.alreadyEntered(this)) return result;
753             for (int i = 0; i < items.length; ++i) {
754                 items[i] = items[i].visit(visitor);
755             }
756             return result;
757         }
758     }
759 
760     /**
761      * Simple class to distribute a number between 0 (inclusive) and 1 (exclusive) among a number of
762      * indices, where each index is weighted. Item weights may be zero, but cannot be negative.
763      *
764      * @author Davis
765      */
766     // As in other case, we use an array for runtime speed; don't care about buildspeed.
767     public static class WeightedIndex {
768         private int[] weights = new int[0];
769         private int minCount = 0;
770         private double total;
771 
WeightedIndex(int minCount)772         public WeightedIndex(int minCount) {
773             this.minCount = minCount;
774         }
775 
add(int count, int itemWeights)776         public WeightedIndex add(int count, int itemWeights) {
777             if (count > 0) {
778                 int[] newWeights = new int[count];
779                 if (itemWeights < 1) itemWeights = 1;
780                 Arrays.fill(newWeights, 0, count, itemWeights);
781                 add(1, newWeights);
782             }
783             return this; // for chaining
784         }
785 
add(int[] newWeights)786         public WeightedIndex add(int[] newWeights) {
787             return add(newWeights.length, newWeights);
788         }
789 
add(int maxCount, int[] newWeights)790         public WeightedIndex add(int maxCount, int[] newWeights) {
791             if (newWeights == null) newWeights = new int[] {1};
792             int oldLen = weights.length;
793             if (maxCount < newWeights.length) maxCount = newWeights.length;
794             weights = realloc(weights, weights.length + maxCount);
795             System.arraycopy(newWeights, 0, weights, oldLen, newWeights.length);
796             int lastWeight = weights[oldLen + newWeights.length - 1];
797             for (int i = oldLen + newWeights.length; i < maxCount; ++i) {
798                 weights[i] = lastWeight;
799             }
800             total = 0;
801             for (int i = 0; i < weights.length; ++i) {
802                 if (weights[i] < 0) {
803                     throw new RuntimeException("only positive weights: " + i);
804                 }
805                 total += weights[i];
806             }
807             return this; // for chaining
808         }
809 
810         // TODO, make this more efficient
toIndex(double zeroToOne)811         public int toIndex(double zeroToOne) {
812             double weight = zeroToOne * total;
813             int i;
814             for (i = 0; i < weights.length; ++i) {
815                 weight -= weights[i];
816                 if (weight <= 0) break;
817             }
818             return i + minCount;
819         }
820 
821         @Override
toString()822         public String toString() {
823             String result = "";
824             for (int i = 0; i < minCount; ++i) {
825                 if (result.length() != 0) result += ",";
826                 result += "0";
827             }
828             for (int i = 0; i < weights.length; ++i) {
829                 if (result.length() != 0) result += ",";
830                 result += weights[i];
831             }
832             return result;
833         }
834     }
835     /*
836     private static Pick convert(Object obj) {
837         if (obj instanceof Pick) return (Pick)obj;
838         return new Literal(obj.toString(), false);
839     }
840      */
841     // Useful statics
842 
pick(Random random, int start, int end)843     public static int pick(Random random, int start, int end) {
844         return start + (int) (random.nextDouble() * (end + 1 - start));
845     }
846 
pick(Random random, double start, double end)847     public static double pick(Random random, double start, double end) {
848         return start + (random.nextDouble() * (end + 1 - start));
849     }
850 
pick(Random random, double percent)851     public static boolean pick(Random random, double percent) {
852         return random.nextDouble() <= percent;
853     }
854 
pick(Random random, UnicodeSet s)855     public static int pick(Random random, UnicodeSet s) {
856         return s.charAt(pick(random, 0, s.size() - 1));
857     }
858 
pick(Random random, String[] source)859     public static String pick(Random random, String[] source) {
860         return source[pick(random, 0, source.length - 1)];
861     }
862 
863     // these utilities really ought to be in Java
864 
realloc(double[] source, int newSize)865     public static double[] realloc(double[] source, int newSize) {
866         double[] temp = new double[newSize];
867         if (newSize > source.length) newSize = source.length;
868         if (newSize != 0) System.arraycopy(source, 0, temp, 0, newSize);
869         return temp;
870     }
871 
realloc(int[] source, int newSize)872     public static int[] realloc(int[] source, int newSize) {
873         int[] temp = new int[newSize];
874         if (newSize > source.length) newSize = source.length;
875         if (newSize != 0) System.arraycopy(source, 0, temp, 0, newSize);
876         return temp;
877     }
878 
realloc(Pick[] source, int newSize)879     public static Pick[] realloc(Pick[] source, int newSize) {
880         Pick[] temp = new Pick[newSize];
881         if (newSize > source.length) newSize = source.length;
882         if (newSize != 0) System.arraycopy(source, 0, temp, 0, newSize);
883         return temp;
884     }
885 
886     // test utilities
887     /*private static void append(StringBuffer target, String toAdd, StringBuffer quoteBuffer) {
888         Utility.appendToRule(target, (int)-1, true, false, quoteBuffer); // close previous quote
889         if (DEBUG) System.out.println("\"" + toAdd + "\"");
890         target.append(toAdd);
891     }
892 
893     private static void appendQuoted(StringBuffer target, String toAdd, StringBuffer quoteBuffer) {
894         if (DEBUG) System.out.println("\"" + toAdd + "\"");
895         Utility.appendToRule(target, toAdd, false, false, quoteBuffer);
896     }*/
897 
898     /*
899     public static abstract class MatchHandler {
900         public abstract void handleString(String source, int start, int limit);
901         public abstract void handleSequence(String source, int start, int limit);
902         public abstract void handleAlternation(String source, int start, int limit);
903 
904     }
905      */
906     /*
907     // redistributes random value
908     // values are still between 0 and 1, but with a different distribution
909     public interface Spread {
910         public double spread(double value);
911     }
912 
913     // give the weight for the high end.
914     // values are linearly scaled according to the weight.
915     static public class SimpleSpread implements Spread {
916         static final Spread FLAT = new SimpleSpread(1.0);
917         boolean flat = false;
918         double aa, bb, cc;
919         public SimpleSpread(double maxWeight) {
920             if (maxWeight > 0.999 && maxWeight < 1.001) {
921                 flat = true;
922             } else {
923                 double q = (maxWeight - 1.0);
924                 aa = -1/q;
925                 bb = 1/(q*q);
926                 cc = (2.0+q)/q;
927            }
928         }
929         public double spread(double value) {
930             if (flat) return value;
931             value = aa + Math.sqrt(bb + cc*value);
932             if (value < 0.0) return 0.0;    // catch math gorp
933             if (value >= 1.0) return 1.0;
934             return value;
935         }
936     }
937     static public int pick(Spread spread, Random random, int start, int end) {
938         return start + (int)(spread.spread(random.nextDouble()) * (end + 1 - start));
939     }
940 
941      */
942 
943 }
944