0001: /* Licensed to the Apache Software Foundation (ASF) under one or more
0002: * contributor license agreements. See the NOTICE file distributed with
0003: * this work for additional information regarding copyright ownership.
0004: * The ASF licenses this file to You under the Apache License, Version 2.0
0005: * (the "License"); you may not use this file except in compliance with
0006: * the License. You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package java.util;
0017:
0018: import java.io.BufferedWriter;
0019: import java.io.Closeable;
0020: import java.io.File;
0021: import java.io.FileNotFoundException;
0022: import java.io.FileOutputStream;
0023: import java.io.Flushable;
0024: import java.io.IOException;
0025: import java.io.OutputStream;
0026: import java.io.OutputStreamWriter;
0027: import java.io.PrintStream;
0028: import java.io.UnsupportedEncodingException;
0029: import java.math.BigDecimal;
0030: import java.math.BigInteger;
0031: import java.nio.CharBuffer;
0032: import java.nio.charset.Charset;
0033: import java.security.AccessController;
0034: import java.security.PrivilegedAction;
0035: import java.text.DateFormatSymbols;
0036: import java.text.DecimalFormat;
0037: import java.text.DecimalFormatSymbols;
0038: import java.text.NumberFormat;
0039:
0040: /**
0041: * Formatter provides the method to give out formatted string just like the
0042: * printf-style. Layout,alignment and other format flags are provided to format
0043: * numeric,string and date/time as well as locale-specified formats applied.
0044: * Besides primitive types, formatter also support some java object types such
0045: * as BigInteger,BigDecimal and Calendar. Customized formatting is provided
0046: * through the Formattable interface.
0047: *
0048: * The class is not multi-threaded safe. The responsibility to maintain thread
0049: * safety is the user's job.
0050: *
0051: * @since 1.5
0052: */
0053: public final class Formatter implements Closeable, Flushable {
0054:
0055: public enum BigDecimalLayoutForm {
0056: SCIENTIFIC, DECIMAL_FLOAT
0057: }
0058:
0059: private Appendable out;
0060:
0061: private Locale locale;
0062:
0063: private boolean closed = false;
0064:
0065: private IOException lastIOException;
0066:
0067: /**
0068: * Constructs a formatter.
0069: *
0070: * The output is a StringBuilder which can be achieved by invoking the out
0071: * method and whose contents can be attained by calling the toString method.
0072: *
0073: * The locale for the formatter is the default locale of the JVM.
0074: */
0075: public Formatter() {
0076: this (new StringBuilder(), Locale.getDefault());
0077: }
0078:
0079: /**
0080: * Constructs a formatter of which the output is denoted.
0081: *
0082: * The locale for the formatter is the default locale of the JVM.
0083: *
0084: * @param a
0085: * The output of the formatter. If a is null, then a
0086: * StringBuilder will be used.
0087: */
0088: public Formatter(Appendable a) {
0089: this (a, Locale.getDefault());
0090: }
0091:
0092: /**
0093: * Constructs a formatter of which the locale is denoted.
0094: *
0095: * The output destination is a StringBuilder which can be achieved by
0096: * invoking the out method and whose contents can be attained by calling the
0097: * toString method.
0098: *
0099: * @param l
0100: * The locale of the formatter. If l is null, then no
0101: * localization will be used.
0102: */
0103: public Formatter(Locale l) {
0104: this (new StringBuilder(), l);
0105: }
0106:
0107: /**
0108: * Constructs a formatter of which the output and locale is denoted.
0109: *
0110: * @param a
0111: * The output of the formatter. If a is null, then a
0112: * StringBuilder will be used.
0113: * @param l
0114: * The locale of the formatter. If l is null, then no
0115: * localization will be used.
0116: */
0117: public Formatter(Appendable a, Locale l) {
0118: if (null == a) {
0119: out = new StringBuilder();
0120: } else {
0121: out = a;
0122: }
0123: locale = l;
0124: }
0125:
0126: /**
0127: * Constructs a formatter of which the filename is denoted.
0128: *
0129: * The charset of the formatter is the default charset of JVM.
0130: *
0131: * The locale for the formatter is the default locale of the JVM.
0132: *
0133: * @param fileName
0134: * The filename of the file that is used as the output
0135: * destination for the formatter. The file will be truncated to
0136: * zero size if the file exists, or else a new file will be
0137: * created. The output of the formatter is buffered.
0138: *
0139: * @throws FileNotFoundException
0140: * If the filename does not denote a normal and writable file,
0141: * or a new file cannot be created or any error rises when
0142: * opening or creating the file.
0143: * @throws SecurityException
0144: * If there is a security manager and it denies writing to the
0145: * file in checkWrite(file.getPath()).
0146: */
0147: public Formatter(String fileName) throws FileNotFoundException {
0148: this (new File(fileName));
0149:
0150: }
0151:
0152: /**
0153: * Constructs a formatter of which the filename and charset is denoted.
0154: *
0155: * The locale for the formatter is the default locale of the JVM.
0156: *
0157: * @param fileName
0158: * The filename of the file that is used as the output
0159: * destination for the formatter. The file will be truncated to
0160: * zero size if the file exists, or else a new file will be
0161: * created. The output of the formatter is buffered.
0162: * @param csn
0163: * The name of the charset for the formatter.
0164: *
0165: * @throws FileNotFoundException
0166: * If the filename does not denote a normal and writable file,
0167: * or a new file cannot be created or any error rises when
0168: * opening or creating the file.
0169: * @throws SecurityException
0170: * If there is a security manager and it denies writing to the
0171: * file in checkWrite(file.getPath()).
0172: * @throws UnsupportedEncodingException
0173: * If the charset with the specified name is not supported.
0174: */
0175: public Formatter(String fileName, String csn)
0176: throws FileNotFoundException, UnsupportedEncodingException {
0177: this (new File(fileName), csn);
0178: }
0179:
0180: /**
0181: * Constructs a formatter of which the filename, charset and locale is
0182: * denoted.
0183: *
0184: * @param fileName
0185: * The filename of the file that is used as the output
0186: * destination for the formatter. The file will be truncated to
0187: * zero size if the file exists, or else a new file will be
0188: * created. The output of the formatter is buffered.
0189: * @param csn
0190: * The name of the charset for the formatter.
0191: * @param l
0192: * The locale of the formatter. If l is null, then no
0193: * localization will be used.
0194: *
0195: * @throws FileNotFoundException
0196: * If the filename does not denote a normal and writable file,
0197: * or a new file cannot be created or any error rises when
0198: * opening or creating the file.
0199: * @throws SecurityException
0200: * If there is a security manager and it denies writing to the
0201: * file in checkWrite(file.getPath()).
0202: * @throws UnsupportedEncodingException
0203: * If the charset with the specified name is not supported.
0204: *
0205: */
0206: public Formatter(String fileName, String csn, Locale l)
0207: throws FileNotFoundException, UnsupportedEncodingException {
0208:
0209: this (new File(fileName), csn, l);
0210: }
0211:
0212: /**
0213: * Constructs a formatter of which the file is denoted.
0214: *
0215: * The charset of the formatter is the default charset of JVM.
0216: *
0217: * The locale for the formatter is the default locale of the JVM.
0218: *
0219: * @param file
0220: * The file that is used as the output destination for the
0221: * formatter. The file will be truncated to zero size if the file
0222: * exists, or else a new file will be created. The output of the
0223: * formatter is buffered.
0224: *
0225: * @throws FileNotFoundException
0226: * If the file does not denote a normal and writable file, or a
0227: * new file cannot be created or any error rises when opening or
0228: * creating the file.
0229: * @throws SecurityException
0230: * If there is a security manager and it denies writing to the
0231: * file in checkWrite(file.getPath()).
0232: */
0233: public Formatter(File file) throws FileNotFoundException {
0234: this (new FileOutputStream(file));
0235: }
0236:
0237: /**
0238: * Constructs a formatter of which the file and charset is denoted.
0239: *
0240: * The locale for the formatter is the default locale of the JVM.
0241: *
0242: * @param file
0243: * The file of the file that is used as the output destination
0244: * for the formatter. The file will be truncated to zero size if
0245: * the file exists, or else a new file will be created. The
0246: * output of the formatter is buffered.
0247: * @param csn
0248: * The name of the charset for the formatter.
0249: * @throws FileNotFoundException
0250: * If the file does not denote a normal and writable file, or a
0251: * new file cannot be created or any error rises when opening or
0252: * creating the file.
0253: * @throws SecurityException
0254: * If there is a security manager and it denies writing to the
0255: * file in checkWrite(file.getPath()).
0256: * @throws UnsupportedEncodingException
0257: * If the charset with the specified name is not supported.
0258: */
0259: public Formatter(File file, String csn)
0260: throws FileNotFoundException, UnsupportedEncodingException {
0261: this (file, csn, Locale.getDefault());
0262: }
0263:
0264: /**
0265: * Constructs a formatter of which the file, charset and locale is denoted.
0266: *
0267: * @param file
0268: * file that is used as the output destination for the formatter.
0269: * The file will be truncated to zero size if the file exists, or
0270: * else a new file will be created. The output of the formatter
0271: * is buffered.
0272: * @param csn
0273: * The name of the charset for the formatter.
0274: * @param l
0275: * The locale of the formatter. If l is null, then no
0276: * localization will be used.
0277: * @throws FileNotFoundException
0278: * If the file does not denote a normal and writable file, or a
0279: * new file cannot be created or any error rises when opening or
0280: * creating the file.
0281: * @throws SecurityException
0282: * If there is a security manager and it denies writing to the
0283: * file in checkWrite(file.getPath()).
0284: * @throws UnsupportedEncodingException
0285: * If the charset with the specified name is not supported.
0286: */
0287: public Formatter(File file, String csn, Locale l)
0288: throws FileNotFoundException, UnsupportedEncodingException {
0289: FileOutputStream fout = null;
0290: try {
0291: fout = new FileOutputStream(file);
0292: OutputStreamWriter writer = new OutputStreamWriter(fout,
0293: csn);
0294: out = new BufferedWriter(writer);
0295: } catch (RuntimeException e) {
0296: closeOutputStream(fout);
0297: throw e;
0298: } catch (UnsupportedEncodingException e) {
0299: closeOutputStream(fout);
0300: throw e;
0301: }
0302:
0303: locale = l;
0304: }
0305:
0306: /**
0307: * Constructs a formatter of which the output destination is specified.
0308: *
0309: * The charset of the formatter is the default charset of JVM.
0310: *
0311: * The locale for the formatter is the default locale of the JVM.
0312: *
0313: * @param os
0314: * The stream used as the destination of the formatter.
0315: */
0316: public Formatter(OutputStream os) {
0317: OutputStreamWriter writer = new OutputStreamWriter(os, Charset
0318: .defaultCharset());
0319: out = new BufferedWriter(writer);
0320: locale = Locale.getDefault();
0321: }
0322:
0323: /**
0324: * Constructs a formatter of which the output destination and the charset is
0325: * specified.
0326: *
0327: * The locale for the formatter is the default locale of the JVM.
0328: *
0329: * @param os
0330: * The stream used as the destination of the formatter.
0331: * @param csn
0332: * The name of the charset for the formatter.
0333: * @throws UnsupportedEncodingException
0334: * If the charset with the specified name is not supported.
0335: */
0336: public Formatter(OutputStream os, String csn)
0337: throws UnsupportedEncodingException {
0338:
0339: this (os, csn, Locale.getDefault());
0340: }
0341:
0342: /**
0343: * Constructs a formatter of which the output destination, the charset and
0344: * the locale is specified.
0345: *
0346: * @param os
0347: * The stream used as the destination of the formatter.
0348: * @param csn
0349: * The name of the charset for the formatter.
0350: * @param l
0351: * The locale of the formatter. If l is null, then no
0352: * localization will be used.
0353: * @throws UnsupportedEncodingException
0354: * If the charset with the specified name is not supported.
0355: */
0356: public Formatter(OutputStream os, String csn, Locale l)
0357: throws UnsupportedEncodingException {
0358:
0359: OutputStreamWriter writer = new OutputStreamWriter(os, csn);
0360: out = new BufferedWriter(writer);
0361:
0362: locale = l;
0363: }
0364:
0365: /**
0366: * Constructs a formatter of which the output destination is specified.
0367: *
0368: * The charset of the formatter is the default charset of JVM.
0369: *
0370: * The locale for the formatter is the default locale of the JVM.
0371: *
0372: * @param ps
0373: * The print stream used as destination of the formatter. If ps
0374: * is null, then NullPointerExcepiton will be thrown out.
0375: */
0376: public Formatter(PrintStream ps) {
0377: if (null == ps) {
0378: throw new NullPointerException();
0379: }
0380: out = ps;
0381: locale = Locale.getDefault();
0382: }
0383:
0384: private void checkClosed() {
0385: if (closed) {
0386: throw new FormatterClosedException();
0387: }
0388: }
0389:
0390: /**
0391: * Returns the locale of the formatter.
0392: *
0393: * @return The locale for the formatter and null for no locale.
0394: * @throws FormatterClosedException
0395: * If the formatter has been closed.
0396: */
0397: public Locale locale() {
0398: checkClosed();
0399: return locale;
0400: }
0401:
0402: /**
0403: * Returns the output destination of the formatter.
0404: *
0405: * @return The output destination of the formatter.
0406: * @throws FormatterClosedException
0407: * If the formatter has been closed.
0408: */
0409: public Appendable out() {
0410: checkClosed();
0411: return out;
0412: }
0413:
0414: /**
0415: * Returns the content by calling the toString() method of the output
0416: * destination.
0417: *
0418: * @return The content by calling the toString() method of the output
0419: * destination.
0420: * @throws FormatterClosedException
0421: * If the formatter has been closed.
0422: */
0423: @Override
0424: public String toString() {
0425: checkClosed();
0426: return out.toString();
0427: }
0428:
0429: /**
0430: * Flushes the formatter. If the output destination is {@link Flushable},
0431: * then the method flush() will be called on that destination.
0432: *
0433: * @throws FormatterClosedException
0434: * If the formatter has been closed.
0435: */
0436: public void flush() {
0437: checkClosed();
0438: if (out instanceof Flushable) {
0439: try {
0440: ((Flushable) out).flush();
0441: } catch (IOException e) {
0442: lastIOException = e;
0443: }
0444: }
0445: }
0446:
0447: /**
0448: * Closes the formatter. If the output destination is {@link Closeable},
0449: * then the method close() will be called on that destination.
0450: *
0451: * If the formatter has been closed, then calling the close will have no
0452: * effect.
0453: *
0454: * Any method but the ioException() that is called after the formatter has
0455: * been closed will raise a FormatterClosedException.
0456: */
0457: public void close() {
0458: closed = true;
0459: try {
0460: if (out instanceof Closeable) {
0461: ((Closeable) out).close();
0462: }
0463: } catch (IOException e) {
0464:
0465: lastIOException = e;
0466: }
0467: }
0468:
0469: /**
0470: * Returns the last IOException thrown out by the formatter's output
0471: * destination. If the append() method of the destination will not throw
0472: * IOException, the ioException() method will always return null.
0473: *
0474: * @return The last IOException thrown out by the formatter's output
0475: * destination.
0476: */
0477: public IOException ioException() {
0478: return lastIOException;
0479: }
0480:
0481: /**
0482: * Writes a formatted string to the output destination of the formatter.
0483: *
0484: * @param format
0485: * A format string.
0486: * @param args
0487: * The arguments list used in the format() method. If there are
0488: * more arguments than those specified by the format string, then
0489: * the additional arguments are ignored.
0490: * @return This formatter.
0491: * @throws IllegalFormatException
0492: * If the format string is illegal or incompatible with the
0493: * arguments or the arguments are less than those required by
0494: * the format string or any other illegal situation.
0495: * @throws FormatterClosedException
0496: * If the formatter has been closed.
0497: */
0498: public Formatter format(String format, Object... args) {
0499: return format(locale, format, args);
0500: }
0501:
0502: /**
0503: * Writes a formatted string to the output destination of the formatter.
0504: *
0505: * @param l
0506: * The locale used in the method. If locale is null, then no
0507: * localization will be applied. This parameter does not
0508: * influence the locale specified during construction.
0509: * @param format
0510: * A format string.
0511: * @param args
0512: * The arguments list used in the format() method. If there are
0513: * more arguments than those specified by the format string, then
0514: * the additional arguments are ignored.
0515: * @return This formatter.
0516: * @throws IllegalFormatException
0517: * If the format string is illegal or incompatible with the
0518: * arguments or the arguments are less than those required by
0519: * the format string or any other illegal situation.
0520: * @throws FormatterClosedException
0521: * If the formatter has been closed.
0522: */
0523: public Formatter format(Locale l, String format, Object... args) {
0524: checkClosed();
0525: CharBuffer formatBuffer = CharBuffer.wrap(format);
0526: ParserStateMachine parser = new ParserStateMachine(formatBuffer);
0527: Transformer transformer = new Transformer(this , l);
0528:
0529: int currentObjectIndex = 0;
0530: Object lastArgument = null;
0531: boolean hasLastArgumentSet = false;
0532: while (formatBuffer.hasRemaining()) {
0533: parser.reset();
0534: FormatToken token = parser.getNextFormatToken();
0535: String result;
0536: String plainText = token.getPlainText();
0537: if (token.getConversionType() == (char) FormatToken.UNSET) {
0538: result = plainText;
0539: } else {
0540: plainText = plainText.substring(0, plainText
0541: .indexOf('%'));
0542: Object argument = null;
0543: if (token.requireArgument()) {
0544: int index = token.getArgIndex() == FormatToken.UNSET ? currentObjectIndex++
0545: : token.getArgIndex();
0546: argument = getArgument(args, index, token,
0547: lastArgument, hasLastArgumentSet);
0548: lastArgument = argument;
0549: hasLastArgumentSet = true;
0550: }
0551: result = transformer.transform(token, argument);
0552: result = (null == result ? plainText : plainText
0553: + result);
0554: }
0555: // if output is made by formattable callback
0556: if (null != result) {
0557: try {
0558: out.append(result);
0559: } catch (IOException e) {
0560: lastIOException = e;
0561: }
0562: }
0563: }
0564: return this ;
0565: }
0566:
0567: private Object getArgument(Object[] args, int index,
0568: FormatToken token, Object lastArgument,
0569: boolean hasLastArgumentSet) {
0570: if (index == FormatToken.LAST_ARGUMENT_INDEX
0571: && !hasLastArgumentSet) {
0572: throw new MissingFormatArgumentException("<"); //$NON-NLS-1$
0573: }
0574:
0575: if (null == args) {
0576: return null;
0577: }
0578:
0579: if (index >= args.length) {
0580: throw new MissingFormatArgumentException(token
0581: .getPlainText());
0582: }
0583:
0584: if (index == FormatToken.LAST_ARGUMENT_INDEX) {
0585: return lastArgument;
0586: }
0587:
0588: return args[index];
0589: }
0590:
0591: private static void closeOutputStream(OutputStream os) {
0592: if (null == os) {
0593: return;
0594: }
0595: try {
0596: os.close();
0597:
0598: } catch (IOException e) {
0599: // silently
0600: }
0601: }
0602:
0603: /*
0604: * Information about the format string of a specified argument, which
0605: * includes the conversion type, flags, width, precision and the argument
0606: * index as well as the plainText that contains the whole format string used
0607: * as the result for output if necessary. Besides, the string for flags is
0608: * recorded to construct corresponding FormatExceptions if necessary.
0609: */
0610: private static class FormatToken {
0611:
0612: static final int LAST_ARGUMENT_INDEX = -2;
0613:
0614: static final int UNSET = -1;
0615:
0616: static final int FLAGS_UNSET = 0;
0617:
0618: static final int DEFAULT_PRECISION = 6;
0619:
0620: static final int FLAG_MINUS = 1;
0621:
0622: static final int FLAG_SHARP = 1 << 1;
0623:
0624: static final int FLAG_ADD = 1 << 2;
0625:
0626: static final int FLAG_SPACE = 1 << 3;
0627:
0628: static final int FLAG_ZERO = 1 << 4;
0629:
0630: static final int FLAG_COMMA = 1 << 5;
0631:
0632: static final int FLAG_PARENTHESIS = 1 << 6;
0633:
0634: private static final int FLAGT_TYPE_COUNT = 6;
0635:
0636: private int formatStringStartIndex;
0637:
0638: private String plainText;
0639:
0640: private int argIndex = UNSET;
0641:
0642: private int flags = 0;
0643:
0644: private int width = UNSET;
0645:
0646: private int precision = UNSET;
0647:
0648: private StringBuilder strFlags = new StringBuilder(
0649: FLAGT_TYPE_COUNT);
0650:
0651: private char dateSuffix;// will be used in new feature.
0652:
0653: private char conversionType = (char) UNSET;
0654:
0655: boolean isPrecisionSet() {
0656: return precision != UNSET;
0657: }
0658:
0659: boolean isWidthSet() {
0660: return width != UNSET;
0661: }
0662:
0663: boolean isFlagSet(int flag) {
0664: return 0 != (flags & flag);
0665: }
0666:
0667: int getArgIndex() {
0668: return argIndex;
0669: }
0670:
0671: void setArgIndex(int index) {
0672: argIndex = index;
0673: }
0674:
0675: String getPlainText() {
0676: return plainText;
0677: }
0678:
0679: void setPlainText(String plainText) {
0680: this .plainText = plainText;
0681: }
0682:
0683: int getWidth() {
0684: return width;
0685: }
0686:
0687: void setWidth(int width) {
0688: this .width = width;
0689: }
0690:
0691: int getPrecision() {
0692: return precision;
0693: }
0694:
0695: void setPrecision(int precise) {
0696: this .precision = precise;
0697: }
0698:
0699: String getStrFlags() {
0700: return strFlags.toString();
0701: }
0702:
0703: int getFlags() {
0704: return flags;
0705: }
0706:
0707: void setFlags(int flags) {
0708: this .flags = flags;
0709: }
0710:
0711: /*
0712: * Sets qualified char as one of the flags. If the char is qualified,
0713: * sets it as a flag and returns true. Or else returns false.
0714: */
0715: boolean setFlag(char c) {
0716: int newFlag;
0717: switch (c) {
0718: case '-': {
0719: newFlag = FLAG_MINUS;
0720: break;
0721: }
0722: case '#': {
0723: newFlag = FLAG_SHARP;
0724: break;
0725: }
0726: case '+': {
0727: newFlag = FLAG_ADD;
0728: break;
0729: }
0730: case ' ': {
0731: newFlag = FLAG_SPACE;
0732: break;
0733: }
0734: case '0': {
0735: newFlag = FLAG_ZERO;
0736: break;
0737: }
0738: case ',': {
0739: newFlag = FLAG_COMMA;
0740: break;
0741: }
0742: case '(': {
0743: newFlag = FLAG_PARENTHESIS;
0744: break;
0745: }
0746: default:
0747: return false;
0748: }
0749: if (0 != (flags & newFlag)) {
0750: throw new DuplicateFormatFlagsException(String
0751: .valueOf(c));
0752: }
0753: flags = (flags | newFlag);
0754: strFlags.append(c);
0755: return true;
0756:
0757: }
0758:
0759: int getFormatStringStartIndex() {
0760: return formatStringStartIndex;
0761: }
0762:
0763: void setFormatStringStartIndex(int index) {
0764: formatStringStartIndex = index;
0765: }
0766:
0767: char getConversionType() {
0768: return conversionType;
0769: }
0770:
0771: void setConversionType(char c) {
0772: conversionType = c;
0773: }
0774:
0775: char getDateSuffix() {
0776: return dateSuffix;
0777: }
0778:
0779: void setDateSuffix(char c) {
0780: dateSuffix = c;
0781: }
0782:
0783: boolean requireArgument() {
0784: return conversionType != '%' && conversionType != 'n';
0785: }
0786: }
0787:
0788: /*
0789: * Transforms the argument to the formatted string according to the format
0790: * information contained in the format token.
0791: */
0792: private static class Transformer {
0793:
0794: private Formatter formatter;
0795:
0796: private FormatToken formatToken;
0797:
0798: private Object arg;
0799:
0800: private Locale locale;
0801:
0802: private static String lineSeparator;
0803:
0804: private NumberFormat numberFormat;
0805:
0806: private DecimalFormatSymbols decimalFormatSymbols;
0807:
0808: private DateTimeUtil dateTimeUtil;
0809:
0810: Transformer(Formatter formatter, Locale locale) {
0811: this .formatter = formatter;
0812: this .locale = (null == locale ? Locale.US : locale);
0813: }
0814:
0815: private NumberFormat getNumberFormat() {
0816: if (null == numberFormat) {
0817: numberFormat = NumberFormat.getInstance(locale);
0818: }
0819: return numberFormat;
0820: }
0821:
0822: private DecimalFormatSymbols getDecimalFormatSymbols() {
0823: if (null == decimalFormatSymbols) {
0824: decimalFormatSymbols = new DecimalFormatSymbols(locale);
0825: }
0826: return decimalFormatSymbols;
0827: }
0828:
0829: /*
0830: * Gets the formatted string according to the format token and the
0831: * argument.
0832: */
0833: String transform(FormatToken token, Object argument) {
0834:
0835: /* init data member to print */
0836: this .formatToken = token;
0837: this .arg = argument;
0838:
0839: String result;
0840: switch (token.getConversionType()) {
0841: case 'B':
0842: case 'b': {
0843: result = transformFromBoolean();
0844: break;
0845: }
0846: case 'H':
0847: case 'h': {
0848: result = transformFromHashCode();
0849: break;
0850: }
0851: case 'S':
0852: case 's': {
0853: result = transformFromString();
0854: break;
0855: }
0856: case 'C':
0857: case 'c': {
0858: result = transformFromCharacter();
0859: break;
0860: }
0861: case 'd':
0862: case 'o':
0863: case 'x':
0864: case 'X': {
0865: if (null == arg || arg instanceof BigInteger) {
0866: result = transformFromBigInteger();
0867: } else {
0868: result = transformFromInteger();
0869: }
0870: break;
0871: }
0872: case 'e':
0873: case 'E':
0874: case 'g':
0875: case 'G':
0876: case 'f':
0877: case 'a':
0878: case 'A': {
0879: result = transformFromFloat();
0880: break;
0881: }
0882: case '%': {
0883: result = transformFromPercent();
0884: break;
0885: }
0886: case 'n': {
0887: result = transformFromLineSeparator();
0888: break;
0889: }
0890: case 't':
0891: case 'T': {
0892: result = transformFromDateTime();
0893: break;
0894: }
0895: default: {
0896: throw new UnknownFormatConversionException(String
0897: .valueOf(token.getConversionType()));
0898: }
0899: }
0900:
0901: if (Character.isUpperCase(token.getConversionType())) {
0902: if (null != result) {
0903: result = result.toUpperCase(Locale.US);
0904: }
0905: }
0906: return result;
0907: }
0908:
0909: /*
0910: * Transforms the Boolean argument to a formatted string.
0911: */
0912: private String transformFromBoolean() {
0913: StringBuilder result = new StringBuilder();
0914: int startIndex = 0;
0915: int flags = formatToken.getFlags();
0916:
0917: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
0918: && !formatToken.isWidthSet()) {
0919: throw new MissingFormatWidthException("-" //$NON-NLS-1$
0920: + formatToken.getConversionType());
0921: }
0922:
0923: // only '-' is valid for flags
0924: if (FormatToken.FLAGS_UNSET != flags
0925: && FormatToken.FLAG_MINUS != flags) {
0926: throw new FormatFlagsConversionMismatchException(
0927: formatToken.getStrFlags(), formatToken
0928: .getConversionType());
0929: }
0930:
0931: if (null == arg) {
0932: result.append("false"); //$NON-NLS-1$
0933: } else if (arg instanceof Boolean) {
0934: result.append(arg);
0935: } else {
0936: result.append("true"); //$NON-NLS-1$
0937: }
0938: return padding(result, startIndex);
0939: }
0940:
0941: /*
0942: * Transforms the hashcode of the argument to a formatted string.
0943: */
0944: private String transformFromHashCode() {
0945: StringBuilder result = new StringBuilder();
0946:
0947: int startIndex = 0;
0948: int flags = formatToken.getFlags();
0949:
0950: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
0951: && !formatToken.isWidthSet()) {
0952: throw new MissingFormatWidthException("-" //$NON-NLS-1$
0953: + formatToken.getConversionType());
0954: }
0955:
0956: // only '-' is valid for flags
0957: if (FormatToken.FLAGS_UNSET != flags
0958: && FormatToken.FLAG_MINUS != flags) {
0959: throw new FormatFlagsConversionMismatchException(
0960: formatToken.getStrFlags(), formatToken
0961: .getConversionType());
0962: }
0963:
0964: if (null == arg) {
0965: result.append("null"); //$NON-NLS-1$
0966: } else {
0967: result.append(Integer.toHexString(arg.hashCode()));
0968: }
0969: return padding(result, startIndex);
0970: }
0971:
0972: /*
0973: * Transforms the String to a formatted string.
0974: */
0975: private String transformFromString() {
0976: StringBuilder result = new StringBuilder();
0977: int startIndex = 0;
0978: int flags = formatToken.getFlags();
0979:
0980: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
0981: && !formatToken.isWidthSet()) {
0982: throw new MissingFormatWidthException("-" //$NON-NLS-1$
0983: + formatToken.getConversionType());
0984: }
0985:
0986: if (arg instanceof Formattable) {
0987: int flag = 0;
0988: // only minus and sharp flag is valid
0989: if (FormatToken.FLAGS_UNSET != (flags
0990: & ~FormatToken.FLAG_MINUS & ~FormatToken.FLAG_SHARP)) {
0991: throw new IllegalFormatFlagsException(formatToken
0992: .getStrFlags());
0993: }
0994: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)) {
0995: flag |= FormattableFlags.LEFT_JUSTIFY;
0996: }
0997: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)) {
0998: flag |= FormattableFlags.ALTERNATE;
0999: }
1000: if (Character.isUpperCase(formatToken
1001: .getConversionType())) {
1002: flag |= FormattableFlags.UPPERCASE;
1003: }
1004: ((Formattable) arg).formatTo(formatter, flag,
1005: formatToken.getWidth(), formatToken
1006: .getPrecision());
1007: // all actions have been taken out in the
1008: // Formattable.formatTo, thus there is nothing to do, just
1009: // returns null, which tells the Parser to add nothing to the
1010: // output.
1011: return null;
1012: }
1013: // only '-' is valid for flags if the argument is not an
1014: // instance of Formattable
1015: if (FormatToken.FLAGS_UNSET != flags
1016: && FormatToken.FLAG_MINUS != flags) {
1017: throw new FormatFlagsConversionMismatchException(
1018: formatToken.getStrFlags(), formatToken
1019: .getConversionType());
1020: }
1021:
1022: result.append(arg);
1023: return padding(result, startIndex);
1024: }
1025:
1026: /*
1027: * Transforms the Character to a formatted string.
1028: */
1029: private String transformFromCharacter() {
1030: StringBuilder result = new StringBuilder();
1031:
1032: int startIndex = 0;
1033: int flags = formatToken.getFlags();
1034:
1035: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1036: && !formatToken.isWidthSet()) {
1037: throw new MissingFormatWidthException("-" //$NON-NLS-1$
1038: + formatToken.getConversionType());
1039: }
1040:
1041: // only '-' is valid for flags
1042: if (FormatToken.FLAGS_UNSET != flags
1043: && FormatToken.FLAG_MINUS != flags) {
1044: throw new FormatFlagsConversionMismatchException(
1045: formatToken.getStrFlags(), formatToken
1046: .getConversionType());
1047: }
1048:
1049: if (formatToken.isPrecisionSet()) {
1050: throw new IllegalFormatPrecisionException(formatToken
1051: .getPrecision());
1052: }
1053:
1054: if (null == arg) {
1055: result.append("null"); //$NON-NLS-1$
1056: } else {
1057: if (arg instanceof Character) {
1058: result.append(arg);
1059: } else if (arg instanceof Byte) {
1060: byte b = ((Byte) arg).byteValue();
1061: if (!Character.isValidCodePoint(b)) {
1062: throw new IllegalFormatCodePointException(b);
1063: }
1064: result.append((char) b);
1065: } else if (arg instanceof Short) {
1066: short s = ((Short) arg).shortValue();
1067: if (!Character.isValidCodePoint(s)) {
1068: throw new IllegalFormatCodePointException(s);
1069: }
1070: result.append((char) s);
1071: } else if (arg instanceof Integer) {
1072: int codePoint = ((Integer) arg).intValue();
1073: if (!Character.isValidCodePoint(codePoint)) {
1074: throw new IllegalFormatCodePointException(
1075: codePoint);
1076: }
1077: result.append(String.valueOf(Character
1078: .toChars(codePoint)));
1079: } else {
1080: // argument of other class is not acceptable.
1081: throw new IllegalFormatConversionException(
1082: formatToken.getConversionType(), arg
1083: .getClass());
1084: }
1085: }
1086: return padding(result, startIndex);
1087: }
1088:
1089: /*
1090: * Transforms percent to a formatted string. Only '-' is legal flag.
1091: * Precision is illegal.
1092: */
1093: private String transformFromPercent() {
1094: StringBuilder result = new StringBuilder("%"); //$NON-NLS-1$
1095:
1096: int startIndex = 0;
1097: int flags = formatToken.getFlags();
1098:
1099: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1100: && !formatToken.isWidthSet()) {
1101: throw new MissingFormatWidthException("-" //$NON-NLS-1$
1102: + formatToken.getConversionType());
1103: }
1104:
1105: if (FormatToken.FLAGS_UNSET != flags
1106: && FormatToken.FLAG_MINUS != flags) {
1107: throw new FormatFlagsConversionMismatchException(
1108: formatToken.getStrFlags(), formatToken
1109: .getConversionType());
1110: }
1111: if (formatToken.isPrecisionSet()) {
1112: throw new IllegalFormatPrecisionException(formatToken
1113: .getPrecision());
1114: }
1115: return padding(result, startIndex);
1116: }
1117:
1118: /*
1119: * Transforms line separator to a formatted string. Any flag, the width
1120: * or the precision is illegal.
1121: */
1122: private String transformFromLineSeparator() {
1123: if (formatToken.isPrecisionSet()) {
1124: throw new IllegalFormatPrecisionException(formatToken
1125: .getPrecision());
1126: }
1127:
1128: if (formatToken.isWidthSet()) {
1129: throw new IllegalFormatWidthException(formatToken
1130: .getWidth());
1131: }
1132:
1133: int flags = formatToken.getFlags();
1134: if (FormatToken.FLAGS_UNSET != flags) {
1135: throw new IllegalFormatFlagsException(formatToken
1136: .getStrFlags());
1137: }
1138:
1139: if (null == lineSeparator) {
1140: lineSeparator = AccessController
1141: .doPrivileged(new PrivilegedAction<String>() {
1142:
1143: public String run() {
1144: return System
1145: .getProperty("line.separator"); //$NON-NLS-1$
1146: }
1147: });
1148: }
1149: return lineSeparator;
1150: }
1151:
1152: /*
1153: * Pads characters to the formatted string.
1154: */
1155: private String padding(StringBuilder source, int startIndex) {
1156: int start = startIndex;
1157: boolean paddingRight = formatToken
1158: .isFlagSet(FormatToken.FLAG_MINUS);
1159: char paddingChar = '\u0020';// space as padding char.
1160: if (formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1161: if ('d' == formatToken.getConversionType()) {
1162: paddingChar = getDecimalFormatSymbols()
1163: .getZeroDigit();
1164: } else {
1165: paddingChar = '0';
1166: }
1167: } else {
1168: // if padding char is space, always padding from the head
1169: // location.
1170: start = 0;
1171: }
1172: int width = formatToken.getWidth();
1173: int precision = formatToken.getPrecision();
1174:
1175: int length = source.length();
1176: if (precision >= 0) {
1177: length = Math.min(length, precision);
1178: source.delete(length, source.length());
1179: }
1180: if (width > 0) {
1181: width = Math.max(source.length(), width);
1182: }
1183: if (length >= width) {
1184: return source.toString();
1185: }
1186:
1187: char[] paddings = new char[width - length];
1188: Arrays.fill(paddings, paddingChar);
1189: String insertString = new String(paddings);
1190:
1191: if (paddingRight) {
1192: source.append(insertString);
1193: } else {
1194: source.insert(start, insertString);
1195: }
1196: return source.toString();
1197: }
1198:
1199: /*
1200: * Transforms the Integer to a formatted string.
1201: */
1202: private String transformFromInteger() {
1203: int startIndex = 0;
1204: boolean isNegative = false;
1205: StringBuilder result = new StringBuilder();
1206: char currentConversionType = formatToken
1207: .getConversionType();
1208: long value;
1209:
1210: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1211: || formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1212: if (!formatToken.isWidthSet()) {
1213: throw new MissingFormatWidthException(formatToken
1214: .getStrFlags());
1215: }
1216: }
1217: // Combination of '+' & ' ' is illegal.
1218: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)
1219: && formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1220: throw new IllegalFormatFlagsException(formatToken
1221: .getStrFlags());
1222: }
1223: if (formatToken.isPrecisionSet()) {
1224: throw new IllegalFormatPrecisionException(formatToken
1225: .getPrecision());
1226: }
1227: if (arg instanceof Long) {
1228: value = ((Long) arg).longValue();
1229: } else if (arg instanceof Integer) {
1230: value = ((Integer) arg).longValue();
1231: } else if (arg instanceof Short) {
1232: value = ((Short) arg).longValue();
1233: } else if (arg instanceof Byte) {
1234: value = ((Byte) arg).longValue();
1235: } else {
1236: throw new IllegalFormatConversionException(formatToken
1237: .getConversionType(), arg.getClass());
1238: }
1239: if ('d' != currentConversionType) {
1240: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)
1241: || formatToken
1242: .isFlagSet(FormatToken.FLAG_SPACE)
1243: || formatToken
1244: .isFlagSet(FormatToken.FLAG_COMMA)
1245: || formatToken
1246: .isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1247: throw new FormatFlagsConversionMismatchException(
1248: formatToken.getStrFlags(), formatToken
1249: .getConversionType());
1250: }
1251: }
1252:
1253: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)) {
1254: if ('d' == currentConversionType) {
1255: throw new FormatFlagsConversionMismatchException(
1256: formatToken.getStrFlags(), formatToken
1257: .getConversionType());
1258: } else if ('o' == currentConversionType) {
1259: result.append("0"); //$NON-NLS-1$
1260: startIndex += 1;
1261: } else {
1262: result.append("0x"); //$NON-NLS-1$
1263: startIndex += 2;
1264: }
1265: }
1266:
1267: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1268: && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1269: throw new IllegalFormatFlagsException(formatToken
1270: .getStrFlags());
1271: }
1272:
1273: if (value < 0) {
1274: isNegative = true;
1275: }
1276:
1277: if ('d' == currentConversionType) {
1278: NumberFormat numberFormat = getNumberFormat();
1279: if (formatToken.isFlagSet(FormatToken.FLAG_COMMA)) {
1280: numberFormat.setGroupingUsed(true);
1281: } else {
1282: numberFormat.setGroupingUsed(false);
1283: }
1284: result.append(numberFormat.format(arg));
1285: } else {
1286: long BYTE_MASK = 0x00000000000000FFL;
1287: long SHORT_MASK = 0x000000000000FFFFL;
1288: long INT_MASK = 0x00000000FFFFFFFFL;
1289: if (isNegative) {
1290: if (arg instanceof Byte) {
1291: value &= BYTE_MASK;
1292: } else if (arg instanceof Short) {
1293: value &= SHORT_MASK;
1294: } else if (arg instanceof Integer) {
1295: value &= INT_MASK;
1296: }
1297: }
1298: if ('o' == currentConversionType) {
1299: result.append(Long.toOctalString(value));
1300: } else {
1301: result.append(Long.toHexString(value));
1302: }
1303: isNegative = false;
1304: }
1305:
1306: if (!isNegative) {
1307: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)) {
1308: result.insert(0, '+');
1309: startIndex += 1;
1310: }
1311: if (formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1312: result.insert(0, ' ');
1313: startIndex += 1;
1314: }
1315: }
1316:
1317: /* pad paddingChar to the output */
1318: if (isNegative
1319: && formatToken
1320: .isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1321: result = wrapParentheses(result);
1322: return result.toString();
1323:
1324: }
1325: if (isNegative
1326: && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1327: startIndex++;
1328: }
1329: return padding(result, startIndex);
1330: }
1331:
1332: /*
1333: * add () to the output,if the value is negative and
1334: * formatToken.FLAG_PARENTHESIS is set. 'result' is used as an in-out
1335: * parameter.
1336: */
1337: private StringBuilder wrapParentheses(StringBuilder result) {
1338: // delete the '-'
1339: result.deleteCharAt(0);
1340: result.insert(0, '(');
1341: if (formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1342: formatToken.setWidth(formatToken.getWidth() - 1);
1343: padding(result, 1);
1344: result.append(')');
1345: } else {
1346: result.append(')');
1347: padding(result, 0);
1348: }
1349: return result;
1350: }
1351:
1352: private String transformFromSpecialNumber() {
1353: String source = null;
1354:
1355: if (!(arg instanceof Number) || arg instanceof BigDecimal) {
1356: return null;
1357: }
1358:
1359: Number number = (Number) arg;
1360: double d = number.doubleValue();
1361: if (Double.isNaN(d)) {
1362: source = "NaN"; //$NON-NLS-1$
1363: } else if (Double.isInfinite(d)) {
1364: if (d >= 0) {
1365: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)) {
1366: source = "+Infinity"; //$NON-NLS-1$
1367: } else if (formatToken
1368: .isFlagSet(FormatToken.FLAG_SPACE)) {
1369: source = " Infinity"; //$NON-NLS-1$
1370: } else {
1371: source = "Infinity"; //$NON-NLS-1$
1372: }
1373: } else {
1374: if (formatToken
1375: .isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1376: source = "(Infinity)"; //$NON-NLS-1$
1377: } else {
1378: source = "-Infinity"; //$NON-NLS-1$
1379: }
1380: }
1381: }
1382:
1383: if (null != source) {
1384: formatToken.setPrecision(FormatToken.UNSET);
1385: formatToken.setFlags(formatToken.getFlags()
1386: & (~FormatToken.FLAG_ZERO));
1387: source = padding(new StringBuilder(source), 0);
1388: }
1389: return source;
1390: }
1391:
1392: private String transformFromNull() {
1393: formatToken.setFlags(formatToken.getFlags()
1394: & (~FormatToken.FLAG_ZERO));
1395: return padding(new StringBuilder("null"), 0); //$NON-NLS-1$
1396: }
1397:
1398: /*
1399: * Transforms a BigInteger to a formatted string.
1400: */
1401: private String transformFromBigInteger() {
1402: int startIndex = 0;
1403: boolean isNegative = false;
1404: StringBuilder result = new StringBuilder();
1405: BigInteger bigInt = (BigInteger) arg;
1406: char currentConversionType = formatToken
1407: .getConversionType();
1408:
1409: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1410: || formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1411: if (!formatToken.isWidthSet()) {
1412: throw new MissingFormatWidthException(formatToken
1413: .getStrFlags());
1414: }
1415: }
1416:
1417: // Combination of '+' & ' ' is illegal.
1418: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)
1419: && formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1420: throw new IllegalFormatFlagsException(formatToken
1421: .getStrFlags());
1422: }
1423:
1424: // Combination of '-' & '0' is illegal.
1425: if (formatToken.isFlagSet(FormatToken.FLAG_ZERO)
1426: && formatToken.isFlagSet(FormatToken.FLAG_MINUS)) {
1427: throw new IllegalFormatFlagsException(formatToken
1428: .getStrFlags());
1429: }
1430:
1431: if (formatToken.isPrecisionSet()) {
1432: throw new IllegalFormatPrecisionException(formatToken
1433: .getPrecision());
1434: }
1435:
1436: if ('d' != currentConversionType
1437: && formatToken.isFlagSet(FormatToken.FLAG_COMMA)) {
1438: throw new FormatFlagsConversionMismatchException(
1439: formatToken.getStrFlags(),
1440: currentConversionType);
1441: }
1442:
1443: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)
1444: && 'd' == currentConversionType) {
1445: throw new FormatFlagsConversionMismatchException(
1446: formatToken.getStrFlags(),
1447: currentConversionType);
1448: }
1449:
1450: if (null == bigInt) {
1451: return transformFromNull();
1452: }
1453:
1454: isNegative = (bigInt.compareTo(BigInteger.ZERO) < 0);
1455:
1456: if ('d' == currentConversionType) {
1457: NumberFormat numberFormat = getNumberFormat();
1458: boolean readableName = formatToken
1459: .isFlagSet(FormatToken.FLAG_COMMA);
1460: numberFormat.setGroupingUsed(readableName);
1461: result.append(numberFormat.format(bigInt));
1462: } else if ('o' == currentConversionType) {
1463: // convert BigInteger to a string presentation using radix 8
1464: result.append(bigInt.toString(8));
1465: } else {
1466: // convert BigInteger to a string presentation using radix 16
1467: result.append(bigInt.toString(16));
1468: }
1469: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)) {
1470: startIndex = isNegative ? 1 : 0;
1471: if ('o' == currentConversionType) {
1472: result.insert(startIndex, "0"); //$NON-NLS-1$
1473: startIndex += 1;
1474: } else if ('x' == currentConversionType
1475: || 'X' == currentConversionType) {
1476: result.insert(startIndex, "0x"); //$NON-NLS-1$
1477: startIndex += 2;
1478: }
1479: }
1480:
1481: if (!isNegative) {
1482: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)) {
1483: result.insert(0, '+');
1484: startIndex += 1;
1485: }
1486: if (formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1487: result.insert(0, ' ');
1488: startIndex += 1;
1489: }
1490: }
1491:
1492: /* pad paddingChar to the output */
1493: if (isNegative
1494: && formatToken
1495: .isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1496: result = wrapParentheses(result);
1497: return result.toString();
1498:
1499: }
1500: if (isNegative
1501: && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1502: startIndex++;
1503: }
1504: return padding(result, startIndex);
1505: }
1506:
1507: /*
1508: * Transforms a Float,Double or BigDecimal to a formatted string.
1509: */
1510: private String transformFromFloat() {
1511: StringBuilder result = new StringBuilder();
1512: int startIndex = 0;
1513: char currentConversionType = formatToken
1514: .getConversionType();
1515:
1516: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS
1517: | FormatToken.FLAG_ZERO)) {
1518: if (!formatToken.isWidthSet()) {
1519: throw new MissingFormatWidthException(formatToken
1520: .getStrFlags());
1521: }
1522: }
1523:
1524: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)
1525: && formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1526: throw new IllegalFormatFlagsException(formatToken
1527: .getStrFlags());
1528: }
1529:
1530: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1531: && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
1532: throw new IllegalFormatFlagsException(formatToken
1533: .getStrFlags());
1534: }
1535:
1536: if ('e' == Character.toLowerCase(currentConversionType)) {
1537: if (formatToken.isFlagSet(FormatToken.FLAG_COMMA)) {
1538: throw new FormatFlagsConversionMismatchException(
1539: formatToken.getStrFlags(),
1540: currentConversionType);
1541: }
1542: }
1543:
1544: if ('g' == Character.toLowerCase(currentConversionType)) {
1545: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)) {
1546: throw new FormatFlagsConversionMismatchException(
1547: formatToken.getStrFlags(),
1548: currentConversionType);
1549: }
1550: }
1551:
1552: if ('a' == Character.toLowerCase(currentConversionType)) {
1553: if (formatToken.isFlagSet(FormatToken.FLAG_COMMA)
1554: || formatToken
1555: .isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1556: throw new FormatFlagsConversionMismatchException(
1557: formatToken.getStrFlags(),
1558: currentConversionType);
1559: }
1560: }
1561:
1562: if (null == arg) {
1563: return transformFromNull();
1564: }
1565:
1566: if (!(arg instanceof Float || arg instanceof Double || arg instanceof BigDecimal)) {
1567: throw new IllegalFormatConversionException(
1568: currentConversionType, arg.getClass());
1569: }
1570:
1571: String specialNumberResult = transformFromSpecialNumber();
1572: if (null != specialNumberResult) {
1573: return specialNumberResult;
1574: }
1575:
1576: if ('a' != Character.toLowerCase(currentConversionType)) {
1577: formatToken
1578: .setPrecision(formatToken.isPrecisionSet() ? formatToken
1579: .getPrecision()
1580: : FormatToken.DEFAULT_PRECISION);
1581: }
1582: // output result
1583: FloatUtil floatUtil = new FloatUtil(result, formatToken,
1584: (DecimalFormat) NumberFormat.getInstance(locale),
1585: arg);
1586: floatUtil.transform(formatToken, result);
1587:
1588: formatToken.setPrecision(FormatToken.UNSET);
1589:
1590: if (getDecimalFormatSymbols().getMinusSign() == result
1591: .charAt(0)) {
1592: if (formatToken.isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
1593: result = wrapParentheses(result);
1594: return result.toString();
1595: }
1596: } else {
1597: if (formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
1598: result.insert(0, ' ');
1599: startIndex++;
1600: }
1601: if (formatToken.isFlagSet(FormatToken.FLAG_ADD)) {
1602: result.insert(0, floatUtil.getAddSign());
1603: startIndex++;
1604: }
1605: }
1606:
1607: char firstChar = result.charAt(0);
1608: if (formatToken.isFlagSet(FormatToken.FLAG_ZERO)
1609: && (firstChar == floatUtil.getAddSign() || firstChar == floatUtil
1610: .getMinusSign())) {
1611: startIndex = 1;
1612: }
1613:
1614: if ('a' == Character.toLowerCase(currentConversionType)) {
1615: startIndex += 2;
1616: }
1617: return padding(result, startIndex);
1618: }
1619:
1620: /*
1621: * Transforms a Date to a formatted string.
1622: */
1623: private String transformFromDateTime() {
1624: int startIndex = 0;
1625: char currentConversionType = formatToken
1626: .getConversionType();
1627:
1628: if (formatToken.isPrecisionSet()) {
1629: throw new IllegalFormatPrecisionException(formatToken
1630: .getPrecision());
1631: }
1632:
1633: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)) {
1634: throw new FormatFlagsConversionMismatchException(
1635: formatToken.getStrFlags(),
1636: currentConversionType);
1637: }
1638:
1639: if (formatToken.isFlagSet(FormatToken.FLAG_MINUS)
1640: && FormatToken.UNSET == formatToken.getWidth()) {
1641: throw new MissingFormatWidthException("-" //$NON-NLS-1$
1642: + currentConversionType);
1643: }
1644:
1645: if (null == arg) {
1646: return transformFromNull();
1647: }
1648:
1649: Calendar calendar;
1650: if (arg instanceof Calendar) {
1651: calendar = (Calendar) arg;
1652: } else {
1653: Date date = null;
1654: if (arg instanceof Long) {
1655: date = new Date(((Long) arg).longValue());
1656: } else if (arg instanceof Date) {
1657: date = (Date) arg;
1658: } else {
1659: throw new IllegalFormatConversionException(
1660: currentConversionType, arg.getClass());
1661: }
1662: calendar = Calendar.getInstance(locale);
1663: calendar.setTime(date);
1664: }
1665:
1666: if (null == dateTimeUtil) {
1667: dateTimeUtil = new DateTimeUtil(locale);
1668: }
1669: StringBuilder result = new StringBuilder();
1670: // output result
1671: dateTimeUtil.transform(formatToken, calendar, result);
1672: return padding(result, startIndex);
1673: }
1674: }
1675:
1676: private static class FloatUtil {
1677: private StringBuilder result;
1678:
1679: private DecimalFormat decimalFormat;
1680:
1681: private FormatToken formatToken;
1682:
1683: private Object argument;
1684:
1685: private char minusSign;
1686:
1687: FloatUtil(StringBuilder result, FormatToken formatToken,
1688: DecimalFormat decimalFormat, Object argument) {
1689: this .result = result;
1690: this .formatToken = formatToken;
1691: this .decimalFormat = decimalFormat;
1692: this .argument = argument;
1693: this .minusSign = decimalFormat.getDecimalFormatSymbols()
1694: .getMinusSign();
1695: }
1696:
1697: void transform(FormatToken aFormatToken, StringBuilder aResult) {
1698: this .result = aResult;
1699: this .formatToken = aFormatToken;
1700: switch (formatToken.getConversionType()) {
1701: case 'e':
1702: case 'E': {
1703: transform_e();
1704: break;
1705: }
1706: case 'f': {
1707: transform_f();
1708: break;
1709: }
1710: case 'g':
1711: case 'G': {
1712: transform_g();
1713: break;
1714: }
1715: case 'a':
1716: case 'A': {
1717: transform_a();
1718: break;
1719: }
1720: default: {
1721: throw new UnknownFormatConversionException(String
1722: .valueOf(formatToken.getConversionType()));
1723: }
1724: }
1725: }
1726:
1727: char getMinusSign() {
1728: return minusSign;
1729: }
1730:
1731: char getAddSign() {
1732: return '+';
1733: }
1734:
1735: void transform_e() {
1736: StringBuilder pattern = new StringBuilder();
1737: pattern.append('0');
1738: if (formatToken.getPrecision() > 0) {
1739: pattern.append('.');
1740: char[] zeros = new char[formatToken.getPrecision()];
1741: Arrays.fill(zeros, '0');
1742: pattern.append(zeros);
1743: }
1744: pattern.append('E');
1745: pattern.append("+00"); //$NON-NLS-1$
1746: decimalFormat.applyPattern(pattern.toString());
1747: String formattedString = decimalFormat.format(argument);
1748: result.append(formattedString.replace('E', 'e'));
1749:
1750: // if the flag is sharp and decimal seperator is always given
1751: // out.
1752: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)
1753: && 0 == formatToken.getPrecision()) {
1754: int indexOfE = result.indexOf("e"); //$NON-NLS-1$
1755: char dot = decimalFormat.getDecimalFormatSymbols()
1756: .getDecimalSeparator();
1757: result.insert(indexOfE, dot);
1758: }
1759: }
1760:
1761: void transform_g() {
1762: int precision = formatToken.getPrecision();
1763: precision = (0 == precision ? 1 : precision);
1764: formatToken.setPrecision(precision);
1765:
1766: if (0.0 == ((Number) argument).doubleValue()) {
1767: precision--;
1768: formatToken.setPrecision(precision);
1769: transform_f();
1770: return;
1771: }
1772:
1773: boolean requireScientificRepresentation = true;
1774: double d = ((Number) argument).doubleValue();
1775: d = Math.abs(d);
1776: long l = Math.round(d);
1777:
1778: if (l >= 1) {
1779: if (l < Math.pow(10, precision)) {
1780: requireScientificRepresentation = false;
1781: precision -= String.valueOf(l).length();
1782: precision = precision < 0 ? 0 : precision;
1783: l = Math.round(d * Math.pow(10, precision + 1));
1784: if (String.valueOf(l).length() <= formatToken
1785: .getPrecision()) {
1786: precision++;
1787: }
1788: formatToken.setPrecision(precision);
1789: }
1790:
1791: } else {
1792: l = Math.round(d * Math.pow(10, 4));
1793: if (l >= 1) {
1794: requireScientificRepresentation = false;
1795: precision += 4 - String.valueOf(l).length();
1796: l = Math.round(d * Math.pow(10, precision + 1));
1797: if (String.valueOf(l).length() <= formatToken
1798: .getPrecision()) {
1799: precision++;
1800: }
1801: l = Math.round(d * Math.pow(10, precision));
1802: if (l < Math.pow(10, precision - 4)) {
1803: requireScientificRepresentation = true;
1804: } else {
1805: formatToken.setPrecision(precision);
1806: }
1807: }
1808: }
1809: if (requireScientificRepresentation) {
1810: precision = formatToken.getPrecision();
1811: precision--;
1812: formatToken.setPrecision(precision);
1813: transform_e();
1814: } else {
1815: transform_f();
1816: }
1817:
1818: }
1819:
1820: void transform_f() {
1821: StringBuilder pattern = new StringBuilder();
1822: if (formatToken.isFlagSet(FormatToken.FLAG_COMMA)) {
1823: pattern.append(',');
1824: int groupingSize = decimalFormat.getGroupingSize();
1825: if (groupingSize > 1) {
1826: char[] sharps = new char[groupingSize - 1];
1827: Arrays.fill(sharps, '#');
1828: pattern.append(sharps);
1829: }
1830: }
1831:
1832: pattern.append(0);
1833:
1834: if (formatToken.getPrecision() > 0) {
1835: pattern.append('.');
1836: char[] zeros = new char[formatToken.getPrecision()];
1837: Arrays.fill(zeros, '0');
1838: pattern.append(zeros);
1839: }
1840: decimalFormat.applyPattern(pattern.toString());
1841: result.append(decimalFormat.format(argument));
1842: // if the flag is sharp and decimal seperator is always given
1843: // out.
1844: if (formatToken.isFlagSet(FormatToken.FLAG_SHARP)
1845: && 0 == formatToken.getPrecision()) {
1846: char dot = decimalFormat.getDecimalFormatSymbols()
1847: .getDecimalSeparator();
1848: result.append(dot);
1849: }
1850:
1851: }
1852:
1853: void transform_a() {
1854: char currentConversionType = formatToken
1855: .getConversionType();
1856:
1857: if (argument instanceof Float) {
1858: Float F = (Float) argument;
1859: result.append(Float.toHexString(F.floatValue()));
1860:
1861: } else if (argument instanceof Double) {
1862: Double D = (Double) argument;
1863: result.append(Double.toHexString(D.doubleValue()));
1864: } else {
1865: // BigInteger is not supported.
1866: throw new IllegalFormatConversionException(
1867: currentConversionType, argument.getClass());
1868: }
1869:
1870: if (!formatToken.isPrecisionSet()) {
1871: return;
1872: }
1873:
1874: int precision = formatToken.getPrecision();
1875: precision = (0 == precision ? 1 : precision);
1876: int indexOfFirstFracitoanlDigit = result.indexOf(".") + 1; //$NON-NLS-1$
1877: int indexOfP = result.indexOf("p"); //$NON-NLS-1$
1878: int fractionalLength = indexOfP
1879: - indexOfFirstFracitoanlDigit;
1880:
1881: if (fractionalLength == precision) {
1882: return;
1883: }
1884:
1885: if (fractionalLength < precision) {
1886: char zeros[] = new char[precision - fractionalLength];
1887: Arrays.fill(zeros, '0');
1888: result.insert(indexOfP, zeros);
1889: return;
1890: }
1891: result.delete(indexOfFirstFracitoanlDigit + precision,
1892: indexOfP);
1893: }
1894: }
1895:
1896: private static class DateTimeUtil {
1897: private Calendar calendar;
1898:
1899: private Locale locale;
1900:
1901: private StringBuilder result;
1902:
1903: private DateFormatSymbols dateFormatSymbols;
1904:
1905: DateTimeUtil(Locale locale) {
1906: this .locale = locale;
1907: }
1908:
1909: void transform(FormatToken formatToken, Calendar aCalendar,
1910: StringBuilder aResult) {
1911: this .result = aResult;
1912: this .calendar = aCalendar;
1913: char suffix = formatToken.getDateSuffix();
1914:
1915: switch (suffix) {
1916: case 'H': {
1917: transform_H();
1918: break;
1919: }
1920: case 'I': {
1921: transform_I();
1922: break;
1923: }
1924: case 'M': {
1925: transform_M();
1926: break;
1927: }
1928: case 'S': {
1929: transform_S();
1930: break;
1931: }
1932: case 'L': {
1933: transform_L();
1934: break;
1935: }
1936: case 'N': {
1937: transform_N();
1938: break;
1939: }
1940: case 'k': {
1941: transform_k();
1942: break;
1943: }
1944: case 'l': {
1945: transform_l();
1946: break;
1947: }
1948: case 'p': {
1949: transform_p(true);
1950: break;
1951: }
1952: case 's': {
1953: transform_s();
1954: break;
1955: }
1956: case 'z': {
1957: transform_z();
1958: break;
1959: }
1960: case 'Z': {
1961: transform_Z();
1962: break;
1963: }
1964: case 'Q': {
1965: transform_Q();
1966: break;
1967: }
1968: case 'B': {
1969: transform_B();
1970: break;
1971: }
1972: case 'b':
1973: case 'h': {
1974: transform_b();
1975: break;
1976: }
1977: case 'A': {
1978: transform_A();
1979: break;
1980: }
1981: case 'a': {
1982: transform_a();
1983: break;
1984: }
1985: case 'C': {
1986: transform_C();
1987: break;
1988: }
1989: case 'Y': {
1990: transform_Y();
1991: break;
1992: }
1993: case 'y': {
1994: transform_y();
1995: break;
1996: }
1997: case 'j': {
1998: transform_j();
1999: break;
2000: }
2001: case 'm': {
2002: transform_m();
2003: break;
2004: }
2005: case 'd': {
2006: transform_d();
2007: break;
2008: }
2009: case 'e': {
2010: transform_e();
2011: break;
2012: }
2013: case 'R': {
2014: transform_R();
2015: break;
2016: }
2017:
2018: case 'T': {
2019: transform_T();
2020: break;
2021: }
2022: case 'r': {
2023: transform_r();
2024: break;
2025: }
2026: case 'D': {
2027: transform_D();
2028: break;
2029: }
2030: case 'F': {
2031: transform_F();
2032: break;
2033: }
2034: case 'c': {
2035: transform_c();
2036: break;
2037: }
2038: default: {
2039: throw new UnknownFormatConversionException(String
2040: .valueOf(formatToken.getConversionType())
2041: + formatToken.getDateSuffix());
2042: }
2043: }
2044: }
2045:
2046: private void transform_e() {
2047: int day = calendar.get(Calendar.DAY_OF_MONTH);
2048: result.append(day);
2049: }
2050:
2051: private void transform_d() {
2052: int day = calendar.get(Calendar.DAY_OF_MONTH);
2053: result.append(paddingZeros(day, 2));
2054: }
2055:
2056: private void transform_m() {
2057: int month = calendar.get(Calendar.MONTH);
2058: // The returned month starts from zero, which needs to be
2059: // incremented by 1.
2060: month++;
2061: result.append(paddingZeros(month, 2));
2062: }
2063:
2064: private void transform_j() {
2065: int day = calendar.get(Calendar.DAY_OF_YEAR);
2066: result.append(paddingZeros(day, 3));
2067: }
2068:
2069: private void transform_y() {
2070: int year = calendar.get(Calendar.YEAR);
2071: year %= 100;
2072: result.append(paddingZeros(year, 2));
2073: }
2074:
2075: private void transform_Y() {
2076: int year = calendar.get(Calendar.YEAR);
2077: result.append(paddingZeros(year, 4));
2078: }
2079:
2080: private void transform_C() {
2081: int year = calendar.get(Calendar.YEAR);
2082: year /= 100;
2083: result.append(paddingZeros(year, 2));
2084: }
2085:
2086: private void transform_a() {
2087: int day = calendar.get(Calendar.DAY_OF_WEEK);
2088: result
2089: .append(getDateFormatSymbols().getShortWeekdays()[day]);
2090: }
2091:
2092: private void transform_A() {
2093: int day = calendar.get(Calendar.DAY_OF_WEEK);
2094: result.append(getDateFormatSymbols().getWeekdays()[day]);
2095: }
2096:
2097: private void transform_b() {
2098: int month = calendar.get(Calendar.MONTH);
2099: result
2100: .append(getDateFormatSymbols().getShortMonths()[month]);
2101: }
2102:
2103: private void transform_B() {
2104: int month = calendar.get(Calendar.MONTH);
2105: result.append(getDateFormatSymbols().getMonths()[month]);
2106: }
2107:
2108: private void transform_Q() {
2109: long milliSeconds = calendar.getTimeInMillis();
2110: result.append(milliSeconds);
2111: }
2112:
2113: private void transform_s() {
2114: long milliSeconds = calendar.getTimeInMillis();
2115: milliSeconds /= 1000;
2116: result.append(milliSeconds);
2117: }
2118:
2119: private void transform_Z() {
2120: TimeZone timeZone = calendar.getTimeZone();
2121: result.append(timeZone.getDisplayName(true, TimeZone.SHORT,
2122: locale));
2123: }
2124:
2125: private void transform_z() {
2126: int zoneOffset = calendar.get(Calendar.ZONE_OFFSET);
2127: zoneOffset /= 3600000;
2128: zoneOffset *= 100;
2129: if (zoneOffset >= 0) {
2130: result.append('+');
2131: }
2132: result.append(paddingZeros(zoneOffset, 4));
2133: }
2134:
2135: private void transform_p(boolean isLowerCase) {
2136: int i = calendar.get(Calendar.AM_PM);
2137: String s = getDateFormatSymbols().getAmPmStrings()[i];
2138: if (isLowerCase) {
2139: s = s.toLowerCase(locale);
2140: }
2141: result.append(s);
2142: }
2143:
2144: private void transform_N() {
2145: // TODO System.nanoTime();
2146: long nanosecond = calendar.get(Calendar.MILLISECOND) * 1000000L;
2147: result.append(paddingZeros(nanosecond, 9));
2148: }
2149:
2150: private void transform_L() {
2151: int millisecond = calendar.get(Calendar.MILLISECOND);
2152: result.append(paddingZeros(millisecond, 3));
2153: }
2154:
2155: private void transform_S() {
2156: int second = calendar.get(Calendar.SECOND);
2157: result.append(paddingZeros(second, 2));
2158: }
2159:
2160: private void transform_M() {
2161: int minute = calendar.get(Calendar.MINUTE);
2162: result.append(paddingZeros(minute, 2));
2163: }
2164:
2165: private void transform_l() {
2166: int hour = calendar.get(Calendar.HOUR);
2167: if (0 == hour) {
2168: hour = 12;
2169: }
2170: result.append(hour);
2171: }
2172:
2173: private void transform_k() {
2174: int hour = calendar.get(Calendar.HOUR_OF_DAY);
2175: result.append(hour);
2176: }
2177:
2178: private void transform_I() {
2179: int hour = calendar.get(Calendar.HOUR);
2180: if (0 == hour) {
2181: hour = 12;
2182: }
2183: result.append(paddingZeros(hour, 2));
2184: }
2185:
2186: private void transform_H() {
2187: int hour = calendar.get(Calendar.HOUR_OF_DAY);
2188: result.append(paddingZeros(hour, 2));
2189: }
2190:
2191: private void transform_R() {
2192: transform_H();
2193: result.append(':');
2194: transform_M();
2195: }
2196:
2197: private void transform_T() {
2198: transform_H();
2199: result.append(':');
2200: transform_M();
2201: result.append(':');
2202: transform_S();
2203: }
2204:
2205: private void transform_r() {
2206: transform_I();
2207: result.append(':');
2208: transform_M();
2209: result.append(':');
2210: transform_S();
2211: result.append(' ');
2212: transform_p(false);
2213: }
2214:
2215: private void transform_D() {
2216: transform_m();
2217: result.append('/');
2218: transform_d();
2219: result.append('/');
2220: transform_y();
2221: }
2222:
2223: private void transform_F() {
2224: transform_Y();
2225: result.append('-');
2226: transform_m();
2227: result.append('-');
2228: transform_d();
2229: }
2230:
2231: private void transform_c() {
2232: transform_a();
2233: result.append(' ');
2234: transform_b();
2235: result.append(' ');
2236: transform_d();
2237: result.append(' ');
2238: transform_T();
2239: result.append(' ');
2240: transform_Z();
2241: result.append(' ');
2242: transform_Y();
2243: }
2244:
2245: private static String paddingZeros(long number, int length) {
2246: int len = length;
2247: StringBuilder result = new StringBuilder();
2248: result.append(number);
2249: int startIndex = 0;
2250: if (number < 0) {
2251: len++;
2252: startIndex = 1;
2253: }
2254: len -= result.length();
2255: if (len > 0) {
2256: char[] zeros = new char[len];
2257: Arrays.fill(zeros, '0');
2258: result.insert(startIndex, zeros);
2259: }
2260: return result.toString();
2261: }
2262:
2263: private DateFormatSymbols getDateFormatSymbols() {
2264: if (null == dateFormatSymbols) {
2265: dateFormatSymbols = new DateFormatSymbols(locale);
2266: }
2267: return dateFormatSymbols;
2268: }
2269: }
2270:
2271: private static class ParserStateMachine {
2272:
2273: private static final char EOS = (char) -1;
2274:
2275: private static final int EXIT_STATE = 0;
2276:
2277: private static final int ENTRY_STATE = 1;
2278:
2279: private static final int START_CONVERSION_STATE = 2;
2280:
2281: private static final int FLAGS_STATE = 3;
2282:
2283: private static final int WIDTH_STATE = 4;
2284:
2285: private static final int PRECISION_STATE = 5;
2286:
2287: private static final int CONVERSION_TYPE_STATE = 6;
2288:
2289: private static final int SUFFIX_STATE = 7;
2290:
2291: private FormatToken token;
2292:
2293: private int state = ENTRY_STATE;
2294:
2295: private char currentChar = 0;
2296:
2297: private CharBuffer format = null;
2298:
2299: ParserStateMachine(CharBuffer format) {
2300: this .format = format;
2301: }
2302:
2303: void reset() {
2304: this .currentChar = (char) FormatToken.UNSET;
2305: this .state = ENTRY_STATE;
2306: this .token = null;
2307: }
2308:
2309: /*
2310: * Gets the information about the current format token. Information is
2311: * recorded in the FormatToken returned and the position of the stream
2312: * for the format string will be advanced till the next format token.
2313: */
2314: FormatToken getNextFormatToken() {
2315: token = new FormatToken();
2316: token.setFormatStringStartIndex(format.position());
2317:
2318: // FINITE AUTOMATIC MACHINE
2319: while (true) {
2320:
2321: if (ParserStateMachine.EXIT_STATE != state) {
2322: // exit state does not need to get next char
2323: currentChar = getNextFormatChar();
2324: if (EOS == currentChar
2325: && ParserStateMachine.ENTRY_STATE != state) {
2326: throw new UnknownFormatConversionException(
2327: getFormatString());
2328: }
2329: }
2330:
2331: switch (state) {
2332: // exit state
2333: case ParserStateMachine.EXIT_STATE: {
2334: process_EXIT_STATE();
2335: return token;
2336: }
2337: // plain text state, not yet applied converter
2338: case ParserStateMachine.ENTRY_STATE: {
2339: process_ENTRY_STATE();
2340: break;
2341: }
2342: // begins converted string
2343: case ParserStateMachine.START_CONVERSION_STATE: {
2344: process_START_CONVERSION_STATE();
2345: break;
2346: }
2347: case ParserStateMachine.FLAGS_STATE: {
2348: process_FlAGS_STATE();
2349: break;
2350: }
2351: case ParserStateMachine.WIDTH_STATE: {
2352: process_WIDTH_STATE();
2353: break;
2354: }
2355: case ParserStateMachine.PRECISION_STATE: {
2356: process_PRECISION_STATE();
2357: break;
2358: }
2359: case ParserStateMachine.CONVERSION_TYPE_STATE: {
2360: process_CONVERSION_TYPE_STATE();
2361: break;
2362: }
2363: case ParserStateMachine.SUFFIX_STATE: {
2364: process_SUFFIX_STATE();
2365: break;
2366: }
2367: }
2368: }
2369: }
2370:
2371: /*
2372: * Gets next char from the format string.
2373: */
2374: private char getNextFormatChar() {
2375: if (format.hasRemaining()) {
2376: return format.get();
2377: }
2378: return EOS;
2379: }
2380:
2381: private String getFormatString() {
2382: int end = format.position();
2383: format.rewind();
2384: String formatString = format.subSequence(
2385: token.getFormatStringStartIndex(), end).toString();
2386: format.position(end);
2387: return formatString;
2388: }
2389:
2390: private void process_ENTRY_STATE() {
2391: if (EOS == currentChar) {
2392: state = ParserStateMachine.EXIT_STATE;
2393: } else if ('%' == currentChar) {
2394: // change to conversion type state
2395: state = START_CONVERSION_STATE;
2396: }
2397: // else remains in ENTRY_STATE
2398: }
2399:
2400: private void process_START_CONVERSION_STATE() {
2401: if (Character.isDigit(currentChar)) {
2402: int position = format.position() - 1;
2403: int number = parseInt(format);
2404: char nextChar = 0;
2405: if (format.hasRemaining()) {
2406: nextChar = format.get();
2407: }
2408: if ('$' == nextChar) {
2409: // the digital sequence stands for the argument
2410: // index.
2411: int argIndex = number;
2412: // k$ stands for the argument whose index is k-1 except that
2413: // 0$ and 1$ both stands for the first element.
2414: if (argIndex > 0) {
2415: token.setArgIndex(argIndex - 1);
2416: } else if (argIndex == FormatToken.UNSET) {
2417: throw new MissingFormatArgumentException(
2418: getFormatString());
2419: }
2420: state = FLAGS_STATE;
2421: } else {
2422: // the digital zero stands for one format flag.
2423: if ('0' == currentChar) {
2424: state = FLAGS_STATE;
2425: format.position(position);
2426: } else {
2427: // the digital sequence stands for the width.
2428: state = WIDTH_STATE;
2429: // do not get the next char.
2430: format.position(format.position() - 1);
2431: token.setWidth(number);
2432: }
2433: }
2434: currentChar = nextChar;
2435: } else if ('<' == currentChar) {
2436: state = FLAGS_STATE;
2437: token.setArgIndex(FormatToken.LAST_ARGUMENT_INDEX);
2438: } else {
2439: state = FLAGS_STATE;
2440: // do not get the next char.
2441: format.position(format.position() - 1);
2442: }
2443:
2444: }
2445:
2446: private void process_FlAGS_STATE() {
2447: if (token.setFlag(currentChar)) {
2448: // remains in FLAGS_STATE
2449: } else if (Character.isDigit(currentChar)) {
2450: token.setWidth(parseInt(format));
2451: state = WIDTH_STATE;
2452: } else if ('.' == currentChar) {
2453: state = PRECISION_STATE;
2454: } else {
2455: state = CONVERSION_TYPE_STATE;
2456: // do not get the next char.
2457: format.position(format.position() - 1);
2458: }
2459: }
2460:
2461: private void process_WIDTH_STATE() {
2462: if ('.' == currentChar) {
2463: state = PRECISION_STATE;
2464: } else {
2465: state = CONVERSION_TYPE_STATE;
2466: // do not get the next char.
2467: format.position(format.position() - 1);
2468: }
2469: }
2470:
2471: private void process_PRECISION_STATE() {
2472: if (Character.isDigit(currentChar)) {
2473: token.setPrecision(parseInt(format));
2474: } else {
2475: // the precision is required but not given by the
2476: // format string.
2477: throw new UnknownFormatConversionException(
2478: getFormatString());
2479: }
2480: state = CONVERSION_TYPE_STATE;
2481: }
2482:
2483: private void process_CONVERSION_TYPE_STATE() {
2484: token.setConversionType(currentChar);
2485: if ('t' == currentChar || 'T' == currentChar) {
2486: state = SUFFIX_STATE;
2487: } else {
2488: state = EXIT_STATE;
2489: }
2490:
2491: }
2492:
2493: private void process_SUFFIX_STATE() {
2494: token.setDateSuffix(currentChar);
2495: state = EXIT_STATE;
2496: }
2497:
2498: private void process_EXIT_STATE() {
2499: token.setPlainText(getFormatString());
2500: }
2501:
2502: /*
2503: * Parses integer value from the given buffer
2504: */
2505: private int parseInt(CharBuffer buffer) {
2506: int start = buffer.position() - 1;
2507: int end = buffer.limit();
2508: while (buffer.hasRemaining()) {
2509: if (!Character.isDigit(buffer.get())) {
2510: end = buffer.position() - 1;
2511: break;
2512: }
2513: }
2514: buffer.position(0);
2515: String intStr = buffer.subSequence(start, end).toString();
2516: buffer.position(end);
2517: try {
2518: return Integer.parseInt(intStr);
2519: } catch (NumberFormatException e) {
2520: return FormatToken.UNSET;
2521: }
2522: }
2523: }
2524: }
|