0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package java.text;
0019:
0020: import java.io.IOException;
0021: import java.io.InvalidObjectException;
0022: import java.io.ObjectInputStream;
0023: import java.io.ObjectOutputStream;
0024: import java.io.ObjectStreamField;
0025: import java.util.Arrays;
0026: import java.util.Date;
0027: import java.util.Iterator;
0028: import java.util.Locale;
0029: import java.util.Vector;
0030:
0031: import org.apache.harmony.text.internal.nls.Messages;
0032:
0033: /**
0034: * MessageFormat is used to format and parse arguments based on a pattern. The
0035: * pattern specifies how each argument will be formatted and concatenated with
0036: * other text to produce the formatted output.
0037: */
0038: public class MessageFormat extends Format {
0039:
0040: private static final long serialVersionUID = 6479157306784022952L;
0041:
0042: private Locale locale = Locale.getDefault();
0043:
0044: transient private String[] strings;
0045:
0046: private int[] argumentNumbers;
0047:
0048: private Format[] formats;
0049:
0050: private int maxOffset;
0051:
0052: transient private int maxArgumentIndex;
0053:
0054: /**
0055: * Constructs a new MessageFormat using the specified pattern and the
0056: * specified Locale for Formats.
0057: *
0058: * @param template
0059: * the pattern
0060: * @param locale
0061: * the locale
0062: *
0063: * @exception IllegalArgumentException
0064: * when the pattern cannot be parsed
0065: */
0066: public MessageFormat(String template, Locale locale) {
0067: this .locale = locale;
0068: applyPattern(template);
0069: }
0070:
0071: /**
0072: * Constructs a new MessageFormat using the specified pattern and the
0073: * default Locale for Formats.
0074: *
0075: * @param template
0076: * the pattern
0077: *
0078: * @exception IllegalArgumentException
0079: * when the pattern cannot be parsed
0080: */
0081: public MessageFormat(String template) {
0082: applyPattern(template);
0083: }
0084:
0085: /**
0086: * Changes this MessageFormat to use the specified pattern.
0087: *
0088: * @param template
0089: * the pattern
0090: *
0091: * @exception IllegalArgumentException
0092: * when the pattern cannot be parsed
0093: */
0094: public void applyPattern(String template) {
0095: int length = template.length();
0096: StringBuffer buffer = new StringBuffer();
0097: ParsePosition position = new ParsePosition(0);
0098: Vector<String> localStrings = new Vector<String>();
0099: int argCount = 0;
0100: int[] args = new int[10];
0101: int maxArg = -1;
0102: Vector<Format> localFormats = new Vector<Format>();
0103: while (position.getIndex() < length) {
0104: if (Format.upTo(template, position, buffer, '{')) {
0105: int arg = 0;
0106: int offset = position.getIndex();
0107: if (offset >= length) {
0108: // text.19=Invalid argument number
0109: throw new IllegalArgumentException(Messages
0110: .getString("text.19")); //$NON-NLS-1$
0111: }
0112: // Get argument number
0113: char ch;
0114: while ((ch = template.charAt(offset++)) != '}'
0115: && ch != ',') {
0116: if (ch < '0' && ch > '9') {
0117: // text.19=Invalid argument number
0118: throw new IllegalArgumentException(Messages
0119: .getString("text.19")); //$NON-NLS-1$
0120: }
0121:
0122: arg = arg * 10 + (ch - '0');
0123:
0124: if (arg < 0 || offset >= length) {
0125: // text.19=Invalid argument number
0126: throw new IllegalArgumentException(Messages
0127: .getString("text.19")); //$NON-NLS-1$
0128: }
0129: }
0130: offset--;
0131: position.setIndex(offset);
0132: localFormats.addElement(parseVariable(template,
0133: position));
0134: if (argCount >= args.length) {
0135: int[] newArgs = new int[args.length * 2];
0136: System.arraycopy(args, 0, newArgs, 0, args.length);
0137: args = newArgs;
0138: }
0139: args[argCount++] = arg;
0140: if (arg > maxArg) {
0141: maxArg = arg;
0142: }
0143: }
0144: localStrings.addElement(buffer.toString());
0145: buffer.setLength(0);
0146: }
0147: this .strings = new String[localStrings.size()];
0148: for (int i = 0; i < localStrings.size(); i++) {
0149: this .strings[i] = localStrings.elementAt(i);
0150: }
0151: argumentNumbers = args;
0152: this .formats = new Format[argCount];
0153: for (int i = 0; i < argCount; i++) {
0154: this .formats[i] = localFormats.elementAt(i);
0155: }
0156: maxOffset = argCount - 1;
0157: maxArgumentIndex = maxArg;
0158: }
0159:
0160: /**
0161: * Answers a new instance of MessageFormat with the same pattern and Formats
0162: * as this MessageFormat.
0163: *
0164: * @return a shallow copy of this MessageFormat
0165: *
0166: * @see java.lang.Cloneable
0167: */
0168: @Override
0169: public Object clone() {
0170: MessageFormat clone = (MessageFormat) super .clone();
0171: Format[] array = new Format[formats.length];
0172: for (int i = formats.length; --i >= 0;) {
0173: if (formats[i] != null) {
0174: array[i] = (Format) formats[i].clone();
0175: }
0176: }
0177: clone.formats = array;
0178: return clone;
0179: }
0180:
0181: /**
0182: * Compares the specified object to this MessageFormat and answer if they
0183: * are equal. The object must be an instance of MessageFormat and have the
0184: * same pattern.
0185: *
0186: * @param object
0187: * the object to compare with this object
0188: * @return true if the specified object is equal to this MessageFormat,
0189: * false otherwise
0190: *
0191: * @see #hashCode
0192: */
0193: @Override
0194: public boolean equals(Object object) {
0195: if (this == object) {
0196: return true;
0197: }
0198: if (!(object instanceof MessageFormat)) {
0199: return false;
0200: }
0201: MessageFormat format = (MessageFormat) object;
0202: if (maxOffset != format.maxOffset) {
0203: return false;
0204: }
0205: // Must use a loop since the lengths may be different due
0206: // to serialization cross-loading
0207: for (int i = 0; i <= maxOffset; i++) {
0208: if (argumentNumbers[i] != format.argumentNumbers[i]) {
0209: return false;
0210: }
0211: }
0212: return locale.equals(format.locale)
0213: && Arrays.equals(strings, format.strings)
0214: && Arrays.equals(formats, format.formats);
0215: }
0216:
0217: /**
0218: * Formats the specified object using the rules of this MessageFormat and
0219: * returns an AttributedCharacterIterator with the formatted message and
0220: * attributes. The AttributedCharacterIterator returned also includes the
0221: * attributes from the formats of this MessageFormat.
0222: *
0223: * @param object
0224: * the object to format
0225: * @return an AttributedCharacterIterator with the formatted message and
0226: * attributes
0227: *
0228: * @exception IllegalArgumentException
0229: * when the arguments in the object array cannot be formatted
0230: * by this Format
0231: */
0232: @Override
0233: public AttributedCharacterIterator formatToCharacterIterator(
0234: Object object) {
0235: if (object == null) {
0236: throw new NullPointerException();
0237: }
0238:
0239: StringBuffer buffer = new StringBuffer();
0240: Vector<FieldContainer> fields = new Vector<FieldContainer>();
0241:
0242: // format the message, and find fields
0243: formatImpl((Object[]) object, buffer, new FieldPosition(0),
0244: fields);
0245:
0246: // create an AttributedString with the formatted buffer
0247: AttributedString as = new AttributedString(buffer.toString());
0248:
0249: // add MessageFormat field attributes and values to the AttributedString
0250: for (int i = 0; i < fields.size(); i++) {
0251: FieldContainer fc = fields.elementAt(i);
0252: as.addAttribute(fc.attribute, fc.value, fc.start, fc.end);
0253: }
0254:
0255: // return the CharacterIterator from AttributedString
0256: return as.getIterator();
0257: }
0258:
0259: /**
0260: * Formats the Object arguments into the specified StringBuffer using the
0261: * pattern of this MessageFormat.
0262: * <p>
0263: * If Field Attribute of the FieldPosition supplied is
0264: * MessageFormat.Field.ARGUMENT, then begin and end index of this field
0265: * position is set to the location of the first occurrence of a message
0266: * format argument. Otherwise the FieldPosition is ignored
0267: * <p>
0268: *
0269: * @param objects
0270: * the array of Objects to format
0271: * @param buffer
0272: * the StringBuffer
0273: * @param field
0274: * a FieldPosition.
0275: *
0276: * @return the StringBuffer parameter <code>buffer</code>
0277: */
0278: public final StringBuffer format(Object[] objects,
0279: StringBuffer buffer, FieldPosition field) {
0280: return formatImpl(objects, buffer, field, null);
0281: }
0282:
0283: private StringBuffer formatImpl(Object[] objects,
0284: StringBuffer buffer, FieldPosition position,
0285: Vector<FieldContainer> fields) {
0286: FieldPosition passedField = new FieldPosition(0);
0287: for (int i = 0; i <= maxOffset; i++) {
0288: buffer.append(strings[i]);
0289: int begin = buffer.length();
0290: Object arg;
0291: if (objects != null && argumentNumbers[i] < objects.length) {
0292: arg = objects[argumentNumbers[i]];
0293: } else {
0294: buffer.append('{');
0295: buffer.append(argumentNumbers[i]);
0296: buffer.append('}');
0297: handleArgumentField(begin, buffer.length(),
0298: argumentNumbers[i], position, fields);
0299: continue;
0300: }
0301: Format format = formats[i];
0302: if (format == null || arg == null) {
0303: if (arg instanceof Number) {
0304: format = NumberFormat.getInstance();
0305: } else if (arg instanceof Date) {
0306: format = DateFormat.getInstance();
0307: } else {
0308: buffer.append(arg);
0309: handleArgumentField(begin, buffer.length(),
0310: argumentNumbers[i], position, fields);
0311: continue;
0312: }
0313: }
0314: if (format instanceof ChoiceFormat) {
0315: String result = format.format(arg);
0316: MessageFormat mf = new MessageFormat(result);
0317: mf.setLocale(locale);
0318: mf.format(objects, buffer, passedField);
0319: handleArgumentField(begin, buffer.length(),
0320: argumentNumbers[i], position, fields);
0321: handleformat(format, arg, begin, fields);
0322: } else {
0323: format.format(arg, buffer, passedField);
0324: handleArgumentField(begin, buffer.length(),
0325: argumentNumbers[i], position, fields);
0326: handleformat(format, arg, begin, fields);
0327: }
0328: }
0329: if (maxOffset + 1 < strings.length) {
0330: buffer.append(strings[maxOffset + 1]);
0331: }
0332: return buffer;
0333: }
0334:
0335: /**
0336: * Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field,
0337: * argnumber, begin and end index to the fields vector, or sets the
0338: * position's begin and end index if it has MessageFormat.Field.ARGUMENT as
0339: * its field attribute.
0340: *
0341: * @param begin
0342: * @param end
0343: * @param argnumber
0344: * @param position
0345: * @param fields
0346: */
0347: private void handleArgumentField(int begin, int end, int argnumber,
0348: FieldPosition position, Vector<FieldContainer> fields) {
0349: if (fields != null) {
0350: fields.add(new FieldContainer(begin, end, Field.ARGUMENT,
0351: new Integer(argnumber)));
0352: } else {
0353: if (position != null
0354: && position.getFieldAttribute() == Field.ARGUMENT
0355: && position.getEndIndex() == 0) {
0356: position.setBeginIndex(begin);
0357: position.setEndIndex(end);
0358: }
0359: }
0360: }
0361:
0362: /**
0363: * An inner class to store attributes, values, start and end indices.
0364: * Instances of this inner class are used as elements for the fields vector
0365: */
0366: private static class FieldContainer {
0367: int start, end;
0368:
0369: AttributedCharacterIterator.Attribute attribute;
0370:
0371: Object value;
0372:
0373: public FieldContainer(int start, int end,
0374: AttributedCharacterIterator.Attribute attribute,
0375: Object value) {
0376: this .start = start;
0377: this .end = end;
0378: this .attribute = attribute;
0379: this .value = value;
0380: }
0381: }
0382:
0383: /**
0384: * If fields vector is not null, find and add the fields of this format to
0385: * the fields vector by iterating through its AttributedCharacterIterator
0386: *
0387: * @param format
0388: * the format to find fields for
0389: * @param arg
0390: * object to format
0391: * @param begin
0392: * the index where the string this format has formatted begins
0393: * @param fields
0394: * fields vector, each entry in this vector are of type
0395: * FieldContainer.
0396: */
0397: private void handleformat(Format format, Object arg, int begin,
0398: Vector<FieldContainer> fields) {
0399: if (fields != null) {
0400: AttributedCharacterIterator iterator = format
0401: .formatToCharacterIterator(arg);
0402: while (iterator.getIndex() != iterator.getEndIndex()) {
0403: int start = iterator.getRunStart();
0404: int end = iterator.getRunLimit();
0405:
0406: Iterator<?> it = iterator.getAttributes().keySet()
0407: .iterator();
0408: while (it.hasNext()) {
0409: AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) it
0410: .next();
0411: Object value = iterator.getAttribute(attribute);
0412: fields.add(new FieldContainer(begin + start, begin
0413: + end, attribute, value));
0414: }
0415: iterator.setIndex(end);
0416: }
0417: }
0418: }
0419:
0420: /**
0421: * Formats the specified object into the specified StringBuffer using the
0422: * pattern of this MessageFormat.
0423: *
0424: * @param object
0425: * the object to format, must be an array of Object
0426: * @param buffer
0427: * the StringBuffer
0428: * @param field
0429: * a FieldPosition which is ignored
0430: * @return the StringBuffer parameter <code>buffer</code>
0431: *
0432: * @exception ClassCastException
0433: * when <code>object</code> is not an array of Object
0434: */
0435: @Override
0436: public final StringBuffer format(Object object,
0437: StringBuffer buffer, FieldPosition field) {
0438: return format((Object[]) object, buffer, field);
0439: }
0440:
0441: /**
0442: * Formats the Object arguments using the specified MessageFormat pattern.
0443: *
0444: * @param template
0445: * the pattern
0446: * @param objects
0447: * the array of Objects to format
0448: * @return the formatted result
0449: *
0450: * @exception IllegalArgumentException
0451: * when the pattern cannot be parsed
0452: */
0453: public static String format(String template, Object... objects) {
0454: if (objects != null) {
0455: for (int i = 0; i < objects.length; i++) {
0456: if (objects[i] == null) {
0457: objects[i] = "null";
0458: }
0459: }
0460: }
0461: return com.ibm.icu.text.MessageFormat.format(template, objects);
0462: }
0463:
0464: /**
0465: * Answers the Formats of this MessageFormat.
0466: *
0467: * @return an array of Format
0468: */
0469: public Format[] getFormats() {
0470: return formats.clone();
0471: }
0472:
0473: /**
0474: * Answers the formats used for each argument index. If an argument is
0475: * placed more than once in the pattern string, than returns the format of
0476: * the last one.
0477: *
0478: * @return an array of formats, ordered by argument index
0479: */
0480: public Format[] getFormatsByArgumentIndex() {
0481: Format[] answer = new Format[maxArgumentIndex + 1];
0482: for (int i = 0; i < maxOffset + 1; i++) {
0483: answer[argumentNumbers[i]] = formats[i];
0484: }
0485: return answer;
0486: }
0487:
0488: /**
0489: * Sets the format used for argument at index <code>argIndex</code>to
0490: * <code>format</code>
0491: *
0492: * @param argIndex
0493: * @param format
0494: */
0495: public void setFormatByArgumentIndex(int argIndex, Format format) {
0496: for (int i = 0; i < maxOffset + 1; i++) {
0497: if (argumentNumbers[i] == argIndex) {
0498: formats[i] = format;
0499: }
0500: }
0501: }
0502:
0503: /**
0504: * Sets the formats used for each argument <code>The formats</code> array
0505: * elements should be in the order of the argument indices.
0506: *
0507: * @param formats
0508: */
0509: public void setFormatsByArgumentIndex(Format[] formats) {
0510: for (int j = 0; j < formats.length; j++) {
0511: for (int i = 0; i < maxOffset + 1; i++) {
0512: if (argumentNumbers[i] == j) {
0513: this .formats[i] = formats[j];
0514: }
0515: }
0516: }
0517: }
0518:
0519: /**
0520: * Answers the Locale used when creating Formats.
0521: *
0522: * @return the Locale used to create Formats
0523: */
0524: public Locale getLocale() {
0525: return locale;
0526: }
0527:
0528: /**
0529: * Answers an integer hash code for the receiver. Objects which are equal
0530: * answer the same value for this method.
0531: *
0532: * @return the receiver's hash
0533: *
0534: * @see #equals
0535: */
0536: @Override
0537: public int hashCode() {
0538: int hashCode = 0;
0539: for (int i = 0; i <= maxOffset; i++) {
0540: hashCode += argumentNumbers[i] + strings[i].hashCode();
0541: if (formats[i] != null) {
0542: hashCode += formats[i].hashCode();
0543: }
0544: }
0545: if (maxOffset + 1 < strings.length) {
0546: hashCode += strings[maxOffset + 1].hashCode();
0547: }
0548: if (locale != null) {
0549: return hashCode + locale.hashCode();
0550: }
0551: return hashCode;
0552: }
0553:
0554: /**
0555: * Parse the message arguments from the specified String using the rules of
0556: * this MessageFormat.
0557: *
0558: * @param string
0559: * the String to parse
0560: * @return the array of Object arguments resulting from the parse
0561: *
0562: * @exception ParseException
0563: * when an error occurs during parsing
0564: */
0565: public Object[] parse(String string) throws ParseException {
0566: ParsePosition position = new ParsePosition(0);
0567: Object[] result = parse(string, position);
0568: if (position.getErrorIndex() != -1 || position.getIndex() == 0) {
0569: throw new ParseException(null, position.getErrorIndex());
0570: }
0571: return result;
0572: }
0573:
0574: /**
0575: * Parse the message argument from the specified String starting at the
0576: * index specified by the ParsePosition. If the string is successfully
0577: * parsed, the index of the ParsePosition is updated to the index following
0578: * the parsed text.
0579: *
0580: * @param string
0581: * the String to parse
0582: * @param position
0583: * the ParsePosition, updated on return with the index following
0584: * the parsed text, or on error the index is unchanged and the
0585: * error index is set to the index where the error occurred
0586: * @return the array of Object arguments resulting from the parse, or null
0587: * if there is an error
0588: */
0589: public Object[] parse(String string, ParsePosition position) {
0590: if (string == null) {
0591: return new Object[0];
0592: }
0593: ParsePosition internalPos = new ParsePosition(0);
0594: int offset = position.getIndex();
0595: Object[] result = new Object[maxArgumentIndex + 1];
0596: for (int i = 0; i <= maxOffset; i++) {
0597: String sub = strings[i];
0598: if (!string.startsWith(sub, offset)) {
0599: position.setErrorIndex(offset);
0600: return null;
0601: }
0602: offset += sub.length();
0603: Object parse;
0604: Format format = formats[i];
0605: if (format == null) {
0606: if (i + 1 < strings.length) {
0607: int next = string.indexOf(strings[i + 1], offset);
0608: if (next == -1) {
0609: position.setErrorIndex(offset);
0610: return null;
0611: }
0612: parse = string.substring(offset, next);
0613: offset = next;
0614: } else {
0615: parse = string.substring(offset);
0616: offset = string.length();
0617: }
0618: } else {
0619: internalPos.setIndex(offset);
0620: parse = format.parseObject(string, internalPos);
0621: if (internalPos.getErrorIndex() != -1) {
0622: position.setErrorIndex(offset);
0623: return null;
0624: }
0625: offset = internalPos.getIndex();
0626: }
0627: result[argumentNumbers[i]] = parse;
0628: }
0629: if (maxOffset + 1 < strings.length) {
0630: String sub = strings[maxOffset + 1];
0631: if (!string.startsWith(sub, offset)) {
0632: position.setErrorIndex(offset);
0633: return null;
0634: }
0635: offset += sub.length();
0636: }
0637: position.setIndex(offset);
0638: return result;
0639: }
0640:
0641: /**
0642: * Parse the message argument from the specified String starting at the
0643: * index specified by the ParsePosition. If the string is successfully
0644: * parsed, the index of the ParsePosition is updated to the index following
0645: * the parsed text.
0646: *
0647: * @param string
0648: * the String to parse
0649: * @param position
0650: * the ParsePosition, updated on return with the index following
0651: * the parsed text, or on error the index is unchanged and the
0652: * error index is set to the index where the error occurred
0653: * @return the array of Object arguments resulting from the parse, or null
0654: * if there is an error
0655: */
0656: @Override
0657: public Object parseObject(String string, ParsePosition position) {
0658: return parse(string, position);
0659: }
0660:
0661: private int match(String string, ParsePosition position,
0662: boolean last, String[] tokens) {
0663: int length = string.length(), offset = position.getIndex(), token = -1;
0664: while (offset < length
0665: && Character.isWhitespace(string.charAt(offset))) {
0666: offset++;
0667: }
0668: for (int i = tokens.length; --i >= 0;) {
0669: if (string.regionMatches(true, offset, tokens[i], 0,
0670: tokens[i].length())) {
0671: token = i;
0672: break;
0673: }
0674: }
0675: if (token == -1) {
0676: return -1;
0677: }
0678: offset += tokens[token].length();
0679: while (offset < length
0680: && Character.isWhitespace(string.charAt(offset))) {
0681: offset++;
0682: }
0683: char ch;
0684: if (offset < length
0685: && ((ch = string.charAt(offset)) == '}' || (!last && ch == ','))) {
0686: position.setIndex(offset + 1);
0687: return token;
0688: }
0689: return -1;
0690: }
0691:
0692: private Format parseVariable(String string, ParsePosition position) {
0693: int length = string.length(), offset = position.getIndex();
0694: char ch;
0695: if (offset >= length
0696: || ((ch = string.charAt(offset++)) != '}' && ch != ',')) {
0697: // text.15=Missing element format
0698: throw new IllegalArgumentException(Messages
0699: .getString("text.15")); //$NON-NLS-1$
0700: }
0701: position.setIndex(offset);
0702: if (ch == '}') {
0703: return null;
0704: }
0705: int type = match(string, position, false, new String[] {
0706: "time", //$NON-NLS-1$
0707: "date", "number", "choice" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0708: if (type == -1) {
0709: // text.16=Unknown element format
0710: throw new IllegalArgumentException(Messages
0711: .getString("text.16")); //$NON-NLS-1$
0712: }
0713: StringBuffer buffer = new StringBuffer();
0714: ch = string.charAt(position.getIndex() - 1);
0715: switch (type) {
0716: case 0: // time
0717: case 1: // date
0718: if (ch == '}') {
0719: return type == 1 ? DateFormat.getDateInstance(
0720: DateFormat.DEFAULT, locale) : DateFormat
0721: .getTimeInstance(DateFormat.DEFAULT, locale);
0722: }
0723: int dateStyle = match(string, position, true, new String[] {
0724: "full", "long", "medium", "short" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
0725: if (dateStyle == -1) {
0726: Format.upToWithQuotes(string, position, buffer, '}',
0727: '{');
0728: return new SimpleDateFormat(buffer.toString(), locale);
0729: }
0730: switch (dateStyle) {
0731: case 0:
0732: dateStyle = DateFormat.FULL;
0733: break;
0734: case 1:
0735: dateStyle = DateFormat.LONG;
0736: break;
0737: case 2:
0738: dateStyle = DateFormat.MEDIUM;
0739: break;
0740: case 3:
0741: dateStyle = DateFormat.SHORT;
0742: break;
0743: }
0744: return type == 1 ? DateFormat.getDateInstance(dateStyle,
0745: locale) : DateFormat.getTimeInstance(dateStyle,
0746: locale);
0747: case 2: // number
0748: if (ch == '}') {
0749: return NumberFormat.getInstance();
0750: }
0751: int numberStyle = match(string, position, true,
0752: new String[] { "currency", "percent", "integer" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0753: if (numberStyle == -1) {
0754: Format.upToWithQuotes(string, position, buffer, '}',
0755: '{');
0756: return new DecimalFormat(buffer.toString(),
0757: new DecimalFormatSymbols(locale));
0758: }
0759: switch (numberStyle) {
0760: case 0: // currency
0761: return NumberFormat.getCurrencyInstance(locale);
0762: case 1: // percent
0763: return NumberFormat.getPercentInstance(locale);
0764: }
0765: return NumberFormat.getIntegerInstance(locale);
0766: }
0767: // choice
0768: try {
0769: Format.upToWithQuotes(string, position, buffer, '}', '{');
0770: } catch (IllegalArgumentException e) {
0771: // ignored
0772: }
0773: return new ChoiceFormat(buffer.toString());
0774: }
0775:
0776: /**
0777: * Sets the specified Format used by this MessageFormat.
0778: *
0779: * @param offset
0780: * the format to change
0781: * @param format
0782: * the Format
0783: */
0784: public void setFormat(int offset, Format format) {
0785: formats[offset] = format;
0786: }
0787:
0788: /**
0789: * Sets the Formats used by this MessageFormat.
0790: *
0791: * @param formats
0792: * an array of Format
0793: */
0794: public void setFormats(Format[] formats) {
0795: int min = this .formats.length;
0796: if (formats.length < min) {
0797: min = formats.length;
0798: }
0799: for (int i = 0; i < min; i++) {
0800: this .formats[i] = formats[i];
0801: }
0802: }
0803:
0804: /**
0805: * Sets the Locale to use when creating Formats.
0806: *
0807: * @param locale
0808: * the Locale
0809: */
0810: public void setLocale(Locale locale) {
0811: this .locale = locale;
0812: for (int i = 0; i <= maxOffset; i++) {
0813: Format format = formats[i];
0814: if (format instanceof DecimalFormat) {
0815: formats[i] = new DecimalFormat(((DecimalFormat) format)
0816: .toPattern(), new DecimalFormatSymbols(locale));
0817: } else if (format instanceof SimpleDateFormat) {
0818: formats[i] = new SimpleDateFormat(
0819: ((SimpleDateFormat) format).toPattern(), locale);
0820: }
0821:
0822: }
0823: }
0824:
0825: private String decodeDecimalFormat(StringBuffer buffer,
0826: Format format) {
0827: buffer.append(",number"); //$NON-NLS-1$
0828: if (format.equals(NumberFormat.getNumberInstance(locale))) {
0829: // Empty block
0830: } else if (format.equals(NumberFormat
0831: .getIntegerInstance(locale))) {
0832: buffer.append(",integer"); //$NON-NLS-1$
0833: } else if (format.equals(NumberFormat
0834: .getCurrencyInstance(locale))) {
0835: buffer.append(",currency"); //$NON-NLS-1$
0836: } else if (format.equals(NumberFormat
0837: .getPercentInstance(locale))) {
0838: buffer.append(",percent"); //$NON-NLS-1$
0839: } else {
0840: buffer.append(',');
0841: return ((DecimalFormat) format).toPattern();
0842: }
0843: return null;
0844: }
0845:
0846: private String decodeSimpleDateFormat(StringBuffer buffer,
0847: Format format) {
0848: if (format.equals(DateFormat.getTimeInstance(
0849: DateFormat.DEFAULT, locale))) {
0850: buffer.append(",time"); //$NON-NLS-1$
0851: } else if (format.equals(DateFormat.getDateInstance(
0852: DateFormat.DEFAULT, locale))) {
0853: buffer.append(",date"); //$NON-NLS-1$
0854: } else if (format.equals(DateFormat.getTimeInstance(
0855: DateFormat.SHORT, locale))) {
0856: buffer.append(",time,short"); //$NON-NLS-1$
0857: } else if (format.equals(DateFormat.getDateInstance(
0858: DateFormat.SHORT, locale))) {
0859: buffer.append(",date,short"); //$NON-NLS-1$
0860: } else if (format.equals(DateFormat.getTimeInstance(
0861: DateFormat.LONG, locale))) {
0862: buffer.append(",time,long"); //$NON-NLS-1$
0863: } else if (format.equals(DateFormat.getDateInstance(
0864: DateFormat.LONG, locale))) {
0865: buffer.append(",date,long"); //$NON-NLS-1$
0866: } else if (format.equals(DateFormat.getTimeInstance(
0867: DateFormat.FULL, locale))) {
0868: buffer.append(",time,full"); //$NON-NLS-1$
0869: } else if (format.equals(DateFormat.getDateInstance(
0870: DateFormat.FULL, locale))) {
0871: buffer.append(",date,full"); //$NON-NLS-1$
0872: } else {
0873: buffer.append(",date,"); //$NON-NLS-1$
0874: return ((SimpleDateFormat) format).toPattern();
0875: }
0876: return null;
0877: }
0878:
0879: /**
0880: * Answers the pattern of this MessageFormat.
0881: *
0882: * @return the pattern
0883: */
0884: public String toPattern() {
0885: StringBuffer buffer = new StringBuffer();
0886: for (int i = 0; i <= maxOffset; i++) {
0887: appendQuoted(buffer, strings[i]);
0888: buffer.append('{');
0889: buffer.append(argumentNumbers[i]);
0890: Format format = formats[i];
0891: String pattern = null;
0892: if (format instanceof ChoiceFormat) {
0893: buffer.append(",choice,"); //$NON-NLS-1$
0894: pattern = ((ChoiceFormat) format).toPattern();
0895: } else if (format instanceof DecimalFormat) {
0896: pattern = decodeDecimalFormat(buffer, format);
0897: } else if (format instanceof SimpleDateFormat) {
0898: pattern = decodeSimpleDateFormat(buffer, format);
0899: } else if (format != null) {
0900: // text.17=Unknown format
0901: throw new IllegalArgumentException(Messages
0902: .getString("text.17")); //$NON-NLS-1$
0903: }
0904: if (pattern != null) {
0905: boolean quote = false;
0906: int index = 0, length = pattern.length(), count = 0;
0907: while (index < length) {
0908: char ch = pattern.charAt(index++);
0909: if (ch == '\'') {
0910: quote = !quote;
0911: }
0912: if (!quote) {
0913: if (ch == '{') {
0914: count++;
0915: }
0916: if (ch == '}') {
0917: if (count > 0) {
0918: count--;
0919: } else {
0920: buffer.append("'}"); //$NON-NLS-1$
0921: ch = '\'';
0922: }
0923: }
0924: }
0925: buffer.append(ch);
0926: }
0927: }
0928: buffer.append('}');
0929: }
0930: if (maxOffset + 1 < strings.length) {
0931: appendQuoted(buffer, strings[maxOffset + 1]);
0932: }
0933: return buffer.toString();
0934: }
0935:
0936: private void appendQuoted(StringBuffer buffer, String string) {
0937: int length = string.length();
0938: for (int i = 0; i < length; i++) {
0939: char ch = string.charAt(i);
0940: if (ch == '{' || ch == '}') {
0941: buffer.append('\'');
0942: buffer.append(ch);
0943: buffer.append('\'');
0944: } else {
0945: buffer.append(ch);
0946: }
0947: }
0948: }
0949:
0950: private static final ObjectStreamField[] serialPersistentFields = {
0951: new ObjectStreamField("argumentNumbers", int[].class), //$NON-NLS-1$
0952: new ObjectStreamField("formats", Format[].class), //$NON-NLS-1$
0953: new ObjectStreamField("locale", Locale.class), //$NON-NLS-1$
0954: new ObjectStreamField("maxOffset", Integer.TYPE), //$NON-NLS-1$
0955: new ObjectStreamField("offsets", int[].class), //$NON-NLS-1$
0956: new ObjectStreamField("pattern", String.class), }; //$NON-NLS-1$
0957:
0958: private void writeObject(ObjectOutputStream stream)
0959: throws IOException {
0960: ObjectOutputStream.PutField fields = stream.putFields();
0961: fields.put("argumentNumbers", argumentNumbers); //$NON-NLS-1$
0962: Format[] compatibleFormats = formats;
0963: fields.put("formats", compatibleFormats); //$NON-NLS-1$
0964: fields.put("locale", locale); //$NON-NLS-1$
0965: fields.put("maxOffset", maxOffset); //$NON-NLS-1$
0966: int offset = 0;
0967: int offsetsLength = maxOffset + 1;
0968: int[] offsets = new int[offsetsLength];
0969: StringBuffer pattern = new StringBuffer();
0970: for (int i = 0; i <= maxOffset; i++) {
0971: offset += strings[i].length();
0972: offsets[i] = offset;
0973: pattern.append(strings[i]);
0974: }
0975: if (maxOffset + 1 < strings.length) {
0976: pattern.append(strings[maxOffset + 1]);
0977: }
0978: fields.put("offsets", offsets); //$NON-NLS-1$
0979: fields.put("pattern", pattern.toString()); //$NON-NLS-1$
0980: stream.writeFields();
0981: }
0982:
0983: private void readObject(ObjectInputStream stream)
0984: throws IOException, ClassNotFoundException {
0985: ObjectInputStream.GetField fields = stream.readFields();
0986: argumentNumbers = (int[]) fields.get("argumentNumbers", null); //$NON-NLS-1$
0987: formats = (Format[]) fields.get("formats", null); //$NON-NLS-1$
0988: locale = (Locale) fields.get("locale", null); //$NON-NLS-1$
0989: maxOffset = fields.get("maxOffset", 0); //$NON-NLS-1$
0990: int[] offsets = (int[]) fields.get("offsets", null); //$NON-NLS-1$
0991: String pattern = (String) fields.get("pattern", null); //$NON-NLS-1$
0992: int length;
0993: if (maxOffset < 0) {
0994: length = pattern.length() > 0 ? 1 : 0;
0995: } else {
0996: length = maxOffset
0997: + (offsets[maxOffset] == pattern.length() ? 1 : 2);
0998: }
0999: strings = new String[length];
1000: int last = 0;
1001: for (int i = 0; i <= maxOffset; i++) {
1002: strings[i] = pattern.substring(last, offsets[i]);
1003: last = offsets[i];
1004: }
1005: if (maxOffset + 1 < strings.length) {
1006: strings[strings.length - 1] = pattern.substring(last,
1007: pattern.length());
1008: }
1009: }
1010:
1011: /**
1012: * The instances of this inner class are used as attribute keys in
1013: * AttributedCharacterIterator that
1014: * MessageFormat.formatToCharacterIterator() method returns.
1015: * <p>
1016: * There is no public constructor to this class, the only instances are the
1017: * constants defined here.
1018: */
1019: public static class Field extends Format.Field {
1020:
1021: private static final long serialVersionUID = 7899943957617360810L;
1022:
1023: public static final Field ARGUMENT = new Field(
1024: "message argument field"); //$NON-NLS-1$
1025:
1026: /**
1027: * Constructs a new instance of MessageFormat.Field with the given field
1028: * name.
1029: */
1030: protected Field(String fieldName) {
1031: super (fieldName);
1032: }
1033:
1034: /**
1035: * serialization method resolve instances to the constant
1036: * MessageFormat.Field values
1037: */
1038: @Override
1039: protected Object readResolve() throws InvalidObjectException {
1040: String name = this .getName();
1041: if (name == null) {
1042: // text.18=Not a valid {0}, subclass should override
1043: // readResolve()
1044: throw new InvalidObjectException(Messages.getString(
1045: "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$
1046: }
1047:
1048: if (name.equals(ARGUMENT.getName())) {
1049: return ARGUMENT;
1050: }
1051: // text.18=Not a valid {0}, subclass should override readResolve()
1052: throw new InvalidObjectException(Messages.getString(
1053: "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$
1054: }
1055: }
1056:
1057: }
|