0001: //##header
0002: //#ifndef FOUNDATION
0003: /*
0004: *******************************************************************************
0005: * Copyright (C) 2006, Google, International Business Machines Corporation and *
0006: * others. All Rights Reserved. *
0007: *******************************************************************************
0008: *
0009: * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/DateTimePatternGenerator.java,v $
0010: * $Date: 2006/09/15 18:09:24 $
0011: * $Revision: 1.9 $
0012: *
0013: *******************************************************************************
0014: */
0015: package com.ibm.icu.text;
0016:
0017: import java.util.ArrayList;
0018: import java.util.Arrays;
0019: import java.util.BitSet;
0020: import java.util.Collection;
0021: import java.util.Collections;
0022: import java.util.Enumeration;
0023: import java.util.HashMap;
0024: import java.util.HashSet;
0025: import java.util.Iterator;
0026: import java.util.LinkedHashMap;
0027: import java.util.LinkedHashSet;
0028: import java.util.List;
0029: import java.util.Map;
0030: import java.util.Set;
0031: import java.util.TreeMap;
0032: import java.util.TreeSet;
0033:
0034: //import org.unicode.cldr.util.Utility;
0035:
0036: import com.ibm.icu.impl.CalendarData;
0037: import com.ibm.icu.impl.PatternTokenizer;
0038: import com.ibm.icu.impl.Utility;
0039: import com.ibm.icu.text.MessageFormat;
0040: import com.ibm.icu.text.Transliterator;
0041: import com.ibm.icu.text.UnicodeSet;
0042: import com.ibm.icu.util.Calendar;
0043: import com.ibm.icu.util.Freezable;
0044: import com.ibm.icu.util.ULocale;
0045: import com.ibm.icu.util.UResourceBundle;
0046:
0047: /**
0048: * This class provides flexible generation of date format patterns, like "yy-MM-dd". The user can build up the generator
0049: * by adding successive patterns. Once that is done, a query can be made using a "skeleton", which is a pattern which just
0050: * includes the desired fields and lengths. The generator will return the "best fit" pattern corresponding to that skeleton.
0051: * <p>The main method people will use is getBestPattern(String skeleton),
0052: * since normally this class is pre-built with data from a particular locale. However, generators can be built directly from other data as well.
0053: * <p><i>Issue: may be useful to also have a function that returns the list of fields in a pattern, in order, since we have that internally.
0054: * That would be useful for getting the UI order of field elements.</i>
0055: * @draft ICU 3.6
0056: * @provisional This API might change or be removed in a future release.
0057: */
0058: public class DateTimePatternGenerator implements Freezable, Cloneable {
0059: // debugging flags
0060: //static boolean SHOW_DISTANCE = false;
0061: // TODO add hack to fix months for CJK, as per bug 1099
0062: // http://dev.icu-project.org/cgi-bin/locale-bugs/incoming?findid=1099
0063:
0064: /**
0065: * Create empty generator, to be constructed with add(...) etc.
0066: * @draft ICU 3.6
0067: * @provisional This API might change or be removed in a future release.
0068: */
0069: public static DateTimePatternGenerator newInstance() {
0070: return new DateTimePatternGenerator();
0071: }
0072:
0073: /**
0074: * Only for use by subclasses
0075: * @draft ICU 3.6
0076: * @provisional This API might change or be removed in a future release.
0077: */
0078: protected DateTimePatternGenerator() {
0079: }
0080:
0081: /**
0082: * Construct a flexible generator according to data for a given locale.
0083: * @draft ICU 3.6
0084: * @provisional This API might change or be removed in a future release.
0085: */
0086: public static DateTimePatternGenerator getInstance() {
0087: return getInstance(ULocale.getDefault());
0088: }
0089:
0090: /**
0091: * Construct a flexible generator according to data for a given locale.
0092: * @param uLocale
0093: * @draft ICU 3.6
0094: * @provisional This API might change or be removed in a future release.
0095: */
0096: public static DateTimePatternGenerator getInstance(ULocale uLocale) {
0097: DateTimePatternGenerator result = new DateTimePatternGenerator();
0098: PatternInfo returnInfo = new PatternInfo();
0099: String hackPattern = null;
0100: // first load with the ICU patterns
0101: for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
0102: SimpleDateFormat df = (SimpleDateFormat) DateFormat
0103: .getDateInstance(i, uLocale);
0104: result.add(df.toPattern(), false, returnInfo);
0105: df = (SimpleDateFormat) DateFormat.getTimeInstance(i,
0106: uLocale);
0107: result.add(df.toPattern(), false, returnInfo);
0108: // HACK for hh:ss
0109: if (i == DateFormat.MEDIUM) {
0110: hackPattern = df.toPattern();
0111: }
0112: }
0113: UResourceBundle rb = UResourceBundle.getBundleInstance(
0114: "com.ibm.icu.impl.data.DateData$MyDateResources",
0115: uLocale);
0116: //ResourceBundle rb = ResourceBundle.getBundle("com.ibm.icu.impl.data.DateData$MyDateResources", ULocale.FRENCH.toLocale());
0117: for (Enumeration en = rb.getKeys(); en.hasMoreElements();) {
0118: String key = (String) en.nextElement();
0119: String value = rb.getString(key);
0120: String[] keyParts = key.split("/");
0121: if (keyParts[0].equals("pattern")) {
0122: result.add(value, false, returnInfo);
0123: } else if (keyParts[0].equals("append")) {
0124: result.setAppendItemFormats(
0125: getAppendFormatNumber(keyParts[1]), value);
0126: } else if (keyParts[0].equals("field")) {
0127: result.setAppendItemNames(
0128: getAppendNameNumber(keyParts[1]), value);
0129: }
0130: }
0131:
0132: // assume it is always big endian (ok for CLDR right now)
0133: // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
0134: if (hackPattern != null) {
0135: hackTimes(result, returnInfo, hackPattern);
0136: }
0137:
0138: // set the datetime pattern. This is ugly code -- there should be a public interface for this
0139: Calendar cal = Calendar.getInstance(uLocale);
0140: CalendarData calData = new CalendarData(uLocale, cal.getType());
0141: String[] patterns = calData.get("DateTimePatterns")
0142: .getStringArray();
0143: result.setDateTimeFormat(patterns[8]);
0144:
0145: // decimal point for seconds
0146: DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
0147: result.setDecimal(String.valueOf(dfs.getDecimalSeparator()));
0148: return result;
0149: }
0150:
0151: private static void hackTimes(DateTimePatternGenerator result,
0152: PatternInfo returnInfo, String hackPattern) {
0153: result.fp.set(hackPattern);
0154: String mmss = new String();
0155: // to get mm:ss, we strip all but mm literal ss
0156: boolean gotMm = false;
0157: for (int i = 0; i < result.fp.items.size(); ++i) {
0158: Object item = result.fp.items.get(i);
0159: if (item instanceof String) {
0160: if (gotMm) {
0161: mmss += result.fp.quoteLiteral(item.toString());
0162: }
0163: } else {
0164: char ch = item.toString().charAt(0);
0165: if (ch == 'm') {
0166: gotMm = true;
0167: mmss += item;
0168: } else if (ch == 's') {
0169: if (!gotMm) {
0170: break; // failed
0171: }
0172: mmss += item;
0173: result.add(mmss, false, returnInfo);
0174: break;
0175: } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v'
0176: || ch == 'V') {
0177: break; // failed
0178: }
0179: }
0180: }
0181: // to get hh:mm, we strip (literal ss) and (literal S)
0182: // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
0183: BitSet variables = new BitSet();
0184: BitSet nuke = new BitSet();
0185: for (int i = 0; i < result.fp.items.size(); ++i) {
0186: Object item = result.fp.items.get(i);
0187: if (item instanceof VariableField) {
0188: variables.set(i);
0189: char ch = item.toString().charAt(0);
0190: if (ch == 's' || ch == 'S') {
0191: nuke.set(i);
0192: for (int j = i - 1; j >= 0; ++j) {
0193: if (variables.get(j))
0194: break;
0195: nuke.set(i);
0196: }
0197: }
0198: }
0199: }
0200: String hhmm = getFilteredPattern(result.fp, nuke);
0201: result.add(hhmm, false, returnInfo);
0202: }
0203:
0204: private static String getFilteredPattern(FormatParser fp,
0205: BitSet nuke) {
0206: String result = new String();
0207: for (int i = 0; i < fp.items.size(); ++i) {
0208: if (nuke.get(i))
0209: continue;
0210: Object item = fp.items.get(i);
0211: if (item instanceof String) {
0212: result += fp.quoteLiteral(item.toString());
0213: } else {
0214: result += item.toString();
0215: }
0216: }
0217: return result;
0218: }
0219:
0220: private static int getAppendNameNumber(String string) {
0221: for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
0222: if (CLDR_FIELD_NAME[i].equals(string))
0223: return i;
0224: }
0225: return -1;
0226: }
0227:
0228: private static int getAppendFormatNumber(String string) {
0229: for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
0230: if (CLDR_FIELD_APPEND[i].equals(string))
0231: return i;
0232: }
0233: return -1;
0234:
0235: }
0236:
0237: /**
0238: * Return the best pattern matching the input skeleton. It is guaranteed to
0239: * have all of the fields in the skeleton.
0240: *
0241: * @param skeleton
0242: * The skeleton is a pattern containing only the variable fields.
0243: * For example, "MMMdd" and "mmhh" are skeletons.
0244: * @draft ICU 3.6
0245: * @provisional This API might change or be removed in a future release.
0246: */
0247: public String getBestPattern(String skeleton) {
0248: //if (!isComplete) complete();
0249: current.set(skeleton, fp);
0250: String best = getBestRaw(current, -1, _distanceInfo);
0251: if (_distanceInfo.missingFieldMask == 0
0252: && _distanceInfo.extraFieldMask == 0) {
0253: // we have a good item. Adjust the field types
0254: return adjustFieldTypes(best, current, false);
0255: }
0256: int neededFields = current.getFieldMask();
0257: // otherwise break up by date and time.
0258: String datePattern = getBestAppending(neededFields & DATE_MASK);
0259: String timePattern = getBestAppending(neededFields & TIME_MASK);
0260:
0261: if (datePattern == null)
0262: return timePattern == null ? "" : timePattern;
0263: if (timePattern == null)
0264: return datePattern;
0265: return MessageFormat.format(getDateTimeFormat(), new Object[] {
0266: datePattern, timePattern });
0267: }
0268:
0269: /**
0270: * PatternInfo supplies output parameters for add(...). It is used because
0271: * Java doesn't have real output parameters. It is treated like a struct (eg
0272: * Point), so all fields are public.
0273: *
0274: * @draft ICU 3.6
0275: * @provisional This API might change or be removed in a future release.
0276: */
0277: public static final class PatternInfo { // struct for return information
0278: /**
0279: * @draft ICU 3.6
0280: * @provisional This API might change or be removed in a future release.
0281: */
0282: public static final int OK = 0;
0283:
0284: /**
0285: * @draft ICU 3.6
0286: * @provisional This API might change or be removed in a future release.
0287: */
0288: public static final int BASE_CONFLICT = 1;
0289:
0290: /**
0291: * @draft ICU 3.6
0292: * @provisional This API might change or be removed in a future release.
0293: */
0294: public static final int CONFLICT = 2;
0295:
0296: /**
0297: * @draft ICU 3.6
0298: * @provisional This API might change or be removed in a future release.
0299: */
0300: public int status;
0301:
0302: /**
0303: * @draft ICU 3.6
0304: * @provisional This API might change or be removed in a future release.
0305: */
0306: public String conflictingPattern;
0307:
0308: /**
0309: * Simple constructor, since this is treated like a struct.
0310: * @draft ICU 3.6
0311: * @provisional This API might change or be removed in a future release.
0312: */
0313: public PatternInfo() {
0314: }
0315: }
0316:
0317: static Transliterator fromHex = Transliterator
0318: .getInstance("hex-any");
0319:
0320: /**
0321: * Adds a pattern to the generator. If the pattern has the same skeleton as
0322: * an existing pattern, and the override parameter is set, then the previous
0323: * value is overriden. Otherwise, the previous value is retained. In either
0324: * case, the conflicting information is returned in PatternInfo.
0325: * <p>
0326: * Note that single-field patterns (like "MMM") are automatically added, and
0327: * don't need to be added explicitly!
0328: *
0329: * @param override
0330: * when existing values are to be overridden use true, otherwise
0331: * use false.
0332: * @draft ICU 3.6
0333: * @provisional This API might change or be removed in a future release.
0334: */
0335: public DateTimePatternGenerator add(String pattern,
0336: boolean override, PatternInfo returnInfo) {
0337: checkFrozen();
0338: if (pattern.indexOf("\\u") >= 0) {
0339: String oldPattern = pattern;
0340: pattern = fromHex.transliterate(pattern);
0341: }
0342: DateTimeMatcher matcher = new DateTimeMatcher()
0343: .set(pattern, fp);
0344: String basePattern = matcher.getBasePattern();
0345: String previousPatternWithSameBase = (String) basePattern_pattern
0346: .get(basePattern);
0347: if (previousPatternWithSameBase != null) {
0348: returnInfo.status = PatternInfo.BASE_CONFLICT;
0349: returnInfo.conflictingPattern = previousPatternWithSameBase;
0350: if (!override)
0351: return this ;
0352: }
0353: String previousValue = (String) skeleton2pattern.get(matcher);
0354: if (previousValue != null) {
0355: returnInfo.status = PatternInfo.CONFLICT;
0356: returnInfo.conflictingPattern = previousValue;
0357: if (!override)
0358: return this ;
0359: }
0360: returnInfo.status = PatternInfo.OK;
0361: returnInfo.conflictingPattern = "";
0362: skeleton2pattern.put(matcher, pattern);
0363: basePattern_pattern.put(basePattern, pattern);
0364: return this ;
0365: }
0366:
0367: /**
0368: * Utility to return a unique skeleton from a given pattern. For example,
0369: * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
0370: *
0371: * @param pattern
0372: * Input pattern, such as "dd/MMM"
0373: * @return skeleton, such as "MMMdd"
0374: * @draft ICU 3.6
0375: * @provisional This API might change or be removed in a future release.
0376: */
0377: public String getSkeleton(String pattern) {
0378: synchronized (this ) { // synchronized since a getter must be thread-safe
0379: current.set(pattern, fp);
0380: return current.toString();
0381: }
0382: }
0383:
0384: /**
0385: * Utility to return a unique base skeleton from a given pattern. This is
0386: * the same as the skeleton, except that differences in length are minimized
0387: * so as to only preserve the difference between string and numeric form. So
0388: * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
0389: * (notice the single d).
0390: *
0391: * @param pattern
0392: * Input pattern, such as "dd/MMM"
0393: * @return skeleton, such as "MMMdd"
0394: * @draft ICU 3.6
0395: * @provisional This API might change or be removed in a future release.
0396: */
0397: public String getBaseSkeleton(String pattern) {
0398: synchronized (this ) { // synchronized since a getter must be thread-safe
0399: current.set(pattern, fp);
0400: return current.getBasePattern();
0401: }
0402: }
0403:
0404: /**
0405: * Return a list of all the skeletons (in canonical form) from this class,
0406: * and the patterns that they map to.
0407: *
0408: * @param result
0409: * an output Map in which to place the mapping from skeleton to
0410: * pattern. If you want to see the internal order being used,
0411: * supply a LinkedHashMap. If the input value is null, then a
0412: * LinkedHashMap is allocated.
0413: * <p>
0414: * <i>Issue: an alternate API would be to just return a list of
0415: * the skeletons, and then have a separate routine to get from
0416: * skeleton to pattern.</i>
0417: * @return the input Map containing the values.
0418: * @draft ICU 3.6
0419: * @provisional This API might change or be removed in a future release.
0420: */
0421: public Map getSkeletons(Map result) {
0422: if (result == null)
0423: result = new LinkedHashMap();
0424: for (Iterator it = skeleton2pattern.keySet().iterator(); it
0425: .hasNext();) {
0426: DateTimeMatcher item = (DateTimeMatcher) it.next();
0427: String pattern = (String) skeleton2pattern.get(item);
0428: if (CANONICAL_SET.contains(pattern))
0429: continue;
0430: result.put(item.toString(), pattern);
0431: }
0432: return result;
0433: }
0434:
0435: /**
0436: * Return a list of all the base skeletons (in canonical form) from this class
0437: * @draft ICU 3.6
0438: * @provisional This API might change or be removed in a future release.
0439: */
0440: public Set getBaseSkeletons(Set result) {
0441: if (result == null)
0442: result = new HashSet();
0443: result.addAll(basePattern_pattern.keySet());
0444: return result;
0445: }
0446:
0447: /**
0448: * Adjusts the field types (width and subtype) of a pattern to match what is
0449: * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
0450: * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
0451: * "dd-MMMM hh:mm". This is used internally to get the best match for the
0452: * input skeleton, but can also be used externally.
0453: *
0454: * @param pattern
0455: * input pattern
0456: * @param skeleton
0457: * @return pattern adjusted to match the skeleton fields widths and
0458: * subtypes.
0459: * @draft ICU 3.6
0460: * @provisional This API might change or be removed in a future release.
0461: */
0462: public String replaceFieldTypes(String pattern, String skeleton) {
0463: synchronized (this ) { // synchronized since a getter must be thread-safe
0464: return adjustFieldTypes(pattern, current.set(skeleton, fp),
0465: false);
0466: }
0467: }
0468:
0469: /**
0470: * The date time format is a message format pattern used to compose date and
0471: * time patterns. The default value is "{0} {1}", where {0} will be replaced
0472: * by the date pattern and {1} will be replaced by the time pattern.
0473: * <p>
0474: * This is used when the input skeleton contains both date and time fields,
0475: * but there is not a close match among the added patterns. For example,
0476: * suppose that this object was created by adding "dd-MMM" and "hh:mm", and
0477: * its datetimeFormat is the default "{0} {1}". Then if the input skeleton
0478: * is "MMMdhmm", there is not an exact match, so the input skeleton is
0479: * broken up into two components "MMMd" and "hmm". There are close matches
0480: * for those two skeletons, so the result is put together with this pattern,
0481: * resulting in "d-MMM h:mm".
0482: *
0483: * @param dateTimeFormat
0484: * message format pattern, here {0} will be replaced by the date
0485: * pattern and {1} will be replaced by the time pattern.
0486: * @draft ICU 3.6
0487: * @provisional This API might change or be removed in a future release.
0488: */
0489: public void setDateTimeFormat(String dateTimeFormat) {
0490: checkFrozen();
0491: this .dateTimeFormat = dateTimeFormat;
0492: }
0493:
0494: /**
0495: * Getter corresponding to setDateTimeFormat.
0496: *
0497: * @return pattern
0498: * @draft ICU 3.6
0499: * @provisional This API might change or be removed in a future release.
0500: */
0501: public String getDateTimeFormat() {
0502: return dateTimeFormat;
0503: }
0504:
0505: /**
0506: * The decimal value is used in formatting fractions of seconds. If the
0507: * skeleton contains fractional seconds, then this is used with the
0508: * fractional seconds. For example, suppose that the input pattern is
0509: * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
0510: * the decimal string is ",". Then the resulting pattern is modified to be
0511: * "H:mm:ss,SSSS"
0512: *
0513: * @param decimal
0514: * @draft ICU 3.6
0515: * @provisional This API might change or be removed in a future release.
0516: */
0517: public void setDecimal(String decimal) {
0518: checkFrozen();
0519: this .decimal = decimal;
0520: }
0521:
0522: /**
0523: * Getter corresponding to setDecimal.
0524: * @return string corresponding to the decimal point
0525: * @draft ICU 3.6
0526: * @provisional This API might change or be removed in a future release.
0527: */
0528: public String getDecimal() {
0529: return decimal;
0530: }
0531:
0532: /**
0533: * Redundant patterns are those which if removed, make no difference in the
0534: * resulting getBestPattern values. This method returns a list of them, to
0535: * help check the consistency of the patterns used to build this generator.
0536: *
0537: * @param output
0538: * stores the redundant patterns that are removed. To get these
0539: * in internal order, supply a LinkedHashSet. If null, a
0540: * collection is allocated.
0541: * @return the collection with added elements.
0542: * @deprecated
0543: * @internal
0544: */
0545: public Collection getRedundants(Collection output) {
0546: synchronized (this ) { // synchronized since a getter must be thread-safe
0547: if (output == null)
0548: output = new LinkedHashSet();
0549: for (Iterator it = skeleton2pattern.keySet().iterator(); it
0550: .hasNext();) {
0551: DateTimeMatcher current = (DateTimeMatcher) it.next();
0552: String pattern = (String) skeleton2pattern.get(current);
0553: if (CANONICAL_SET.contains(pattern))
0554: continue;
0555: skipMatcher = current;
0556: String trial = getBestPattern(current.toString());
0557: if (trial.equals(pattern)) {
0558: output.add(pattern);
0559: }
0560: }
0561: if (false) { // ordered
0562: DateTimePatternGenerator results = new DateTimePatternGenerator();
0563: PatternInfo pinfo = new PatternInfo();
0564: for (Iterator it = skeleton2pattern.keySet().iterator(); it
0565: .hasNext();) {
0566: DateTimeMatcher current = (DateTimeMatcher) it
0567: .next();
0568: String pattern = (String) skeleton2pattern
0569: .get(current);
0570: if (CANONICAL_SET.contains(pattern))
0571: continue;
0572: //skipMatcher = current;
0573: String trial = results.getBestPattern(current
0574: .toString());
0575: if (trial.equals(pattern)) {
0576: output.add(pattern);
0577: } else {
0578: results.add(pattern, false, pinfo);
0579: }
0580: }
0581: }
0582: return output;
0583: }
0584: }
0585:
0586: // Field numbers, used for AppendItem functions
0587:
0588: /**
0589: * @draft ICU 3.6
0590: * @provisional This API might change or be removed in a future release.
0591: */
0592: static final public int ERA = 0;
0593:
0594: /**
0595: * @draft ICU 3.6
0596: * @provisional This API might change or be removed in a future release.
0597: */
0598: static final public int YEAR = 1;
0599:
0600: /**
0601: * @draft ICU 3.6
0602: * @provisional This API might change or be removed in a future release.
0603: */
0604: static final public int QUARTER = 2;
0605:
0606: /**
0607: * @draft ICU 3.6
0608: * @provisional This API might change or be removed in a future release.
0609: */
0610: static final public int MONTH = 3;
0611:
0612: /**
0613: * @draft ICU 3.6
0614: * @provisional This API might change or be removed in a future release.
0615: */
0616: static final public int WEEK_OF_YEAR = 4;
0617:
0618: /**
0619: * @draft ICU 3.6
0620: * @provisional This API might change or be removed in a future release.
0621: */
0622: static final public int WEEK_OF_MONTH = 5;
0623:
0624: /**
0625: * @draft ICU 3.6
0626: * @provisional This API might change or be removed in a future release.
0627: */
0628: static final public int WEEKDAY = 6;
0629:
0630: /**
0631: * @draft ICU 3.6
0632: * @provisional This API might change or be removed in a future release.
0633: */
0634: static final public int DAY = 7;
0635:
0636: /**
0637: * @draft ICU 3.6
0638: * @provisional This API might change or be removed in a future release.
0639: */
0640: static final public int DAY_OF_YEAR = 8;
0641:
0642: /**
0643: * @draft ICU 3.6
0644: * @provisional This API might change or be removed in a future release.
0645: */
0646: static final public int DAY_OF_WEEK_IN_MONTH = 9;
0647:
0648: /**
0649: * @draft ICU 3.6
0650: * @provisional This API might change or be removed in a future release.
0651: */
0652: static final public int DAYPERIOD = 10;
0653:
0654: /**
0655: * @draft ICU 3.6
0656: * @provisional This API might change or be removed in a future release.
0657: */
0658: static final public int HOUR = 11;
0659:
0660: /**
0661: * @draft ICU 3.6
0662: * @provisional This API might change or be removed in a future release.
0663: */
0664: static final public int MINUTE = 12;
0665:
0666: /**
0667: * @draft ICU 3.6
0668: * @provisional This API might change or be removed in a future release.
0669: */
0670: static final public int SECOND = 13;
0671:
0672: /**
0673: * @draft ICU 3.6
0674: * @provisional This API might change or be removed in a future release.
0675: */
0676: static final public int FRACTIONAL_SECOND = 14;
0677:
0678: /**
0679: * @draft ICU 3.6
0680: * @provisional This API might change or be removed in a future release.
0681: */
0682: static final public int ZONE = 15;
0683:
0684: /**
0685: * @draft ICU 3.6
0686: * @provisional This API might change or be removed in a future release.
0687: */
0688: static final public int TYPE_LIMIT = 16;
0689:
0690: /**
0691: * An AppendItem format is a pattern used to append a field if there is no
0692: * good match. For example, suppose that the input skeleton is "GyyyyMMMd",
0693: * and there is no matching pattern internally, but there is a pattern
0694: * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
0695: * G. The way these two are conjoined is by using the AppendItemFormat for G
0696: * (era). So if that value is, say "{0}, {1}" then the final resulting
0697: * pattern is "d-MM-yyyy, G".
0698: * <p>
0699: * There are actually three available variables: {0} is the pattern so far,
0700: * {1} is the element we are adding, and {2} is the name of the element.
0701: * <p>
0702: * This reflects the way that the CLDR data is organized.
0703: *
0704: * @param field
0705: * such as ERA
0706: * @param value
0707: * pattern, such as "{0}, {1}"
0708: * @draft ICU 3.6
0709: * @provisional This API might change or be removed in a future release.
0710: */
0711: public void setAppendItemFormats(int field, String value) {
0712: checkFrozen();
0713: appendItemFormats[field] = value;
0714: }
0715:
0716: /**
0717: * Getter corresponding to setAppendItemFormats. Values below 0 or at or
0718: * above TYPE_LIMIT are illegal arguments.
0719: *
0720: * @param field
0721: * @return append pattern for field
0722: * @draft ICU 3.6
0723: * @provisional This API might change or be removed in a future release.
0724: */
0725: public String getAppendItemFormats(int field) {
0726: return appendItemFormats[field];
0727: }
0728:
0729: /**
0730: * Sets the names of fields, eg "era" in English for ERA. These are only
0731: * used if the corresponding AppendItemFormat is used, and if it contains a
0732: * {2} variable.
0733: * <p>
0734: * This reflects the way that the CLDR data is organized.
0735: *
0736: * @param field
0737: * @param value
0738: * @draft ICU 3.6
0739: * @provisional This API might change or be removed in a future release.
0740: */
0741: public void setAppendItemNames(int field, String value) {
0742: checkFrozen();
0743: appendItemNames[field] = value;
0744: }
0745:
0746: /**
0747: * Getter corresponding to setAppendItemNames. Values below 0 or at or above
0748: * TYPE_LIMIT are illegal arguments.
0749: *
0750: * @param field
0751: * @return name for field
0752: * @draft ICU 3.6
0753: * @provisional This API might change or be removed in a future release.
0754: */
0755: public String getAppendItemNames(int field) {
0756: return appendItemNames[field];
0757: }
0758:
0759: /**
0760: * Determines whether a skeleton contains a single field
0761: *
0762: * @param skeleton
0763: * @return true or not
0764: * @deprecated
0765: * @internal
0766: */
0767: public static boolean isSingleField(String skeleton) {
0768: char first = skeleton.charAt(0);
0769: for (int i = 1; i < skeleton.length(); ++i) {
0770: if (skeleton.charAt(i) != first)
0771: return false;
0772: }
0773: return true;
0774: }
0775:
0776: /**
0777: * Boilerplate for Freezable
0778: * @draft ICU 3.6
0779: * @provisional This API might change or be removed in a future release.
0780: */
0781: public boolean isFrozen() {
0782: return frozen;
0783: }
0784:
0785: /**
0786: * Boilerplate for Freezable
0787: * @draft ICU 3.6
0788: * @provisional This API might change or be removed in a future release.
0789: */
0790: public Object freeze() {
0791: frozen = true;
0792: return this ;
0793: }
0794:
0795: /**
0796: * Boilerplate for Freezable
0797: * @draft ICU 3.6
0798: * @provisional This API might change or be removed in a future release.
0799: */
0800: public Object cloneAsThawed() {
0801: DateTimePatternGenerator result = (DateTimePatternGenerator) (this
0802: .clone());
0803: frozen = false;
0804: return result;
0805: }
0806:
0807: /**
0808: * Boilerplate
0809: * @draft ICU 3.6
0810: * @provisional This API might change or be removed in a future release.
0811: */
0812: public Object clone() {
0813: try {
0814: DateTimePatternGenerator result = (DateTimePatternGenerator) (super
0815: .clone());
0816: result.skeleton2pattern = (TreeMap) skeleton2pattern
0817: .clone();
0818: result.basePattern_pattern = (TreeMap) basePattern_pattern
0819: .clone();
0820: result.appendItemFormats = (String[]) appendItemFormats
0821: .clone();
0822: result.appendItemNames = (String[]) appendItemNames.clone();
0823: result.current = new DateTimeMatcher();
0824: result.fp = new FormatParser();
0825: result._distanceInfo = new DistanceInfo();
0826:
0827: result.frozen = false;
0828: return result;
0829: } catch (CloneNotSupportedException e) {
0830: throw new IllegalArgumentException("Internal Error");
0831: }
0832: }
0833:
0834: /**
0835: * Utility class for FormatParser. Immutable class.
0836: * @deprecated
0837: * @internal
0838: */
0839: public static class VariableField {
0840: private String string;
0841:
0842: /**
0843: * Create a variable field
0844: * @param string
0845: * @deprecated
0846: * @internal
0847: */
0848: public VariableField(String string) {
0849: this .string = string;
0850: }
0851:
0852: /**
0853: * Get the internal results
0854: * @deprecated
0855: * @internal
0856: */
0857: public String toString() {
0858: return string;
0859: }
0860: }
0861:
0862: /**
0863: * Class providing date formatting
0864: * @deprecated
0865: * @internal
0866: */
0867: static public class FormatParser {
0868: private transient PatternTokenizer tokenizer = new PatternTokenizer()
0869: .setSyntaxCharacters(new UnicodeSet("[a-zA-Z]"))
0870: //.setEscapeCharacters(new UnicodeSet("[^\\u0020-\\u007E]")) // WARNING: DateFormat doesn't accept \\uXXXX
0871: .setUsingQuote(true);
0872: private List items = new ArrayList();
0873:
0874: /**
0875: * Set the string to parse
0876: * @param string
0877: * @return this, for chaining
0878: * @deprecated
0879: * @internal
0880: */
0881: public FormatParser set(String string) {
0882: items.clear();
0883: if (string.length() == 0)
0884: return this ;
0885: tokenizer.setPattern(string);
0886: StringBuffer buffer = new StringBuffer();
0887: StringBuffer variable = new StringBuffer();
0888: while (true) {
0889: buffer.setLength(0);
0890: int status = tokenizer.next(buffer);
0891: if (status == PatternTokenizer.DONE)
0892: break;
0893: if (status == PatternTokenizer.SYNTAX) {
0894: if (variable.length() != 0
0895: && buffer.charAt(0) != variable.charAt(0)) {
0896: addVariable(variable);
0897: }
0898: variable.append(buffer);
0899: } else {
0900: addVariable(variable);
0901: items.add(buffer.toString());
0902: }
0903: }
0904: addVariable(variable);
0905: return this ;
0906: }
0907:
0908: private void addVariable(StringBuffer variable) {
0909: if (variable.length() != 0) {
0910: items.add(new VariableField(variable.toString()));
0911: variable.setLength(0);
0912: }
0913: }
0914:
0915: /** Return a collection of fields. These will be a mixture of Strings and VariableFields. Any "a" variable field is removed.
0916: * @param output List to append the items to. If null, is allocated as an ArrayList.
0917: * @return list
0918: */
0919: private List getVariableFields(List output) {
0920: if (output == null)
0921: output = new ArrayList();
0922: main: for (Iterator it = items.iterator(); it.hasNext();) {
0923: Object item = it.next();
0924: if (item instanceof VariableField) {
0925: String s = item.toString();
0926: switch (s.charAt(0)) {
0927: //case 'Q': continue main; // HACK
0928: case 'a':
0929: continue main; // remove
0930: }
0931: output.add(s);
0932: }
0933: }
0934: //System.out.println(output);
0935: return output;
0936: }
0937:
0938: /**
0939: * @return a string which is a concatenation of all the variable fields
0940: * @deprecated
0941: * @internal
0942: */
0943: public String getVariableFieldString() {
0944: List list = getVariableFields(null);
0945: StringBuffer result = new StringBuffer();
0946: for (Iterator it = list.iterator(); it.hasNext();) {
0947: String item = (String) it.next();
0948: result.append(item);
0949: }
0950: return result.toString();
0951: }
0952:
0953: /**
0954: * Returns modifiable list which is a mixture of Strings and VariableFields, in the order found during parsing.
0955: * @return modifiable list of items.
0956: * @deprecated
0957: * @internal
0958: */
0959: public List getItems() {
0960: return items;
0961: }
0962:
0963: /** Provide display form of formatted input
0964: * @return printable output string
0965: * @deprecated
0966: * @internal
0967: */
0968: public String toString() {
0969: return toString(0, items.size());
0970: }
0971:
0972: /**
0973: * Provide display form of formatted input
0974: * @param start item to start from
0975: * @param limit last item +1
0976: * @return printable output string
0977: * @deprecated
0978: * @internal
0979: */
0980: public String toString(int start, int limit) {
0981: StringBuffer result = new StringBuffer();
0982: for (int i = start; i < limit; ++i) {
0983: result.append(items.get(i).toString());
0984: }
0985: return result.toString();
0986: }
0987:
0988: /**
0989: * Internal method <p>
0990: * Returns true if it has a mixture of date and time fields
0991: * @return true or false
0992: * @deprecated
0993: * @internal
0994: */
0995: public boolean hasDateAndTimeFields() {
0996: int foundMask = 0;
0997: for (Iterator it = items.iterator(); it.hasNext();) {
0998: Object item = it.next();
0999: if (item instanceof VariableField) {
1000: int type = getType(item);
1001: foundMask |= 1 << type;
1002: }
1003: }
1004: boolean isDate = (foundMask & DATE_MASK) != 0;
1005: boolean isTime = (foundMask & TIME_MASK) != 0;
1006: return isDate && isTime;
1007: }
1008:
1009: /**
1010: * Internal routine
1011: * @param value
1012: * @param result
1013: * @return list
1014: * @deprecated
1015: * @internal
1016: */
1017: public List getAutoPatterns(String value, List result) {
1018: if (result == null)
1019: result = new ArrayList();
1020: int fieldCount = 0;
1021: int minField = Integer.MAX_VALUE;
1022: int maxField = Integer.MIN_VALUE;
1023: for (Iterator it = items.iterator(); it.hasNext();) {
1024: Object item = it.next();
1025: if (item instanceof VariableField) {
1026: try {
1027: int type = getType(item);
1028: if (minField > type)
1029: minField = type;
1030: if (maxField < type)
1031: maxField = type;
1032: if (type == ZONE || type == DAYPERIOD
1033: || type == WEEKDAY)
1034: return result; // skip anything with zones
1035: fieldCount++;
1036: } catch (Exception e) {
1037: return result; // if there are any funny fields, return
1038: }
1039: }
1040: }
1041: if (fieldCount < 3)
1042: return result; // skip
1043: // trim from start
1044: // trim first field IF there are no letters around it
1045: // and it is either the min or the max field
1046: // first field is either 0 or 1
1047: for (int i = 0; i < items.size(); ++i) {
1048: Object item = items.get(i);
1049: if (item instanceof VariableField) {
1050: int type = getType(item);
1051: if (type != minField && type != maxField)
1052: break;
1053:
1054: if (i > 0) {
1055: Object previousItem = items.get(0);
1056: if (alpha.containsSome(previousItem.toString()))
1057: break;
1058: }
1059: int start = i + 1;
1060: if (start < items.size()) {
1061: Object nextItem = items.get(start);
1062: if (nextItem instanceof String) {
1063: if (alpha.containsSome(nextItem.toString()))
1064: break;
1065: start++; // otherwise skip over string
1066: }
1067: }
1068: result.add(toString(start, items.size()));
1069: break;
1070: }
1071: }
1072: // now trim from end
1073: for (int i = items.size() - 1; i >= 0; --i) {
1074: Object item = items.get(i);
1075: if (item instanceof VariableField) {
1076: int type = getType(item);
1077: if (type != minField && type != maxField)
1078: break;
1079: if (i < items.size() - 1) {
1080: Object previousItem = items
1081: .get(items.size() - 1);
1082: if (alpha.containsSome(previousItem.toString()))
1083: break;
1084: }
1085: int end = i - 1;
1086: if (end > 0) {
1087: Object nextItem = items.get(end);
1088: if (nextItem instanceof String) {
1089: if (alpha.containsSome(nextItem.toString()))
1090: break;
1091: end--; // otherwise skip over string
1092: }
1093: }
1094: result.add(toString(0, end + 1));
1095: break;
1096: }
1097: }
1098:
1099: return result;
1100: }
1101:
1102: private static UnicodeSet alpha = new UnicodeSet(
1103: "[:alphabetic:]");
1104:
1105: private int getType(Object item) {
1106: String s = item.toString();
1107: int canonicalIndex = getCanonicalIndex(s);
1108: if (canonicalIndex < 0) {
1109: throw new IllegalArgumentException("Illegal field:\t"
1110: + s);
1111: }
1112: int type = types[canonicalIndex][1];
1113: return type;
1114: }
1115:
1116: /**
1117: * produce a quoted literal
1118: * @param string
1119: * @return string with quoted literals
1120: * @deprecated
1121: * @internal
1122: */
1123: public Object quoteLiteral(String string) {
1124: return tokenizer.quoteLiteral(string);
1125: }
1126:
1127: /**
1128: * Simple constructor, since this is treated like a struct.
1129: * @deprecated
1130: * @internal
1131: */
1132: public FormatParser() {
1133: super ();
1134: // TODO Auto-generated constructor stub
1135: }
1136: }
1137:
1138: // ========= PRIVATES ============
1139:
1140: private TreeMap skeleton2pattern = new TreeMap(); // items are in priority order
1141: private TreeMap basePattern_pattern = new TreeMap(); // items are in priority order
1142: private String decimal = "?";
1143: private String dateTimeFormat = "{0} {1}";
1144: private String[] appendItemFormats = new String[TYPE_LIMIT];
1145: private String[] appendItemNames = new String[TYPE_LIMIT];
1146: {
1147: for (int i = 0; i < TYPE_LIMIT; ++i) {
1148: appendItemFormats[i] = "{0} \u251C{2}: {1}\u2524";
1149: appendItemNames[i] = "F" + i;
1150: }
1151: }
1152:
1153: private transient DateTimeMatcher current = new DateTimeMatcher();
1154: private transient FormatParser fp = new FormatParser();
1155: private transient DistanceInfo _distanceInfo = new DistanceInfo();
1156: private transient boolean isComplete = false;
1157: private transient DateTimeMatcher skipMatcher = null; // only used temporarily, for internal purposes
1158: private transient boolean frozen = false;
1159:
1160: private static final int FRACTIONAL_MASK = 1 << FRACTIONAL_SECOND;
1161: private static final int SECOND_AND_FRACTIONAL_MASK = (1 << SECOND)
1162: | (1 << FRACTIONAL_SECOND);
1163:
1164: private void checkFrozen() {
1165: if (isFrozen()) {
1166: throw new UnsupportedOperationException(
1167: "Attempt to modify frozen object");
1168: }
1169: }
1170:
1171: /**
1172: * We only get called here if we failed to find an exact skeleton. We have broken it into date + time, and look for the pieces.
1173: * If we fail to find a complete skeleton, we compose in a loop until we have all the fields.
1174: */
1175: private String getBestAppending(int missingFields) {
1176: String resultPattern = null;
1177: if (missingFields != 0) {
1178: resultPattern = getBestRaw(current, missingFields,
1179: _distanceInfo);
1180: resultPattern = adjustFieldTypes(resultPattern, current,
1181: false);
1182:
1183: while (_distanceInfo.missingFieldMask != 0) { // precondition: EVERY single field must work!
1184:
1185: // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the
1186: // number separator
1187: if ((_distanceInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK
1188: && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) {
1189: resultPattern = adjustFieldTypes(resultPattern,
1190: current, true);
1191: _distanceInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit
1192: continue;
1193: }
1194:
1195: int startingMask = _distanceInfo.missingFieldMask;
1196: String temp = getBestRaw(current,
1197: _distanceInfo.missingFieldMask, _distanceInfo);
1198: temp = adjustFieldTypes(temp, current, false);
1199: int foundMask = startingMask
1200: & ~_distanceInfo.missingFieldMask;
1201: int topField = getTopBitNumber(foundMask);
1202: resultPattern = MessageFormat.format(
1203: getAppendFormat(topField), new Object[] {
1204: resultPattern, temp,
1205: getAppendName(topField) });
1206: }
1207: }
1208: return resultPattern;
1209: }
1210:
1211: private String getAppendName(int foundMask) {
1212: return "'" + appendItemNames[foundMask] + "'";
1213: }
1214:
1215: private String getAppendFormat(int foundMask) {
1216: return appendItemFormats[foundMask];
1217: }
1218:
1219: /**
1220: * @param current2
1221: * @return
1222: */
1223: private String adjustSeconds(DateTimeMatcher current2) {
1224: // TODO Auto-generated method stub
1225: return null;
1226: }
1227:
1228: /**
1229: * @param foundMask
1230: * @return
1231: */
1232: private int getTopBitNumber(int foundMask) {
1233: int i = 0;
1234: while (foundMask != 0) {
1235: foundMask >>>= 1;
1236: ++i;
1237: }
1238: return i - 1;
1239: }
1240:
1241: /**
1242: *
1243: */
1244: private void complete() {
1245: PatternInfo patternInfo = new PatternInfo();
1246: // make sure that every valid field occurs once, with a "default" length
1247: for (int i = 0; i < CANONICAL_ITEMS.length; ++i) {
1248: char c = (char) types[i][0];
1249: add(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo);
1250: }
1251: isComplete = true;
1252: }
1253:
1254: {
1255: complete();
1256: }
1257:
1258: /**
1259: *
1260: */
1261: private String getBestRaw(DateTimeMatcher source, int includeMask,
1262: DistanceInfo missingFields) {
1263: // if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
1264: // + ", mask: " + showMask(includeMask));
1265: int bestDistance = Integer.MAX_VALUE;
1266: String bestPattern = "";
1267: DistanceInfo tempInfo = new DistanceInfo();
1268: for (Iterator it = skeleton2pattern.keySet().iterator(); it
1269: .hasNext();) {
1270: DateTimeMatcher trial = (DateTimeMatcher) it.next();
1271: if (trial.equals(skipMatcher))
1272: continue;
1273: int distance = source.getDistance(trial, includeMask,
1274: tempInfo);
1275: // if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
1276: // + distance + ",\tmissing fields: " + tempInfo);
1277: if (distance < bestDistance) {
1278: bestDistance = distance;
1279: bestPattern = (String) skeleton2pattern.get(trial);
1280: missingFields.setTo(tempInfo);
1281: if (distance == 0)
1282: break;
1283: }
1284: }
1285: return bestPattern;
1286: }
1287:
1288: /**
1289: * @param fixFractionalSeconds TODO
1290: *
1291: */
1292: private String adjustFieldTypes(String pattern,
1293: DateTimeMatcher inputRequest, boolean fixFractionalSeconds) {
1294: fp.set(pattern);
1295: StringBuffer newPattern = new StringBuffer();
1296: for (Iterator it = fp.getItems().iterator(); it.hasNext();) {
1297: Object item = it.next();
1298: if (item instanceof String) {
1299: newPattern.append(fp.quoteLiteral((String) item));
1300: } else {
1301: String field = ((VariableField) item).string;
1302: int canonicalIndex = getCanonicalIndex(field);
1303: if (canonicalIndex < 0) {
1304: continue; // don't adjust
1305: }
1306: int type = types[canonicalIndex][1];
1307: if (fixFractionalSeconds && type == SECOND) {
1308: String newField = inputRequest.original[FRACTIONAL_SECOND];
1309: field = field + decimal + newField;
1310: } else if (inputRequest.type[type] != 0) {
1311: String newField = inputRequest.original[type];
1312: // normally we just replace the field. However HOUR is special; we only change the length
1313: if (type != HOUR) {
1314: field = newField;
1315: } else if (field.length() != newField.length()) {
1316: char c = field.charAt(0);
1317: field = "";
1318: for (int i = newField.length(); i > 0; --i)
1319: field += c;
1320: }
1321: }
1322: newPattern.append(field);
1323: }
1324: }
1325: //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern);
1326: return newPattern.toString();
1327: }
1328:
1329: // public static String repeat(String s, int count) {
1330: // StringBuffer result = new StringBuffer();
1331: // for (int i = 0; i < count; ++i) {
1332: // result.append(s);
1333: // }
1334: // return result.toString();
1335: // }
1336:
1337: /**
1338: * internal routine
1339: * @param pattern
1340: * @return field value
1341: * @deprecated
1342: * @internal
1343: */
1344: public String getFields(String pattern) {
1345: fp.set(pattern);
1346: StringBuffer newPattern = new StringBuffer();
1347: for (Iterator it = fp.getItems().iterator(); it.hasNext();) {
1348: Object item = it.next();
1349: if (item instanceof String) {
1350: newPattern.append(fp.quoteLiteral((String) item));
1351: } else {
1352: newPattern.append("{" + getName(item.toString()) + "}");
1353: }
1354: }
1355: return newPattern.toString();
1356: }
1357:
1358: private static String showMask(int mask) {
1359: String result = "";
1360: for (int i = 0; i < TYPE_LIMIT; ++i) {
1361: if ((mask & (1 << i)) == 0)
1362: continue;
1363: if (result.length() != 0)
1364: result += " | ";
1365: result += FIELD_NAME[i] + " ";
1366: }
1367: return result;
1368: }
1369:
1370: static private String[] CLDR_FIELD_APPEND = { "Era", "Year",
1371: "Quarter", "Month", "Week", "*", "Day-Of-Week", "Day", "*",
1372: "*", "*", "Hour", "Minute", "Second", "*", "Timezone" };
1373:
1374: static private String[] CLDR_FIELD_NAME = { "era", "year",
1375: "quarter", "month", "week", "*", "weekday", "day", "*",
1376: "*", "dayperiod", "hour", "minute", "second", "*", "zone" };
1377:
1378: static private String[] FIELD_NAME = { "Era", "Year", "Quarter",
1379: "Month", "Week_in_Year", "Week_in_Month", "Weekday", "Day",
1380: "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod", "Hour",
1381: "Minute", "Second", "Fractional_Second", "Zone" };
1382:
1383: static private String[] CANONICAL_ITEMS = { "G", "y", "Q", "M",
1384: "w", "W", "e", "d", "D", "F", "H", "m", "s", "S", "v" };
1385:
1386: static private Set CANONICAL_SET = new HashSet(Arrays
1387: .asList(CANONICAL_ITEMS));
1388:
1389: static final private int DATE_MASK = (1 << DAYPERIOD) - 1,
1390: TIME_MASK = (1 << TYPE_LIMIT) - 1 - DATE_MASK;
1391:
1392: static final private int // numbers are chosen to express 'distance'
1393: DELTA = 0x10,
1394: NUMERIC = 0x100, NONE = 0, NARROW = -0x100,
1395: SHORT = -0x101,
1396: LONG = -0x102,
1397: EXTRA_FIELD = 0x10000,
1398: MISSING_FIELD = 0x1000;
1399:
1400: static private String getName(String s) {
1401: int i = getCanonicalIndex(s);
1402: String name = FIELD_NAME[types[i][1]];
1403: int subtype = types[i][2];
1404: boolean string = subtype < 0;
1405: if (string)
1406: subtype = -subtype;
1407: if (subtype < 0)
1408: name += ":S";
1409: else
1410: name += ":N";
1411: return name;
1412: }
1413:
1414: static private int getCanonicalIndex(String s) {
1415: int len = s.length();
1416: int ch = s.charAt(0);
1417: for (int i = 0; i < types.length; ++i) {
1418: int[] row = types[i];
1419: if (row[0] != ch)
1420: continue;
1421: if (row[3] > len)
1422: continue;
1423: if (row[row.length - 1] < len)
1424: continue;
1425: return i;
1426: }
1427: return -1;
1428: }
1429:
1430: static private int[][] types = {
1431: // the order here makes a difference only when searching for single field.
1432: // format is:
1433: // pattern character, main type, weight, min length, weight
1434: { 'G', ERA, SHORT, 1, 3 },
1435: { 'G', ERA, LONG, 4 },
1436:
1437: { 'y', YEAR, NUMERIC, 1, 20 },
1438: { 'Y', YEAR, NUMERIC + DELTA, 1, 20 },
1439: { 'u', YEAR, NUMERIC + 2 * DELTA, 1, 20 },
1440:
1441: { 'Q', QUARTER, NUMERIC, 1, 2 },
1442: { 'Q', QUARTER, SHORT, 3 },
1443: { 'Q', QUARTER, LONG, 4 },
1444:
1445: { 'M', MONTH, NUMERIC, 1, 2 },
1446: { 'M', MONTH, SHORT, 3 },
1447: { 'M', MONTH, LONG, 4 },
1448: { 'M', MONTH, NARROW, 5 },
1449: { 'L', MONTH, NUMERIC + DELTA, 1, 2 },
1450: { 'L', MONTH, SHORT - DELTA, 3 },
1451: { 'L', MONTH, LONG - DELTA, 4 },
1452: { 'L', MONTH, NARROW - DELTA, 5 },
1453:
1454: { 'w', WEEK_OF_YEAR, NUMERIC, 1, 2 },
1455: { 'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1 },
1456:
1457: { 'e', WEEKDAY, NUMERIC + DELTA, 1, 2 },
1458: { 'e', WEEKDAY, SHORT - DELTA, 3 },
1459: { 'e', WEEKDAY, LONG - DELTA, 4 },
1460: { 'e', WEEKDAY, NARROW - DELTA, 5 },
1461: { 'E', WEEKDAY, SHORT, 1, 3 },
1462: { 'E', WEEKDAY, LONG, 4 },
1463: { 'E', WEEKDAY, NARROW, 5 },
1464: { 'c', WEEKDAY, NUMERIC + 2 * DELTA, 1, 2 },
1465: { 'c', WEEKDAY, SHORT - 2 * DELTA, 3 },
1466: { 'c', WEEKDAY, LONG - 2 * DELTA, 4 },
1467: { 'c', WEEKDAY, NARROW - 2 * DELTA, 5 },
1468:
1469: { 'd', DAY, NUMERIC, 1, 2 },
1470: { 'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3 },
1471: { 'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2 * DELTA, 1 },
1472: { 'g', DAY, NUMERIC + 3 * DELTA, 1, 20 }, // really internal use, so we don't care
1473:
1474: { 'a', DAYPERIOD, SHORT, 1 },
1475:
1476: { 'H', HOUR, NUMERIC + 10 * DELTA, 1, 2 }, // 24 hour
1477: { 'k', HOUR, NUMERIC + 11 * DELTA, 1, 2 },
1478: { 'h', HOUR, NUMERIC, 1, 2 }, // 12 hour
1479: { 'K', HOUR, NUMERIC + DELTA, 1, 2 },
1480:
1481: { 'm', MINUTE, NUMERIC, 1, 2 },
1482:
1483: { 's', SECOND, NUMERIC, 1, 2 },
1484: { 'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000 },
1485: { 'A', SECOND, NUMERIC + 2 * DELTA, 1, 1000 },
1486:
1487: { 'v', ZONE, SHORT - 2 * DELTA, 1 },
1488: { 'v', ZONE, LONG - 2 * DELTA, 4 },
1489: { 'z', ZONE, SHORT, 1, 3 }, { 'z', ZONE, LONG, 4 },
1490: { 'Z', ZONE, SHORT - DELTA, 1, 3 },
1491: { 'Z', ZONE, LONG - DELTA, 4 }, };
1492:
1493: private static class DateTimeMatcher implements Comparable {
1494: //private String pattern = null;
1495: private int[] type = new int[TYPE_LIMIT];
1496: private String[] original = new String[TYPE_LIMIT];
1497: private String[] baseOriginal = new String[TYPE_LIMIT];
1498:
1499: // just for testing; fix to make multi-threaded later
1500: // private static FormatParser fp = new FormatParser();
1501:
1502: public String toString() {
1503: StringBuffer result = new StringBuffer();
1504: for (int i = 0; i < TYPE_LIMIT; ++i) {
1505: if (original[i].length() != 0)
1506: result.append(original[i]);
1507: }
1508: return result.toString();
1509: }
1510:
1511: String getBasePattern() {
1512: StringBuffer result = new StringBuffer();
1513: for (int i = 0; i < TYPE_LIMIT; ++i) {
1514: if (baseOriginal[i].length() != 0)
1515: result.append(baseOriginal[i]);
1516: }
1517: return result.toString();
1518: }
1519:
1520: DateTimeMatcher set(String pattern, FormatParser fp) {
1521: if (pattern.indexOf("\\u") >= 0) {
1522: String oldPattern = pattern;
1523: pattern = fromHex.transliterate(pattern);
1524: }
1525: for (int i = 0; i < TYPE_LIMIT; ++i) {
1526: type[i] = NONE;
1527: original[i] = "";
1528: baseOriginal[i] = "";
1529: }
1530: fp.set(pattern);
1531: for (Iterator it = fp.getVariableFields(new ArrayList())
1532: .iterator(); it.hasNext();) {
1533: String field = (String) it.next();
1534: if (field.charAt(0) == 'a')
1535: continue; // skip day period, special cass
1536: int canonicalIndex = getCanonicalIndex(field);
1537: if (canonicalIndex < 0) {
1538: throw new IllegalArgumentException(
1539: "Illegal field:\t" + field + "\t in "
1540: + pattern);
1541: }
1542: int[] row = types[canonicalIndex];
1543: int typeValue = row[1];
1544: if (original[typeValue].length() != 0) {
1545: throw new IllegalArgumentException(
1546: "Conflicting fields:\t"
1547: + original[typeValue] + ", "
1548: + field + "\t in " + pattern);
1549: }
1550: original[typeValue] = field;
1551: char repeatChar = (char) row[0];
1552: int repeatCount = row[3];
1553: if (repeatCount > 3)
1554: repeatCount = 3; // hack to discard differences
1555: if ("GEzvQ".indexOf(repeatChar) >= 0)
1556: repeatCount = 1;
1557: baseOriginal[typeValue] = Utility.repeat(String
1558: .valueOf(repeatChar), repeatCount);
1559: int subTypeValue = row[2];
1560: if (subTypeValue > 0)
1561: subTypeValue += field.length();
1562: type[typeValue] = (byte) subTypeValue;
1563: }
1564: return this ;
1565: }
1566:
1567: /**
1568: *
1569: */
1570: int getFieldMask() {
1571: int result = 0;
1572: for (int i = 0; i < type.length; ++i) {
1573: if (type[i] != 0)
1574: result |= (1 << i);
1575: }
1576: return result;
1577: }
1578:
1579: /**
1580: *
1581: */
1582: void extractFrom(DateTimeMatcher source, int fieldMask) {
1583: for (int i = 0; i < type.length; ++i) {
1584: if ((fieldMask & (1 << i)) != 0) {
1585: type[i] = source.type[i];
1586: original[i] = source.original[i];
1587: } else {
1588: type[i] = NONE;
1589: original[i] = "";
1590: }
1591: }
1592: }
1593:
1594: int getDistance(DateTimeMatcher other, int includeMask,
1595: DistanceInfo distanceInfo) {
1596: int result = 0;
1597: distanceInfo.clear();
1598: for (int i = 0; i < type.length; ++i) {
1599: int myType = (includeMask & (1 << i)) == 0 ? 0
1600: : type[i];
1601: int otherType = other.type[i];
1602: if (myType == otherType)
1603: continue; // identical (maybe both zero) add 0
1604: if (myType == 0) { // and other is not
1605: result += EXTRA_FIELD;
1606: distanceInfo.addExtra(i);
1607: } else if (otherType == 0) { // and mine is not
1608: result += MISSING_FIELD;
1609: distanceInfo.addMissing(i);
1610: } else {
1611: result += Math.abs(myType - otherType); // square of mismatch
1612: }
1613: }
1614: return result;
1615: }
1616:
1617: public int compareTo(Object o) {
1618: DateTimeMatcher that = (DateTimeMatcher) o;
1619: for (int i = 0; i < original.length; ++i) {
1620: int comp = original[i].compareTo(that.original[i]);
1621: if (comp != 0)
1622: return -comp;
1623: }
1624: return 0;
1625: }
1626:
1627: public boolean equals(Object other) {
1628: if (other == null)
1629: return false;
1630: DateTimeMatcher that = (DateTimeMatcher) other;
1631: for (int i = 0; i < original.length; ++i) {
1632: if (!original[i].equals(that.original[i]))
1633: return false;
1634: }
1635: return true;
1636: }
1637:
1638: public int hashCode() {
1639: int result = 0;
1640: for (int i = 0; i < original.length; ++i) {
1641: result ^= original[i].hashCode();
1642: }
1643: return result;
1644: }
1645: }
1646:
1647: private static class DistanceInfo {
1648: int missingFieldMask;
1649: int extraFieldMask;
1650:
1651: void clear() {
1652: missingFieldMask = extraFieldMask = 0;
1653: }
1654:
1655: /**
1656: *
1657: */
1658: void setTo(DistanceInfo other) {
1659: missingFieldMask = other.missingFieldMask;
1660: extraFieldMask = other.extraFieldMask;
1661: }
1662:
1663: void addMissing(int field) {
1664: missingFieldMask |= (1 << field);
1665: }
1666:
1667: void addExtra(int field) {
1668: extraFieldMask |= (1 << field);
1669: }
1670:
1671: public String toString() {
1672: return "missingFieldMask: "
1673: + DateTimePatternGenerator
1674: .showMask(missingFieldMask)
1675: + ", extraFieldMask: "
1676: + DateTimePatternGenerator.showMask(extraFieldMask);
1677: }
1678: }
1679: }
1680: //#endif
1681: //eof
|