]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_8_1_1/main/tests/translit/src/com/ibm/icu/dev/test/util/BagFormatter.java
Added flags.
[Dictionary.git] / jars / icu4j-4_8_1_1 / main / tests / translit / src / com / ibm / icu / dev / test / util / BagFormatter.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 2002-2011, International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package com.ibm.icu.dev.test.util;
8
9 import java.io.BufferedReader;
10 import java.io.BufferedWriter;
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileOutputStream;
14 import java.io.IOException;
15 import java.io.InputStreamReader;
16 import java.io.OutputStreamWriter;
17 import java.io.PrintWriter;
18 import java.io.StringWriter;
19 import java.text.MessageFormat;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import com.ibm.icu.impl.Utility;
27 import com.ibm.icu.text.NumberFormat;
28 import com.ibm.icu.text.Transliterator;
29 import com.ibm.icu.text.UTF16;
30 import com.ibm.icu.text.UnicodeSet;
31
32 public class BagFormatter {
33     static final boolean DEBUG = false;
34     public static final boolean SHOW_FILES;
35     static {
36     boolean showFiles = false;
37     try {
38         showFiles = System.getProperty("SHOW_FILES") != null;
39     }
40     catch (SecurityException e) {
41     }
42     SHOW_FILES = showFiles;
43     }
44
45     public static final PrintWriter CONSOLE = new PrintWriter(System.out,true);
46
47     private static PrintWriter log = CONSOLE;
48
49     private boolean abbreviated = false;
50     private String separator = ",";
51     private String prefix = "[";
52     private String suffix = "]";
53     private UnicodeProperty.Factory source;
54     private UnicodeLabel nameSource;
55     private UnicodeLabel labelSource;
56     private UnicodeLabel rangeBreakSource;
57     private UnicodeLabel valueSource;
58     private String propName = "";
59     private boolean showCount = true;
60     //private boolean suppressReserved = true;
61     private boolean hexValue = false;
62     private static final String NULL_VALUE = "_NULL_VALUE_";
63     private int fullTotal = -1;
64     private boolean showTotal = true;
65     private String lineSeparator = "\r\n";
66     private Tabber tabber = new Tabber.MonoTabber();
67
68     /**
69      * Compare two UnicodeSets, and show the differences
70      * @param name1 name of first set to be compared
71      * @param set1 first set
72      * @param name2 name of second set to be compared
73      * @param set2 second set
74      * @return formatted string
75      */
76     public String showSetDifferences(
77         String name1,
78         UnicodeSet set1,
79         String name2,
80         UnicodeSet set2) {
81
82         StringWriter result = new StringWriter();
83         showSetDifferences(new PrintWriter(result),name1,set1,name2,set2);
84         result.flush();
85         return result.getBuffer().toString();
86     }
87
88     public String showSetDifferences(
89         String name1,
90         Collection set1,
91         String name2,
92         Collection set2) {
93
94         StringWriter result = new StringWriter();
95         showSetDifferences(new PrintWriter(result), name1, set1, name2, set2);
96         result.flush();
97         return result.getBuffer().toString();
98     }
99
100     public void showSetDifferences(
101             PrintWriter pw,
102             String name1,
103             UnicodeSet set1,
104             String name2,
105             UnicodeSet set2) {
106         showSetDifferences(pw, name1, set1, name2, set2, -1);
107     }
108     /**
109      * Compare two UnicodeSets, and show the differences
110      * @param name1 name of first set to be compared
111      * @param set1 first set
112      * @param name2 name of second set to be compared
113      * @param set2 second set
114      */
115     public void showSetDifferences(
116         PrintWriter pw,
117         String name1,
118         UnicodeSet set1,
119         String name2,
120         UnicodeSet set2,
121         int flags) 
122     {
123         if (pw == null) pw = CONSOLE;
124         String[] names = { name1, name2 };
125
126         UnicodeSet temp;
127         
128         if ((flags&1) != 0) {
129             temp = new UnicodeSet(set1).removeAll(set2);
130             pw.print(lineSeparator);
131             pw.print(inOut.format(names));
132             pw.print(lineSeparator);
133             showSetNames(pw, temp);
134         }
135
136         if ((flags&2) != 0) {
137             temp = new UnicodeSet(set2).removeAll(set1);
138             pw.print(lineSeparator);
139             pw.print(outIn.format(names));
140             pw.print(lineSeparator);
141             showSetNames(pw, temp);
142         }
143
144         if ((flags&4) != 0) {
145             temp = new UnicodeSet(set2).retainAll(set1);
146             pw.print(lineSeparator);
147             pw.print(inIn.format(names));
148             pw.print(lineSeparator);
149             showSetNames(pw, temp);
150         }
151         pw.flush();
152     }
153
154     public void showSetDifferences(
155         PrintWriter pw,
156         String name1,
157         Collection set1,
158         String name2,
159         Collection set2) {
160
161         if (pw == null) pw = CONSOLE;
162         String[] names = { name1, name2 };
163         // damn'd collection doesn't have a clone, so
164         // we go with Set, even though that
165         // may not preserve order and duplicates
166         Collection temp = new HashSet(set1);
167         temp.removeAll(set2);
168         pw.println();
169         pw.println(inOut.format(names));
170         showSetNames(pw, temp);
171
172         temp.clear();
173         temp.addAll(set2);
174         temp.removeAll(set1);
175         pw.println();
176         pw.println(outIn.format(names));
177         showSetNames(pw, temp);
178
179         temp.clear();
180         temp.addAll(set1);
181         temp.retainAll(set2);
182         pw.println();
183         pw.println(inIn.format(names));
184         showSetNames(pw, temp);
185     }
186
187     /**
188      * Returns a list of items in the collection, with each separated by the separator.
189      * Each item must not be null; its toString() is called for a printable representation
190      * @param c source collection
191      * @return a String representation of the list
192      */
193     public String showSetNames(Object c) {
194         StringWriter buffer = new StringWriter();
195         PrintWriter output = new PrintWriter(buffer);
196         showSetNames(output,c);
197         return buffer.toString();
198     }
199
200     /**
201      * Returns a list of items in the collection, with each separated by the separator.
202      * Each item must not be null; its toString() is called for a printable representation
203      * @param output destination to which to write names
204      * @param c source collection
205      */
206     public void showSetNames(PrintWriter output, Object c) {
207         mainVisitor.doAt(c, output);
208         output.flush();
209     }
210
211     /**
212      * Returns a list of items in the collection, with each separated by the separator.
213      * Each item must not be null; its toString() is called for a printable representation
214      * @param filename destination to which to write names
215      * @param c source collection
216      */
217     public void showSetNames(String filename, Object c) throws IOException {
218         PrintWriter pw = new PrintWriter(
219             new OutputStreamWriter(
220                 new FileOutputStream(filename),"utf-8"));
221         showSetNames(log,c);
222         pw.close();
223     }
224
225     public String getAbbreviatedName(
226         String src,
227         String pattern,
228         String substitute) {
229
230         int matchEnd = NameIterator.findMatchingEnd(src, pattern);
231         int sdiv = src.length() - matchEnd;
232         int pdiv = pattern.length() - matchEnd;
233         StringBuffer result = new StringBuffer();
234         addMatching(
235             src.substring(0, sdiv),
236             pattern.substring(0, pdiv),
237             substitute,
238             result);
239         addMatching(
240             src.substring(sdiv),
241             pattern.substring(pdiv),
242             substitute,
243             result);
244         return result.toString();
245     }
246
247     abstract public static class Relation {
248         abstract public String getRelation(String a, String b);
249     }
250
251     static class NullRelation extends Relation {
252         public String getRelation(String a, String b) { return ""; }
253     }
254
255     private Relation r = new NullRelation();
256
257     public BagFormatter setRelation(Relation r) {
258         this.r = r;
259         return this; // for chaining
260     }
261
262     public Relation getRelation() {
263         return r;
264     }
265
266     /*
267      r.getRelati on(last, s) + quote(s) + "\t#" + UnicodeSetFormatter.getResolvedName(s)
268     */
269     /*
270     static final UnicodeSet NO_NAME =
271         new UnicodeSet("[\\u0080\\u0081\\u0084\\u0099\\p{Cn}\\p{Co}]");
272     static final UnicodeSet HAS_NAME = new UnicodeSet(NO_NAME).complement();
273     static final UnicodeSet NAME_CHARACTERS =
274         new UnicodeSet("[A-Za-z0-9\\<\\>\\-\\ ]");
275
276     public UnicodeSet getSetForName(String namePattern) {
277         UnicodeSet result = new UnicodeSet();
278         Matcher m = Pattern.compile(namePattern).matcher("");
279         // check for no-name items, and add in bulk
280         m.reset("<no name>");
281         if (m.matches()) {
282             result.addAll(NO_NAME);
283         }
284         // check all others
285         UnicodeSetIterator usi = new UnicodeSetIterator(HAS_NAME);
286         while (usi.next()) {
287             String name = getName(usi.codepoint);
288             if (name == null)
289                 continue;
290             m.reset(name);
291             if (m.matches()) {
292                 result.add(usi.codepoint);
293             }
294         }
295         // Note: if Regex had some API so that if we could tell that
296         // an initial substring couldn't match, e.g. "CJK IDEOGRAPH-"
297         // then we could optimize by skipping whole swathes of characters
298         return result;
299     }
300     */
301
302     public BagFormatter setMergeRanges(boolean in) {
303         mergeRanges = in;
304         return this;
305     }
306     public BagFormatter setShowSetAlso(boolean b) {
307         showSetAlso = b;
308         return this;
309     }
310
311     public String getName(int codePoint) {
312         return getName("", codePoint, codePoint);
313     }
314
315     public String getName(String sep, int start, int end) {
316         if (getNameSource() == null || getNameSource() == UnicodeLabel.NULL) return "";
317         String result = getName(start, false);
318         if (start == end) return sep + result;
319         String endString = getName(end, false);
320         if (result.length() == 0 && endString.length() == 0) return sep;
321         if (abbreviated) endString = getAbbreviatedName(endString,result,"~");
322         return sep + result + ".." + endString;
323     }
324
325     public String getName(String s) {
326         return getName(s, false);
327     }
328
329     public static class NameLabel extends UnicodeLabel {
330         UnicodeProperty nameProp;
331         UnicodeSet control;
332         UnicodeSet private_use;
333         UnicodeSet noncharacter;
334         UnicodeSet surrogate;
335
336         public NameLabel(UnicodeProperty.Factory source) {
337             nameProp = source.getProperty("Name");
338             control = source.getSet("gc=Cc");
339             private_use = source.getSet("gc=Co");
340             surrogate = source.getSet("gc=Cs");
341             noncharacter = source.getSet("noncharactercodepoint=yes");
342         }
343
344         public String getValue(int codePoint, boolean isShort) {
345             String hcp = !isShort
346                 ? "U+" + Utility.hex(codePoint, 4) + " "
347                 : "";
348             String result = nameProp.getValue(codePoint);
349             if (result != null)
350                 return hcp + result;
351             if (control.contains(codePoint)) {
352                 return "<control-" + Utility.hex(codePoint, 4) + ">";
353             }
354             if (private_use.contains(codePoint)) {
355                 return "<private-use-" + Utility.hex(codePoint, 4) + ">";
356             }
357             if (surrogate.contains(codePoint)) {
358                 return "<surrogate-" + Utility.hex(codePoint, 4) + ">";
359             }
360             if (noncharacter.contains(codePoint)) {
361                 return "<noncharacter-" + Utility.hex(codePoint, 4) + ">";
362             }
363             //if (suppressReserved) return "";
364             return hcp + "<reserved-" + Utility.hex(codePoint, 4) + ">";
365         }
366
367     }
368
369     // refactored
370     public String getName(int codePoint, boolean withCodePoint) {
371         String result = getNameSource().getValue(codePoint, !withCodePoint);
372         return fixName == null ? result : fixName.transliterate(result);
373     }
374
375     public String getName(String s, boolean withCodePoint) {
376            String result = getNameSource().getValue(s, separator, !withCodePoint);
377         return fixName == null ? result : fixName.transliterate(result);
378      }
379
380     public String hex(String s) {
381         return hex(s,separator);
382     }
383
384     public String hex(String s, String sep) {
385         return UnicodeLabel.HEX.getValue(s, sep, true);
386     }
387
388     public String hex(int start, int end) {
389         String s = Utility.hex(start,4);
390         if (start == end) return s;
391         return s + ".." + Utility.hex(end,4);
392     }
393
394     public BagFormatter setUnicodePropertyFactory(UnicodeProperty.Factory source) {
395         this.source = source;
396         return this;
397     }
398
399     public UnicodeProperty.Factory getUnicodePropertyFactory() {
400         if (source == null) source = ICUPropertyFactory.make();
401         return source;
402     }
403
404     public BagFormatter () {
405     }
406
407     public BagFormatter (UnicodeProperty.Factory source) {
408         setUnicodePropertyFactory(source);
409     }
410
411     public String join(Object o) {
412         return labelVisitor.join(o);
413     }
414
415     // ===== PRIVATES =====
416
417     private Join labelVisitor = new Join();
418
419     private boolean mergeRanges = true;
420     private Transliterator showLiteral = null;
421     private Transliterator fixName = null;
422     private boolean showSetAlso = false;
423
424     private RangeFinder rf = new RangeFinder();
425
426     private MessageFormat inOut = new MessageFormat("In {0}, but not in {1}:");
427     private MessageFormat outIn = new MessageFormat("Not in {0}, but in {1}:");
428     private MessageFormat inIn = new MessageFormat("In both {0}, and in {1}:");
429
430     private MyVisitor mainVisitor = new MyVisitor();
431
432     /*
433     private String getLabels(int start, int end) {
434         Set names = new TreeSet();
435         for (int cp = start; cp <= end; ++cp) {
436             names.add(getLabel(cp));
437         }
438         return labelVisitor.join(names);
439     }
440     */
441
442     private void addMatching(
443         String src,
444         String pattern,
445         String substitute,
446         StringBuffer result) {
447         NameIterator n1 = new NameIterator(src);
448         NameIterator n2 = new NameIterator(pattern);
449         boolean first = true;
450         while (true) {
451             String s1 = n1.next();
452             if (s1 == null)
453                 break;
454             String s2 = n2.next();
455             if (!first)
456                 result.append(" ");
457             first = false;
458             if (s1.equals(s2))
459                 result.append(substitute);
460             else
461                 result.append(s1);
462         }
463     }
464
465     private static NumberFormat nf =
466         NumberFormat.getIntegerInstance(Locale.ENGLISH);
467     static {
468         nf.setGroupingUsed(false);
469     }
470     
471     private int maxWidthOverride = -1;
472     private int maxLabelWidthOverride = -1;
473
474     public BagFormatter setValueWidthOverride(int maxWidthOverride) {
475         this.maxWidthOverride  = maxWidthOverride;
476         return this;
477     }
478
479     public int getValueWidthOverride() {
480         return maxWidthOverride;
481     }
482
483     public BagFormatter setLabelWidthOverride(int maxWidthOverride) {
484         this.maxLabelWidthOverride  = maxWidthOverride;
485         return this;
486     }
487
488     public int getLabelWidthOverride() {
489         return maxLabelWidthOverride;
490     }
491
492
493     private class MyVisitor extends Visitor {
494         private PrintWriter output;
495         String commentSeparator;
496         int counter;
497         int valueSize;
498         int labelSize;
499         boolean isHtml;
500         boolean inTable = false;
501         
502         public void toOutput(String s) {
503           if (isHtml) {
504             if (inTable) {
505               output.print("</table>");
506               inTable = false;
507             }
508             output.print("<p>");
509           }
510           output.print(s);
511           if (isHtml)
512             output.println("</p>");
513           else
514             output.print(lineSeparator);
515         }
516         
517         public void toTable(String s) {
518           if (isHtml && !inTable) {
519             output.print("<table>");
520             inTable = true;
521           }
522           output.print(tabber.process(s) +  lineSeparator);
523         }
524
525         public void doAt(Object c, PrintWriter out) {
526             output = out;
527             isHtml = tabber instanceof Tabber.HTMLTabber;
528             counter = 0;
529             
530             tabber.clear();
531             // old:
532             // 0009..000D    ; White_Space # Cc   [5] <control-0009>..<control-000D>
533             // new
534             // 0009..000D    ; White_Space #Cc  [5] <control>..<control>
535             tabber.add(mergeRanges ? 14 : 6,Tabber.LEFT);
536
537             if (propName.length() > 0) {
538                 tabber.add(propName.length() + 2,Tabber.LEFT);
539             }
540
541             valueSize = maxWidthOverride > 0 ? maxWidthOverride : getValueSource().getMaxWidth(shortValue);
542             
543             if (DEBUG) System.out.println("ValueSize: " + valueSize);
544             if (valueSize > 0) {
545                 tabber.add(valueSize + 2,Tabber.LEFT); // value
546             }
547
548             tabber.add(3,Tabber.LEFT); // comment character
549
550             labelSize = maxLabelWidthOverride > 0 ? maxLabelWidthOverride : getLabelSource(true).getMaxWidth(shortLabel);
551             if (labelSize > 0) {
552                 tabber.add(labelSize + 1,Tabber.LEFT); // value
553             }
554
555             if (mergeRanges && showCount) {
556                 tabber.add(5,Tabber.RIGHT);
557             }
558
559             if (showLiteral != null) {
560                 tabber.add(4,Tabber.LEFT);
561             }
562             //myTabber.add(7,Tabber.LEFT);
563
564             commentSeparator = (showCount || showLiteral != null
565               || getLabelSource(true) != UnicodeLabel.NULL
566               || getNameSource() != UnicodeLabel.NULL)
567             ? "\t #" : "";
568
569             if (DEBUG) System.out.println("Tabber: " + tabber.toString());
570             if (DEBUG) System.out.println("Tabber: " + tabber.process(
571                     "200C..200D\t; White_Space\t #\tCf\t [2]\t ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER"));
572             doAt(c);
573         }
574
575         @SuppressWarnings("unused")
576         public String format(Object o) {
577             StringWriter sw = new StringWriter();
578             PrintWriter pw = new PrintWriter(sw);
579             doAt(o);
580             pw.flush();
581             String result = sw.getBuffer().toString();
582             pw.close();
583             return result;
584         }
585
586         protected void doBefore(Object container, Object o) {
587             if (showSetAlso && container instanceof UnicodeSet) {
588               toOutput("#" + container);
589             }
590         }
591
592         protected void doBetween(Object container, Object lastItem, Object nextItem) {
593         }
594
595         protected void doAfter(Object container, Object o) {
596             if (fullTotal != -1 && fullTotal != counter) {
597                 if (showTotal) {
598                     toOutput("");
599                     toOutput("# The above property value applies to " + nf.format(fullTotal-counter) + " code points not listed here.");
600                     toOutput("# Total code points: " + nf.format(fullTotal));
601                 }
602                 fullTotal = -1;
603             } else if (showTotal) {
604                 toOutput("");
605                 toOutput("# Total code points: " + nf.format(counter));
606             }
607         }
608
609         protected void doSimpleAt(Object o) {
610             if (o instanceof Map.Entry) {
611                 Map.Entry oo = (Map.Entry)o;
612                 Object key = oo.getKey();
613                 Object value = oo.getValue();
614                 doBefore(o, key);
615                 doAt(key);
616                 output.println("\u2192");
617                 doAt(value);
618                 doAfter(o, value);
619                 counter++;
620             } else if (o instanceof Visitor.CodePointRange) {
621                 doAt((Visitor.CodePointRange) o);
622             } else {
623                 String thing = o.toString();
624                 String value = getValueSource() == UnicodeLabel.NULL ? "" : getValueSource().getValue(thing, ",", true);
625                 if (getValueSource() != UnicodeLabel.NULL) value = "\t; " + value;
626                 String label = getLabelSource(true) == UnicodeLabel.NULL ? "" : getLabelSource(true).getValue(thing, ",", true);
627                 if (label.length() != 0) label = " " + label;
628                 toTable(
629                     hex(thing)
630                     + value
631                     + commentSeparator
632                     + label
633                     + insertLiteral(thing)
634                     + "\t"
635                     + getName(thing));
636                 counter++;
637             }
638         }
639
640         protected void doAt(Visitor.CodePointRange usi) {
641             if (!mergeRanges) {
642                 for (int cp = usi.codepoint; cp <= usi.codepointEnd; ++cp) {
643                     showLine(cp, cp);
644                 }
645             } else {
646                 rf.reset(usi.codepoint, usi.codepointEnd + 1);
647                 while (rf.next()) {
648                     showLine(rf.start, rf.limit - 1);
649                 }
650             }
651         }
652
653         private void showLine(int start, int end) {
654             String label = getLabelSource(true).getValue(start, shortLabel);
655             String value = getValue(start, shortValue);
656             if (value == NULL_VALUE) return;
657
658             counter += end - start + 1;
659             String pn = propName;
660             if (pn.length() != 0) {
661                 pn = "\t; " + pn;
662             }
663             if (valueSize > 0) {
664                 value = "\t; " + value;
665             } else if (value.length() > 0) {
666                 throw new IllegalArgumentException("maxwidth bogus " + value + "," + getValueSource().getMaxWidth(shortValue));
667             }
668             if (labelSize > 0) {
669                 label = "\t" + label;
670             } else if (label.length() > 0) {
671                 throw new IllegalArgumentException("maxwidth bogus " + label + ", " + getLabelSource(true).getMaxWidth(shortLabel));
672             }
673
674             String count = "";
675             if (mergeRanges && showCount) {
676                 if (end == start) count = "\t";
677                 else count = "\t ["+ nf.format(end - start + 1)+ "]";
678            }
679
680             toTable(
681                 hex(start, end)
682                 + pn
683                 + value
684                 + commentSeparator
685                 + label
686                 + count
687                 + insertLiteral(start, end)
688                 + getName("\t ", start, end));
689         }
690
691         private String insertLiteral(String thing) {
692             return (showLiteral == null ? ""
693                 :  " \t(" + showLiteral.transliterate(thing) + ") ");
694         }
695
696         private String insertLiteral(int start, int end) {
697             return (showLiteral == null ? "" :
698                 " \t(" + showLiteral.transliterate(UTF16.valueOf(start))
699                         + ((start != end)
700                             ? (".." + showLiteral.transliterate(UTF16.valueOf(end)))
701                             : "")
702                 + ") ");
703         }
704         /*
705         private String insertLiteral(int cp) {
706             return (showLiteral == null ? ""
707                 :  " \t(" + showLiteral.transliterate(UTF16.valueOf(cp)) + ") ");
708         }
709         */
710     }
711
712     /**
713      * Iterate through a string, breaking at words.
714      * @author Davis
715      */
716     private static class NameIterator {
717         String source;
718         int position;
719         int limit;
720
721         NameIterator(String source) {
722             this.source = source;
723             this.limit = source.length();
724         }
725         /**
726          * Find next word, including trailing spaces
727          * @return the next word
728          */
729         String next() {
730             if (position >= limit)
731                 return null;
732             int pos = source.indexOf(' ', position);
733             if (pos < 0 || pos >= limit)
734                 pos = limit;
735             String result = source.substring(position, pos);
736             position = pos + 1;
737             return result;
738         }
739
740         static int findMatchingEnd(String s1, String s2) {
741             int i = s1.length();
742             int j = s2.length();
743             try {
744                 while (true) {
745                     --i; // decrement both before calling function!
746                     --j;
747                     if (s1.charAt(i) != s2.charAt(j))
748                         break;
749                 }
750             } catch (Exception e) {} // run off start
751
752             ++i; // counteract increment
753             i = s1.indexOf(' ', i); // move forward to space
754             if (i < 0)
755                 return 0;
756             return s1.length() - i;
757         }
758     }
759
760     private class RangeFinder {
761         int start, limit;
762         private int veryLimit;
763         //String label, value;
764         void reset(int rangeStart, int rangeLimit) {
765             limit = rangeStart;
766             veryLimit = rangeLimit;
767         }
768         boolean next() {
769             if (limit >= veryLimit)
770                 return false;
771             start = limit; // set to end of last
772             String label = getLabelSource(false).getValue(limit, true);
773             String value = getValue(limit, true);
774             String breaker = getRangeBreakSource().getValue(limit,true);
775             if (DEBUG && limit < 0x7F) System.out.println("Label: " + label + ", Value: " + value + ", Break: " + breaker);
776             limit++;
777             for (; limit < veryLimit; limit++) {
778                 String s = getLabelSource(false).getValue(limit, true);
779                 String v = getValue(limit, true);
780                 String b = getRangeBreakSource().getValue(limit, true);
781                 if (DEBUG && limit < 0x7F) System.out.println("*Label: " + label + ", Value: " + value + ", Break: " + breaker);
782                 if (!equalTo(s, label) || !equalTo(v, value) || !equalTo(b, breaker)) break;
783             }
784             // at this point, limit is the first item that has a different label than source
785             // OR, we got to the end, and limit == veryLimit
786             return true;
787         }
788     }
789
790     boolean equalTo(Object a, Object b) {
791         if (a == b) return true;
792         if (a == null) return false;
793         return a.equals(b);
794     }
795
796     boolean shortLabel = true;
797     boolean shortValue = true;
798
799     public String getPrefix() {
800         return prefix;
801     }
802
803     public String getSuffix() {
804         return suffix;
805     }
806
807     public BagFormatter setPrefix(String string) {
808         prefix = string;
809         return this;
810     }
811
812     public BagFormatter setSuffix(String string) {
813         suffix = string;
814         return this;
815     }
816
817     public boolean isAbbreviated() {
818         return abbreviated;
819     }
820
821     public BagFormatter setAbbreviated(boolean b) {
822         abbreviated = b;
823         return this;
824     }
825
826     public UnicodeLabel getLabelSource(boolean visible) {
827         if (labelSource == null) {
828             Map labelMap = new HashMap();
829             //labelMap.put("Lo","L&");
830             labelMap.put("Lu","L&");
831             labelMap.put("Lt","L&");
832             labelMap.put("Ll","L&");
833             labelSource = new UnicodeProperty.FilteredProperty(
834                 getUnicodePropertyFactory().getProperty("General_Category"),
835                 new UnicodeProperty.MapFilter(labelMap)
836             ).setAllowValueAliasCollisions(true);
837         }
838         return labelSource;
839     }
840
841     /**
842      * @deprecated
843      */
844     public static void addAll(UnicodeSet source, Collection target) {
845         source.addAllTo(target);
846     }
847
848     // UTILITIES
849
850     public static final Transliterator hex = Transliterator.getInstance(
851         "[^\\u0009\\u0020-\\u007E\\u00A0-\\u00FF] hex");
852
853     public static BufferedReader openUTF8Reader(String dir, String filename) throws IOException {
854         return openReader(dir,filename,"UTF-8");
855     }
856
857     public static BufferedReader openReader(String dir, String filename, String encoding) throws IOException {
858         File file = dir.length() == 0 ? new File(filename) : new File(dir, filename);
859         if (SHOW_FILES && log != null) {
860             log.println("Opening File: "
861                 + file.getCanonicalPath());
862         }
863         return new BufferedReader(
864             new InputStreamReader(
865                 new FileInputStream(file),
866                 encoding),
867             4*1024);
868     }
869
870     public static PrintWriter openUTF8Writer(String dir, String filename) throws IOException {
871         return openWriter(dir,filename,"UTF-8");
872     }
873
874     public static PrintWriter openWriter(String dir, String filename, String encoding) throws IOException {
875         File file = new File(dir, filename);
876         if (SHOW_FILES && log != null) {
877             log.println("Creating File: "
878                 + file.getCanonicalPath());
879         }
880         String parentName = file.getParent();
881         if (parentName != null) {
882             File parent = new File(parentName);
883             parent.mkdirs();
884         }
885         return new PrintWriter(
886             new BufferedWriter(
887                 new OutputStreamWriter(
888                     new FileOutputStream(file),
889                     encoding),
890                 4*1024));
891     }
892     public static PrintWriter getLog() {
893         return log;
894     }
895     public BagFormatter setLog(PrintWriter writer) {
896         log = writer;
897         return this;
898     }
899     public String getSeparator() {
900         return separator;
901     }
902     public BagFormatter setSeparator(String string) {
903         separator = string;
904         return this;
905     }
906     public Transliterator getShowLiteral() {
907         return showLiteral;
908     }
909     public BagFormatter setShowLiteral(Transliterator transliterator) {
910         showLiteral = transliterator;
911         return this;
912     }
913
914     // ===== CONVENIENCES =====
915     private class Join extends Visitor {
916         StringBuffer output = new StringBuffer();
917         @SuppressWarnings("unused")
918         int depth = 0;
919         String join (Object o) {
920             output.setLength(0);
921             doAt(o);
922             return output.toString();
923         }
924         protected void doBefore(Object container, Object item) {
925             ++depth;
926             output.append(prefix);
927         }
928         protected void doAfter(Object container, Object item) {
929             output.append(suffix);
930             --depth;
931         }
932         protected void doBetween(Object container, Object lastItem, Object nextItem) {
933             output.append(separator);
934         }
935         protected void doSimpleAt(Object o) {
936             if (o != null) output.append(o.toString());
937         }
938     }
939
940     /**
941      * @param label
942      */
943     public BagFormatter setLabelSource(UnicodeLabel label) {
944         if (label == null) label = UnicodeLabel.NULL;
945         labelSource = label;
946         return this;
947     }
948
949     /**
950      * @return the NameLable representing the source
951      */
952     public UnicodeLabel getNameSource() {
953         if (nameSource == null) {
954             nameSource = new NameLabel(getUnicodePropertyFactory());
955         }
956         return nameSource;
957     }
958
959     /**
960      * @param label
961      */
962     public BagFormatter setNameSource(UnicodeLabel label) {
963         if (label == null) label = UnicodeLabel.NULL;
964         nameSource = label;
965         return this;
966     }
967
968     /**
969      * @return the UnicodeLabel representing the value
970      */
971     public UnicodeLabel getValueSource() {
972         if (valueSource == null) valueSource = UnicodeLabel.NULL;
973         return valueSource;
974     }
975
976     private String getValue(int cp, boolean shortVal) {
977         String result = getValueSource().getValue(cp, shortVal);
978         if (result == null) return NULL_VALUE;
979         if (hexValue) result = hex(result, " ");
980         return result;
981     }
982
983     /**
984      * @param label
985      */
986     public BagFormatter setValueSource(UnicodeLabel label) {
987         if (label == null) label = UnicodeLabel.NULL;
988         valueSource = label;
989         return this;
990     }
991
992     public BagFormatter setValueSource(String label) {
993         return setValueSource(new UnicodeLabel.Constant(label));
994     }
995
996     /**
997      * @return true if showCount is true
998      */
999     public boolean isShowCount() {
1000         return showCount;
1001     }
1002
1003     /**
1004      * @param b true to show the count
1005      * @return this (for chaining)
1006      */
1007     public BagFormatter setShowCount(boolean b) {
1008         showCount = b;
1009         return this;
1010     }
1011
1012     /**
1013      * @return the property name
1014      */
1015     public String getPropName() {
1016         return propName;
1017     }
1018
1019     /**
1020      * @param string
1021      * @return this (for chaining)
1022      */
1023     public BagFormatter setPropName(String string) {
1024         if (string == null) string = "";
1025         propName = string;
1026         return this;
1027     }
1028
1029     /**
1030      * @return true if this is a hexValue
1031      */
1032     public boolean isHexValue() {
1033         return hexValue;
1034     }
1035
1036     /**
1037      * @param b
1038      * @return this (for chaining)
1039      */
1040     public BagFormatter setHexValue(boolean b) {
1041         hexValue = b;
1042         return this;
1043     }
1044
1045     /**
1046      * @return the full total
1047      */
1048     public int getFullTotal() {
1049         return fullTotal;
1050     }
1051
1052     /**
1053      * @param i set the full total
1054      * @return this (for chaining)
1055      */
1056     public BagFormatter setFullTotal(int i) {
1057         fullTotal = i;
1058         return this;
1059     }
1060
1061     /**
1062      * @return the line separator
1063      */
1064     public String getLineSeparator() {
1065         return lineSeparator;
1066     }
1067
1068     /**
1069      * @param string
1070      * @return this (for chaining)
1071      */
1072     public BagFormatter setLineSeparator(String string) {
1073         lineSeparator = string;
1074         return this;
1075     }
1076
1077     /**
1078      * @return the UnicodeLabel representing the range break source
1079      */
1080     public UnicodeLabel getRangeBreakSource() {
1081         if (rangeBreakSource == null) {
1082             Map labelMap = new HashMap();
1083             // reflects the code point types on p 25
1084             labelMap.put("Lo", "G&");
1085             labelMap.put("Lm", "G&");
1086             labelMap.put("Lu", "G&");
1087             labelMap.put("Lt", "G&");
1088             labelMap.put("Ll", "G&");
1089             labelMap.put("Mn", "G&");
1090             labelMap.put("Me", "G&");
1091             labelMap.put("Mc", "G&");
1092             labelMap.put("Nd", "G&");
1093             labelMap.put("Nl", "G&");
1094             labelMap.put("No", "G&");
1095             labelMap.put("Zs", "G&");
1096             labelMap.put("Pd", "G&");
1097             labelMap.put("Ps", "G&");
1098             labelMap.put("Pe", "G&");
1099             labelMap.put("Pc", "G&");
1100             labelMap.put("Po", "G&");
1101             labelMap.put("Pi", "G&");
1102             labelMap.put("Pf", "G&");
1103             labelMap.put("Sm", "G&");
1104             labelMap.put("Sc", "G&");
1105             labelMap.put("Sk", "G&");
1106             labelMap.put("So", "G&");
1107
1108             labelMap.put("Zl", "Cf");
1109             labelMap.put("Zp", "Cf");
1110
1111             rangeBreakSource =
1112                 new UnicodeProperty
1113                     .FilteredProperty(
1114                         getUnicodePropertyFactory().getProperty(
1115                             "General_Category"),
1116                         new UnicodeProperty.MapFilter(labelMap))
1117                     .setAllowValueAliasCollisions(true);
1118
1119             /*
1120             "Cn", // = Other, Not Assigned 0
1121             "Cc", // = Other, Control 15
1122             "Cf", // = Other, Format 16
1123             UnicodeProperty.UNUSED, // missing
1124             "Co", // = Other, Private Use 18
1125             "Cs", // = Other, Surrogate 19
1126             */
1127         }
1128         return rangeBreakSource;
1129     }
1130
1131     /**
1132      * @param label
1133      */
1134     public BagFormatter setRangeBreakSource(UnicodeLabel label) {
1135         if (label == null) label = UnicodeLabel.NULL;
1136         rangeBreakSource = label;
1137         return this;
1138     }
1139
1140     /**
1141      * @return Returns the fixName.
1142      */
1143     public Transliterator getFixName() {
1144         return fixName;
1145     }
1146     /**
1147      * @param fixName The fixName to set.
1148      */
1149     public BagFormatter setFixName(Transliterator fixName) {
1150         this.fixName = fixName;
1151         return this;
1152     }
1153
1154     public Tabber getTabber() {
1155         return tabber;
1156     }
1157
1158     public void setTabber(Tabber tabber) {
1159         this.tabber = tabber;
1160     }
1161
1162     public boolean isShowTotal() {
1163         return showTotal;
1164     }
1165
1166     public void setShowTotal(boolean showTotal) {
1167         this.showTotal = showTotal;
1168     }
1169 }