Source Code Cross Referenced for TransliteratorIDParser.java in  » Internationalization-Localization » icu4j » com » ibm » icu » text » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Internationalization Localization » icu4j » com.ibm.icu.text 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         **********************************************************************
003:         *   Copyright (c) 2002-2006, International Business Machines Corporation
004:         *   and others.  All Rights Reserved.
005:         **********************************************************************
006:         *   Date        Name        Description
007:         *   01/14/2002  aliu        Creation.
008:         **********************************************************************
009:         */
010:
011:        package com.ibm.icu.text;
012:
013:        import com.ibm.icu.util.CaseInsensitiveString;
014:        import com.ibm.icu.impl.Utility;
015:        import java.text.ParsePosition;
016:        import java.util.Hashtable;
017:        import java.util.Vector;
018:
019:        /**
020:         * Parsing component for transliterator IDs.  This class contains only
021:         * static members; it cannot be instantiated.  Methods in this class
022:         * parse various ID formats, including the following:
023:         *
024:         * A basic ID, which contains source, target, and variant, but no
025:         * filter and no explicit inverse.  Examples include
026:         * "Latin-Greek/UNGEGN" and "Null".
027:         *
028:         * A single ID, which is a basic ID plus optional filter and optional
029:         * explicit inverse.  Examples include "[a-zA-Z] Latin-Greek" and
030:         * "Lower (Upper)".
031:         *
032:         * A compound ID, which is a sequence of one or more single IDs,
033:         * separated by semicolons, with optional forward and reverse global
034:         * filters.  The global filters are UnicodeSet patterns prepended or
035:         * appended to the IDs, separated by semicolons.  An appended filter
036:         * must be enclosed in parentheses and applies in the reverse
037:         * direction.
038:         *
039:         * @author Alan Liu
040:         */
041:        class TransliteratorIDParser {
042:
043:            private static final char ID_DELIM = ';';
044:
045:            private static final char TARGET_SEP = '-';
046:
047:            private static final char VARIANT_SEP = '/';
048:
049:            private static final char OPEN_REV = '(';
050:
051:            private static final char CLOSE_REV = ')';
052:
053:            private static final String ANY = "Any";
054:
055:            private static final int FORWARD = Transliterator.FORWARD;
056:
057:            private static final int REVERSE = Transliterator.REVERSE;
058:
059:            private static final Hashtable SPECIAL_INVERSES = new Hashtable();
060:
061:            /**
062:             * A structure containing the parsed data of a filtered ID, that
063:             * is, a basic ID optionally with a filter.
064:             *
065:             * 'source' and 'target' will always be non-null.  The 'variant'
066:             * will be non-null only if a non-empty variant was parsed.
067:             *
068:             * 'sawSource' is true if there was an explicit source in the
069:             * parsed id.  If there was no explicit source, then an implied
070:             * source of ANY is returned and 'sawSource' is set to false.
071:             * 
072:             * 'filter' is the parsed filter pattern, or null if there was no
073:             * filter.
074:             */
075:            private static class Specs {
076:                public String source; // not null
077:                public String target; // not null
078:                public String variant; // may be null
079:                public String filter; // may be null
080:                public boolean sawSource;
081:
082:                Specs(String s, String t, String v, boolean sawS, String f) {
083:                    source = s;
084:                    target = t;
085:                    variant = v;
086:                    sawSource = sawS;
087:                    filter = f;
088:                }
089:            }
090:
091:            /**
092:             * A structure containing the canonicalized data of a filtered ID,
093:             * that is, a basic ID optionally with a filter.
094:             *
095:             * 'canonID' is always non-null.  It may be the empty string "".
096:             * It is the id that should be assigned to the created
097:             * transliterator.  It _cannot_ be instantiated directly.
098:             *
099:             * 'basicID' is always non-null and non-empty.  It is always of
100:             * the form S-T or S-T/V.  It is designed to be fed to low-level
101:             * instantiation code that only understands these two formats.
102:             *
103:             * 'filter' may be null, if there is none, or non-null and
104:             * non-empty.
105:             */
106:            static class SingleID {
107:                public String canonID;
108:                public String basicID;
109:                public String filter;
110:
111:                SingleID(String c, String b, String f) {
112:                    canonID = c;
113:                    basicID = b;
114:                    filter = f;
115:                }
116:
117:                SingleID(String c, String b) {
118:                    this (c, b, null);
119:                }
120:
121:                Transliterator getInstance() {
122:                    Transliterator t;
123:                    if (basicID == null || basicID.length() == 0) {
124:                        t = Transliterator
125:                                .getBasicInstance("Any-Null", canonID);
126:                    } else {
127:                        t = Transliterator.getBasicInstance(basicID, canonID);
128:                    }
129:                    if (t != null) {
130:                        if (filter != null) {
131:                            t.setFilter(new UnicodeSet(filter));
132:                        }
133:                    }
134:                    return t;
135:                }
136:            }
137:
138:            /**
139:             * Parse a filter ID, that is, an ID of the general form
140:             * "[f1] s1-t1/v1", with the filters optional, and the variants optional.
141:             * @param id the id to be parsed
142:             * @param pos INPUT-OUTPUT parameter.  On input, the position of
143:             * the first character to parse.  On output, the position after
144:             * the last character parsed.
145:             * @return a SingleID object or null if the parse fails
146:             */
147:            public static SingleID parseFilterID(String id, int[] pos) {
148:
149:                int start = pos[0];
150:                Specs specs = parseFilterID(id, pos, true);
151:                if (specs == null) {
152:                    pos[0] = start;
153:                    return null;
154:                }
155:
156:                // Assemble return results
157:                SingleID single = specsToID(specs, FORWARD);
158:                single.filter = specs.filter;
159:                return single;
160:            }
161:
162:            /**
163:             * Parse a single ID, that is, an ID of the general form
164:             * "[f1] s1-t1/v1 ([f2] s2-t3/v2)", with the parenthesized element
165:             * optional, the filters optional, and the variants optional.
166:             * @param id the id to be parsed
167:             * @param pos INPUT-OUTPUT parameter.  On input, the position of
168:             * the first character to parse.  On output, the position after
169:             * the last character parsed.
170:             * @param dir the direction.  If the direction is REVERSE then the
171:             * SingleID is constructed for the reverse direction.
172:             * @return a SingleID object or null
173:             */
174:            public static SingleID parseSingleID(String id, int[] pos, int dir) {
175:
176:                int start = pos[0];
177:
178:                // The ID will be of the form A, A(), A(B), or (B), where
179:                // A and B are filter IDs.
180:                Specs specsA = null;
181:                Specs specsB = null;
182:                boolean sawParen = false;
183:
184:                // On the first pass, look for (B) or ().  If this fails, then
185:                // on the second pass, look for A, A(B), or A().
186:                for (int pass = 1; pass <= 2; ++pass) {
187:                    if (pass == 2) {
188:                        specsA = parseFilterID(id, pos, true);
189:                        if (specsA == null) {
190:                            pos[0] = start;
191:                            return null;
192:                        }
193:                    }
194:                    if (Utility.parseChar(id, pos, OPEN_REV)) {
195:                        sawParen = true;
196:                        if (!Utility.parseChar(id, pos, CLOSE_REV)) {
197:                            specsB = parseFilterID(id, pos, true);
198:                            // Must close with a ')'
199:                            if (specsB == null
200:                                    || !Utility.parseChar(id, pos, CLOSE_REV)) {
201:                                pos[0] = start;
202:                                return null;
203:                            }
204:                        }
205:                        break;
206:                    }
207:                }
208:
209:                // Assemble return results
210:                SingleID single;
211:                if (sawParen) {
212:                    if (dir == FORWARD) {
213:                        single = specsToID(specsA, FORWARD);
214:                        single.canonID = single.canonID + OPEN_REV
215:                                + specsToID(specsB, FORWARD).canonID
216:                                + CLOSE_REV;
217:                        if (specsA != null) {
218:                            single.filter = specsA.filter;
219:                        }
220:                    } else {
221:                        single = specsToID(specsB, FORWARD);
222:                        single.canonID = single.canonID + OPEN_REV
223:                                + specsToID(specsA, FORWARD).canonID
224:                                + CLOSE_REV;
225:                        if (specsB != null) {
226:                            single.filter = specsB.filter;
227:                        }
228:                    }
229:                } else {
230:                    // assert(specsA != null);
231:                    if (dir == FORWARD) {
232:                        single = specsToID(specsA, FORWARD);
233:                    } else {
234:                        single = specsToSpecialInverse(specsA);
235:                        if (single == null) {
236:                            single = specsToID(specsA, REVERSE);
237:                        }
238:                    }
239:                    single.filter = specsA.filter;
240:                }
241:
242:                return single;
243:            }
244:
245:            /**
246:             * Parse a global filter of the form "[f]" or "([f])", depending
247:             * on 'withParens'.
248:             * @param id the pattern the parse
249:             * @param pos INPUT-OUTPUT parameter.  On input, the position of
250:             * the first character to parse.  On output, the position after
251:             * the last character parsed.
252:             * @param dir the direction.
253:             * @param withParens INPUT-OUTPUT parameter.  On entry, if
254:             * withParens[0] is 0, then parens are disallowed.  If it is 1,
255:             * then parens are requires.  If it is -1, then parens are
256:             * optional, and the return result will be set to 0 or 1.
257:             * @param canonID OUTPUT parameter.  The pattern for the filter
258:             * added to the canonID, either at the end, if dir is FORWARD, or
259:             * at the start, if dir is REVERSE.  The pattern will be enclosed
260:             * in parentheses if appropriate, and will be suffixed with an
261:             * ID_DELIM character.  May be null.
262:             * @return a UnicodeSet object or null.  A non-null results
263:             * indicates a successful parse, regardless of whether the filter
264:             * applies to the given direction.  The caller should discard it
265:             * if withParens != (dir == REVERSE).
266:             */
267:            public static UnicodeSet parseGlobalFilter(String id, int[] pos,
268:                    int dir, int[] withParens, StringBuffer canonID) {
269:                UnicodeSet filter = null;
270:                int start = pos[0];
271:
272:                if (withParens[0] == -1) {
273:                    withParens[0] = Utility.parseChar(id, pos, OPEN_REV) ? 1
274:                            : 0;
275:                } else if (withParens[0] == 1) {
276:                    if (!Utility.parseChar(id, pos, OPEN_REV)) {
277:                        pos[0] = start;
278:                        return null;
279:                    }
280:                }
281:
282:                Utility.skipWhitespace(id, pos);
283:
284:                if (UnicodeSet.resemblesPattern(id, pos[0])) {
285:                    ParsePosition ppos = new ParsePosition(pos[0]);
286:                    try {
287:                        filter = new UnicodeSet(id, ppos, null);
288:                    } catch (IllegalArgumentException e) {
289:                        pos[0] = start;
290:                        return null;
291:                    }
292:
293:                    String pattern = id.substring(pos[0], ppos.getIndex());
294:                    pos[0] = ppos.getIndex();
295:
296:                    if (withParens[0] == 1
297:                            && !Utility.parseChar(id, pos, CLOSE_REV)) {
298:                        pos[0] = start;
299:                        return null;
300:                    }
301:
302:                    // In the forward direction, append the pattern to the
303:                    // canonID.  In the reverse, insert it at zero, and invert
304:                    // the presence of parens ("A" <-> "(A)").
305:                    if (canonID != null) {
306:                        if (dir == FORWARD) {
307:                            if (withParens[0] == 1) {
308:                                pattern = String.valueOf(OPEN_REV) + pattern
309:                                        + CLOSE_REV;
310:                            }
311:                            canonID.append(pattern + ID_DELIM);
312:                        } else {
313:                            if (withParens[0] == 0) {
314:                                pattern = String.valueOf(OPEN_REV) + pattern
315:                                        + CLOSE_REV;
316:                            }
317:                            canonID.insert(0, pattern + ID_DELIM);
318:                        }
319:                    }
320:                }
321:
322:                return filter;
323:            }
324:
325:            /**
326:             * Parse a compound ID, consisting of an optional forward global
327:             * filter, a separator, one or more single IDs delimited by
328:             * separators, an an optional reverse global filter.  The
329:             * separator is a semicolon.  The global filters are UnicodeSet
330:             * patterns.  The reverse global filter must be enclosed in
331:             * parentheses.
332:             * @param id the pattern the parse
333:             * @param dir the direction.
334:             * @param canonID OUTPUT parameter that receives the canonical ID,
335:             * consisting of canonical IDs for all elements, as returned by
336:             * parseSingleID(), separated by semicolons.  Previous contents
337:             * are discarded.
338:             * @param list OUTPUT parameter that receives a list of SingleID
339:             * objects representing the parsed IDs.  Previous contents are
340:             * discarded.
341:             * @param globalFilter OUTPUT parameter that receives a pointer to
342:             * a newly created global filter for this ID in this direction, or
343:             * null if there is none.
344:             * @return true if the parse succeeds, that is, if the entire
345:             * id is consumed without syntax error.
346:             */
347:            public static boolean parseCompoundID(String id, int dir,
348:                    StringBuffer canonID, Vector list, UnicodeSet[] globalFilter) {
349:                int[] pos = new int[] { 0 };
350:                int[] withParens = new int[1];
351:                list.removeAllElements();
352:                UnicodeSet filter;
353:                globalFilter[0] = null;
354:                canonID.setLength(0);
355:
356:                // Parse leading global filter, if any
357:                withParens[0] = 0; // parens disallowed
358:                filter = parseGlobalFilter(id, pos, dir, withParens, canonID);
359:                if (filter != null) {
360:                    if (!Utility.parseChar(id, pos, ID_DELIM)) {
361:                        // Not a global filter; backup and resume
362:                        canonID.setLength(0);
363:                        pos[0] = 0;
364:                    }
365:                    if (dir == FORWARD) {
366:                        globalFilter[0] = filter;
367:                    }
368:                }
369:
370:                boolean sawDelimiter = true;
371:                for (;;) {
372:                    SingleID single = parseSingleID(id, pos, dir);
373:                    if (single == null) {
374:                        break;
375:                    }
376:                    if (dir == FORWARD) {
377:                        list.addElement(single);
378:                    } else {
379:                        list.insertElementAt(single, 0);
380:                    }
381:                    if (!Utility.parseChar(id, pos, ID_DELIM)) {
382:                        sawDelimiter = false;
383:                        break;
384:                    }
385:                }
386:
387:                if (list.size() == 0) {
388:                    return false;
389:                }
390:
391:                // Construct canonical ID
392:                for (int i = 0; i < list.size(); ++i) {
393:                    SingleID single = (SingleID) list.elementAt(i);
394:                    canonID.append(single.canonID);
395:                    if (i != (list.size() - 1)) {
396:                        canonID.append(ID_DELIM);
397:                    }
398:                }
399:
400:                // Parse trailing global filter, if any, and only if we saw
401:                // a trailing delimiter after the IDs.
402:                if (sawDelimiter) {
403:                    withParens[0] = 1; // parens required
404:                    filter = parseGlobalFilter(id, pos, dir, withParens,
405:                            canonID);
406:                    if (filter != null) {
407:                        // Don't require trailing ';', but parse it if present
408:                        Utility.parseChar(id, pos, ID_DELIM);
409:
410:                        if (dir == REVERSE) {
411:                            globalFilter[0] = filter;
412:                        }
413:                    }
414:                }
415:
416:                // Trailing unparsed text is a syntax error
417:                Utility.skipWhitespace(id, pos[0]);
418:                if (pos[0] != id.length()) {
419:                    return false;
420:                }
421:
422:                return true;
423:            }
424:
425:            /**
426:             * Convert the elements of the 'list' vector, which are SingleID
427:             * objects, into actual Transliterator objects.  In the course of
428:             * this, some (or all) entries may be removed.  If all entries
429:             * are removed, the Null transliterator will be added.
430:             *
431:             * Delete entries with empty basicIDs; these are generated by
432:             * elements like "(A)" in the forward direction, or "A()" in
433:             * the reverse.  THIS MAY RESULT IN AN EMPTY VECTOR.  Convert
434:             * SingleID entries to actual transliterators.
435:             *
436:             * @param list vector of SingleID objects.  On exit, vector
437:             * of one or more Transliterators.
438:             */
439:            public static void instantiateList(Vector list) {
440:                Transliterator t;
441:                for (int i = 0; i <= list.size();) { // [sic]: i<=list.size()
442:                    // We run the loop too long by one, so we can
443:                    // do an insert after the last element
444:                    if (i == list.size()) {
445:                        break;
446:                    }
447:
448:                    SingleID single = (SingleID) list.elementAt(i);
449:                    if (single.basicID.length() == 0) {
450:                        list.removeElementAt(i);
451:                    } else {
452:                        t = single.getInstance();
453:                        if (t == null) {
454:                            t = single.getInstance();
455:                            throw new IllegalArgumentException("Illegal ID "
456:                                    + single.canonID);
457:                        }
458:                        list.setElementAt(t, i);
459:                        ++i;
460:                    }
461:                }
462:
463:                // An empty list is equivalent to a Null transliterator.
464:                if (list.size() == 0) {
465:                    t = Transliterator.getBasicInstance("Any-Null", null);
466:                    if (t == null) {
467:                        // Should never happen
468:                        throw new IllegalArgumentException(
469:                                "Internal error; cannot instantiate Any-Null");
470:                    }
471:                    list.addElement(t);
472:                }
473:            }
474:
475:            /**
476:             * Parse an ID into pieces.  Take IDs of the form T, T/V, S-T,
477:             * S-T/V, or S/V-T.  If the source is missing, return a source of
478:             * ANY.
479:             * @param id the id string, in any of several forms
480:             * @return an array of 4 strings: source, target, variant, and
481:             * isSourcePresent.  If the source is not present, ANY will be
482:             * given as the source, and isSourcePresent will be null.  Otherwise
483:             * isSourcePresent will be non-null.  The target may be empty if the
484:             * id is not well-formed.  The variant may be empty.
485:             */
486:            public static String[] IDtoSTV(String id) {
487:                String source = ANY;
488:                String target = null;
489:                String variant = "";
490:
491:                int sep = id.indexOf(TARGET_SEP);
492:                int var = id.indexOf(VARIANT_SEP);
493:                if (var < 0) {
494:                    var = id.length();
495:                }
496:                boolean isSourcePresent = false;
497:
498:                if (sep < 0) {
499:                    // Form: T/V or T (or /V)
500:                    target = id.substring(0, var);
501:                    variant = id.substring(var);
502:                } else if (sep < var) {
503:                    // Form: S-T/V or S-T (or -T/V or -T)
504:                    if (sep > 0) {
505:                        source = id.substring(0, sep);
506:                        isSourcePresent = true;
507:                    }
508:                    target = id.substring(++sep, var);
509:                    variant = id.substring(var);
510:                } else {
511:                    // Form: (S/V-T or /V-T)
512:                    if (var > 0) {
513:                        source = id.substring(0, var);
514:                        isSourcePresent = true;
515:                    }
516:                    variant = id.substring(var, sep++);
517:                    target = id.substring(sep);
518:                }
519:
520:                if (variant.length() > 0) {
521:                    variant = variant.substring(1);
522:                }
523:
524:                return new String[] { source, target, variant,
525:                        isSourcePresent ? "" : null };
526:            }
527:
528:            /**
529:             * Given source, target, and variant strings, concatenate them into a
530:             * full ID.  If the source is empty, then "Any" will be used for the
531:             * source, so the ID will always be of the form s-t/v or s-t.
532:             */
533:            public static String STVtoID(String source, String target,
534:                    String variant) {
535:                StringBuffer id = new StringBuffer(source);
536:                if (id.length() == 0) {
537:                    id.append(ANY);
538:                }
539:                id.append(TARGET_SEP).append(target);
540:                if (variant != null && variant.length() != 0) {
541:                    id.append(VARIANT_SEP).append(variant);
542:                }
543:                return id.toString();
544:            }
545:
546:            /**
547:             * Register two targets as being inverses of one another.  For
548:             * example, calling registerSpecialInverse("NFC", "NFD", true) causes
549:             * Transliterator to form the following inverse relationships:
550:             *
551:             * <pre>NFC => NFD
552:             * Any-NFC => Any-NFD
553:             * NFD => NFC
554:             * Any-NFD => Any-NFC</pre>
555:             *
556:             * (Without the special inverse registration, the inverse of NFC
557:             * would be NFC-Any.)  Note that NFD is shorthand for Any-NFD, but
558:             * that the presence or absence of "Any-" is preserved.
559:             *
560:             * <p>The relationship is symmetrical; registering (a, b) is
561:             * equivalent to registering (b, a).
562:             *
563:             * <p>The relevant IDs must still be registered separately as
564:             * factories or classes.
565:             *
566:             * <p>Only the targets are specified.  Special inverses always
567:             * have the form Any-Target1 <=> Any-Target2.  The target should
568:             * have canonical casing (the casing desired to be produced when
569:             * an inverse is formed) and should contain no whitespace or other
570:             * extraneous characters.
571:             *
572:             * @param target the target against which to register the inverse
573:             * @param inverseTarget the inverse of target, that is
574:             * Any-target.getInverse() => Any-inverseTarget
575:             * @param bidirectional if true, register the reverse relation
576:             * as well, that is, Any-inverseTarget.getInverse() => Any-target
577:             */
578:            public static void registerSpecialInverse(String target,
579:                    String inverseTarget, boolean bidirectional) {
580:                SPECIAL_INVERSES.put(new CaseInsensitiveString(target),
581:                        inverseTarget);
582:                if (bidirectional && !target.equalsIgnoreCase(inverseTarget)) {
583:                    SPECIAL_INVERSES.put(new CaseInsensitiveString(
584:                            inverseTarget), target);
585:                }
586:            }
587:
588:            //----------------------------------------------------------------
589:            // Private implementation
590:            //----------------------------------------------------------------
591:
592:            /**
593:             * Parse an ID into component pieces.  Take IDs of the form T,
594:             * T/V, S-T, S-T/V, or S/V-T.  If the source is missing, return a
595:             * source of ANY.
596:             * @param id the id string, in any of several forms
597:             * @param pos INPUT-OUTPUT parameter.  On input, pos[0] is the
598:             * offset of the first character to parse in id.  On output,
599:             * pos[0] is the offset after the last parsed character.  If the
600:             * parse failed, pos[0] will be unchanged.
601:             * @param allowFilter if true, a UnicodeSet pattern is allowed
602:             * at any location between specs or delimiters, and is returned
603:             * as the fifth string in the array.
604:             * @return a Specs object, or null if the parse failed.  If
605:             * neither source nor target was seen in the parsed id, then the
606:             * parse fails.  If allowFilter is true, then the parsed filter
607:             * pattern is returned in the Specs object, otherwise the returned
608:             * filter reference is null.  If the parse fails for any reason
609:             * null is returned.
610:             */
611:            private static Specs parseFilterID(String id, int[] pos,
612:                    boolean allowFilter) {
613:                String first = null;
614:                String source = null;
615:                String target = null;
616:                String variant = null;
617:                String filter = null;
618:                char delimiter = 0;
619:                int specCount = 0;
620:                int start = pos[0];
621:
622:                // This loop parses one of the following things with each
623:                // pass: a filter, a delimiter character (either '-' or '/'),
624:                // or a spec (source, target, or variant).
625:                for (;;) {
626:                    Utility.skipWhitespace(id, pos);
627:                    if (pos[0] == id.length()) {
628:                        break;
629:                    }
630:
631:                    // Parse filters
632:                    if (allowFilter && filter == null
633:                            && UnicodeSet.resemblesPattern(id, pos[0])) {
634:
635:                        ParsePosition ppos = new ParsePosition(pos[0]);
636:                        UnicodeSet set = new UnicodeSet(id, ppos, null);
637:                        filter = id.substring(pos[0], ppos.getIndex());
638:                        pos[0] = ppos.getIndex();
639:                        continue;
640:                    }
641:
642:                    if (delimiter == 0) {
643:                        char c = id.charAt(pos[0]);
644:                        if ((c == TARGET_SEP && target == null)
645:                                || (c == VARIANT_SEP && variant == null)) {
646:                            delimiter = c;
647:                            ++pos[0];
648:                            continue;
649:                        }
650:                    }
651:
652:                    // We are about to try to parse a spec with no delimiter
653:                    // when we can no longer do so (we can only do so at the
654:                    // start); break.
655:                    if (delimiter == 0 && specCount > 0) {
656:                        break;
657:                    }
658:
659:                    String spec = Utility.parseUnicodeIdentifier(id, pos);
660:                    if (spec == null) {
661:                        // Note that if there was a trailing delimiter, we
662:                        // consume it.  So Foo-, Foo/, Foo-Bar/, and Foo/Bar-
663:                        // are legal.
664:                        break;
665:                    }
666:
667:                    switch (delimiter) {
668:                    case 0:
669:                        first = spec;
670:                        break;
671:                    case TARGET_SEP:
672:                        target = spec;
673:                        break;
674:                    case VARIANT_SEP:
675:                        variant = spec;
676:                        break;
677:                    }
678:                    ++specCount;
679:                    delimiter = 0;
680:                }
681:
682:                // A spec with no prior character is either source or target,
683:                // depending on whether an explicit "-target" was seen.
684:                if (first != null) {
685:                    if (target == null) {
686:                        target = first;
687:                    } else {
688:                        source = first;
689:                    }
690:                }
691:
692:                // Must have either source or target
693:                if (source == null && target == null) {
694:                    pos[0] = start;
695:                    return null;
696:                }
697:
698:                // Empty source or target defaults to ANY
699:                boolean sawSource = true;
700:                if (source == null) {
701:                    source = ANY;
702:                    sawSource = false;
703:                }
704:                if (target == null) {
705:                    target = ANY;
706:                }
707:
708:                return new Specs(source, target, variant, sawSource, filter);
709:            }
710:
711:            /**
712:             * Givens a Spec object, convert it to a SingleID object.  The
713:             * Spec object is a more unprocessed parse result.  The SingleID
714:             * object contains information about canonical and basic IDs.
715:             * @return a SingleID; never returns null.  Returned object always
716:             * has 'filter' field of null.
717:             */
718:            private static SingleID specsToID(Specs specs, int dir) {
719:                String canonID = "";
720:                String basicID = "";
721:                String basicPrefix = "";
722:                if (specs != null) {
723:                    StringBuffer buf = new StringBuffer();
724:                    if (dir == FORWARD) {
725:                        if (specs.sawSource) {
726:                            buf.append(specs.source).append(TARGET_SEP);
727:                        } else {
728:                            basicPrefix = specs.source + TARGET_SEP;
729:                        }
730:                        buf.append(specs.target);
731:                    } else {
732:                        buf.append(specs.target).append(TARGET_SEP).append(
733:                                specs.source);
734:                    }
735:                    if (specs.variant != null) {
736:                        buf.append(VARIANT_SEP).append(specs.variant);
737:                    }
738:                    basicID = basicPrefix + buf.toString();
739:                    if (specs.filter != null) {
740:                        buf.insert(0, specs.filter);
741:                    }
742:                    canonID = buf.toString();
743:                }
744:                return new SingleID(canonID, basicID);
745:            }
746:
747:            /**
748:             * Given a Specs object, return a SingleID representing the
749:             * special inverse of that ID.  If there is no special inverse
750:             * then return null.
751:             * @return a SingleID or null.  Returned object always has
752:             * 'filter' field of null.
753:             */
754:            private static SingleID specsToSpecialInverse(Specs specs) {
755:                if (!specs.source.equalsIgnoreCase(ANY)) {
756:                    return null;
757:                }
758:                String inverseTarget = (String) SPECIAL_INVERSES
759:                        .get(new CaseInsensitiveString(specs.target));
760:                if (inverseTarget != null) {
761:                    // If the original ID contained "Any-" then make the
762:                    // special inverse "Any-Foo"; otherwise make it "Foo".
763:                    // So "Any-NFC" => "Any-NFD" but "NFC" => "NFD".
764:                    StringBuffer buf = new StringBuffer();
765:                    if (specs.filter != null) {
766:                        buf.append(specs.filter);
767:                    }
768:                    if (specs.sawSource) {
769:                        buf.append(ANY).append(TARGET_SEP);
770:                    }
771:                    buf.append(inverseTarget);
772:
773:                    String basicID = ANY + TARGET_SEP + inverseTarget;
774:
775:                    if (specs.variant != null) {
776:                        buf.append(VARIANT_SEP).append(specs.variant);
777:                        basicID = basicID + VARIANT_SEP + specs.variant;
778:                    }
779:                    return new SingleID(buf.toString(), basicID);
780:                }
781:                return null;
782:            }
783:        }
784:
785:        //eof
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.