All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.enterprise.fmt.FmtFactory Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

//@(#) $Id: FmtFactory.java 3120 2009-02-11 10:44:52Z gub $
package at.spardat.enterprise.fmt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

import at.spardat.enterprise.exc.SysException;
import at.spardat.enterprise.util.StringSplitter;
import at.spardat.enterprise.util.StringUtil;

/**
 * This class is a factory that allows to construct all the formatters/validators provided
 * in this package by using a String-specification. 

* * The specification starts with a component indicating the type, then follows a list * of additional parameters needed for the particular type and usually ends with a * combination of style-constants. All components are separated by comma. Example: *

 * n,13,2,m&t
 * 
* denotes a numeric formatter (n) that accepts at most 13 digits in front of the comma, * at most 2 digits after the comma and has two styles m and t, expressing * that mandatory (m) input is required and (t) thousands-separation characters are used. Here * are some more examples * *
 * format         meaning
 * s,35                  String which must not be longer than 35 digits.
 * s,35,m                As above, but mandatory, i.e., must not be empty.
 * sm,5,5,m              Exactly 5 characters.
 * sr,0,35,a-c           String of length not more than 35 consisting only of characters { 'a', 'b', 'c' }.
 * n,13,2,m&t            Numeric with at most 13 digits before and 2 after the dec-point, example:
 *                       -1.000.000,99 in the austrian locale
 * n,-1,2,nn&m           10000000000000,99 in the austrian locale. Negative inputs are not allowed.
 *                       May consist of arbitrary long sequence of digits in front of dec-point.
 * nr,-1,0,5,596         Integer number between 5 and 596.
 * nr,13,0,inf,596       Integer number between -9999999999999 and 596.
 * d                     Date using the format 24.04.2000 (this is Locale-dependent).
 * d,m                   As above, but input is required.
 * d,m&ful               Mandatory date using the format "Montag, 24. April 2000", although this is locale-dependent.
 * dr,0,7,m              Mandatory date that must lie in the range [today ... today plus 7 days].
 * dr,20000101,20101231  Date that must lie in the range [1.1.2000 ... 31.12.2010].
 * dp,dd-MM-yyyy,m       Mandatory, non-localized date format, e.g., "24-04-2000".
 * b					 Boolean format, e.g. "yes".
 * 
* * Here is the more formal definitions of the format specifications: * *
 * s,maxLen(,style)
 *     String with arbitrary characters of max length maxLen. -1 for maxLen
 *     means unrestricted maximum length. With an optional third component, a style { m,uc,lc}
 *     may be specified.
 *     create may be used without a Locale.
 *
 * sm,minLen,maxLen(,style)
 *     Like the last, with the exception that an accepted string must have at least
 *     a length of minLen. The empty string is accepted unless style m
 *     is specified.
 *
 * sr,minLen,maxLen,range(,style)
 *     String of maximum length maxLen and minimum len minLen. Both minLen
 *     and maxLen may be empty of inf to denote "unrestrictedness".
 *     The character set is restricted to
 *     a range specification range, e.g., a-z denotes for example all unicode characters
 *     greater equal to a and less equal to z. For a detailed description see
 *     class {@link at.spardat.enterprise.fmt.AStringFmtRange}.
 *     With the last optional parameter style, the styles {m,uc,lc} may be specified.
 *     create may be used without a Locale.
 *
 * sre,(minLen),(maxLen),(range),(m&ul&lc),bundleKey,resBundle,regex
 *     The values in brackets can be empty strings, all commas are mandatory.
 *     String of maximum length maxLen and minimum len minLen. Both minLen
 *     and maxLen may be empty of inf to denote "unrestrictedness".
 *     The character set is restricted to
 *     a range specification by the optional string range, e.g., a-z denotes for example all unicode characters
 *     greater equal to a and less equal to z. For a detailed description see
 *     class {@link at.spardat.enterprise.fmt.AStringFmtRange}.
 *     The optional parameter style, the styles {m,uc,lc} may be specified.
 *     create may be used without a Locale.
 *     For the error message shown in the case of a negative evaluation of the regular expression
 *     a bundleKey identifying the error message and a resBundle (without locale)
 *     from which the error message is taken have to be specified.
 *     regex states the regular expression which validates the input.
 *     The regular expression interpretation/validation is done by the java.util.regex classes,
 *     see there for the proper regular expression usage.
 *
 * n,beforeC,afterC(,style)
 *     Numeric value with at most beforeC digits before and afterC digits
 *     after the decimal point. Both may be -1 or empty, indicating unlimited length.
 *     An optional last parameter style that may adopt values out of { m, t, nn }
 *     may be specified.
 *     create requires a Locale.
 *     Example: "n,13,2,nn&t" specifies a numeric value with 13 digits before and 2
 *              after the comma. It is displayed with thousands separation chars
 *              and does not accept negative values when entered.
 *
 * nr,beforeC,afterC,min,max(,style)
 *     Behaves like the last one, additionally lower and upper bounds concerning the legal
 *     input values, that is the parameters min and max are specified. Both,
 *     min and max expect a string encoding of a double as is accepted by
 *     the method Double.parseDouble. If you want to let one of either min
 *     or max unrestricted, specify inf which denotes infinity.
 *     FmtFactory.create requires a Locale.
 *
 * np,pattern,beforeC,afterC(,style)
 *     Specifies a numeric format by using a java.text.DecimalFormat-pattern.
 *     The style parameter may adopt values out of { m, nn }. Please note that
 *     the number of decimal places in the pattern must match afterC. Also note
 *     that java.text.DecimalFormat internally uses doubles and therefore
 *     the maximum number of digits that may be represented without loss of precision
 *     is 15.
 *     FmtFactory.create requires a Locale.
 *
 * d(,style)
 *     Specifies a date format. If a style is specified, it must be one of { sho, med, lon, ful }
 *     optionally combined with { m }. If no style is specified, med is default.
 *     FmtFactory.create requires a Locale.
 *
 * dr,min,max(,style)
 *     Same as the last one, with the extension that lower- and/or upper-bounds for
 *     permissible dates are specified. Both min and max can be an absolute
 *     dates in the format yyyyMMdd or day-offsets relative to today,
 *     i.e., -1 stands for yesterday, 0 for today, 1 for tomorrow.
 *     inf stands for infinity and may be used if one of the two
 *     bounds is to be unspecified. The same is achieved if min or max
 *     is empty. Example:
 *     "dr,0,inf,m" or "dr,0,,m" specifies that a date must be entered (mandatory) and that is must
 *     not lie in the past (lower bound is today).
 *     "dr,,7" specifies that permissible dates are the ones in the past up to
 *     today plus one week.
 *     "dr,19000101,," specifies that the entered date must be 1.1.1900 or later.
 *     FmtFactory.create requires a Locale.
 *
 * dp,pattern(,style)
 *     Specifies a date format by using a java.text.SimpleDateFormat-pattern.
 *     For the last optional style-parameter, m (mandatory) may be used.
 *     FmtFactory.create may be used without Locale.
 *     Please note that this pattern does not work with internationalization since
 *     you are specifying the pattern directly. In international applications, use
 *     d(,style) instead.
 *
 * ts(,dateStyle(,timeStyle))
 *     Specifies a timestamp format. Styles may be specified for both the date-component
 *     and the time-component (or only the date-component) that are values out of { sho, med, lon, ful }.
 *     If input should be mandatory, m-style may be combined with either style.
 *     FmtFactory.create requires a Locale.
 *
 * tsp,pattern(,style)
 *     Specifies a timestamp format by using a java.util.SimpleDateFormat-pattern.
 *     style is optional and may be m.
 *     FmtFactory.create requires a Locale.
 *     Please note that this pattern does not work with internationalization since
 *     you are specifying the pattern yourself. In international applications, use
 *     ts(,...) instead.
 *
 * b(,style)
 * 	   Specifies a boolean format. If a style is specified, it must be one of { sho, lon }
 *     optionally combined with { m }. If no style is specified, lon is default.
 *     FmtFactory.create requires a Locale.
 * 
* * The following list defines the style-strings supported: * *
 * styles     description
 * m                 mandatory; non-empty input must be provided
 * t                 used with numeric types and indicates that thousands separation characters
 *                   should be used.
 * nn                used with numeric types and indicates that just positive numbers including
 *                   zero may be entered (i.e., no negative numbers).
 * sz                suppressZero; used with numeric types to display zero as empty string and,
 *                   vice versa, an empty UI-string becomes zero.
 * sho               denotes a short date/time format, e.g., 24.04.00
 * med               denotes medium data/time format, e.g., 24.04.2000
 * lon               denotes long date/time format, e.g., 24. April 2000
 * ful               denotes full date/time format, e.g., Montag, 24. April 2000
 * uc                uppercase; input is converted to uppercase; used with strings
 * lc                lowercase; input is converted to lowercase; used with strings
 * 
* * Wherever style-components are requested, you may enter a combination of styles * appending them with the &sign. For example, m&nn describes a number where * input must be entered and the provided input must not be negative.

* * Comma is used as delimiter to separate the components. What if one component itself * needs to have a comma? Then you must use a percent-character as escape-character. For example, * suppose you need a dp-spec where the SimpleDateFormat-pattern is * EEE, dd.MM. Then the dp-spec must look like *

 * dp,EEE%, dd.MM
 * 
* If the component should contain a percent itself, you must use a double-percent instead. * * @author YSD */ public class FmtFactory { private static HashMap styles_ = new HashMap(); private static FmtFactory theInstance_ = new FmtFactory(); static { styles_.put ("m", new Integer (IFmt.MANDATORY)); styles_.put ("t", new Integer (ABcdFmt.THOUS_SEPS)); styles_.put ("nn", new Integer (ABcdFmt.NO_NEG)); styles_.put ("sz", new Integer (ABcdFmt.SUPPRESS_ZERO)); styles_.put ("sho", new Integer (ADateFmt.SHORT)); styles_.put ("med", new Integer (ADateFmt.MEDIUM)); styles_.put ("lon", new Integer (ADateFmt.LONG)); styles_.put ("ful", new Integer (ADateFmt.FULL)); styles_.put ("uc", new Integer (AStringFmt.UPPER_CASE)); styles_.put ("lc", new Integer (AStringFmt.LOWER_CASE)); } /** * Creates a formatter according to a provided specification that must follow the rules * defined above. This method does not require a Locale, therefore should * only be used with formats that are not localizable (see above). But if some particular * format needs a Locale and this method is called, Locale.getDefault is used instead. * * @param spec the specification * @return newly created formatter */ public static IFmt create (String spec) { return create (spec, Locale.getDefault()); } /** * Creates a formatter according to a provided specification that must follow the rules * defined above. * * @param spec the specification * @param l the locale required for formatter-classes that are locale-dependent. * @return newly created formatter */ public static IFmt create (String spec, Locale l) { return theInstance_.createImpl (spec, l); } /** * Enables an application to change the used factory-implementation. The provided * object must be a subclass of this class. */ public static void setFactory (FmtFactory fac) { if (fac != null) theInstance_ = fac; } /** * This method actually does the work and is responsible to create an IFmt. * * @param spec specification * @param l Locale * @return formatter * @exception RuntimeException on illegal inputs */ protected IFmt createImpl (String spec, Locale l) { StringSplitter ss = new StringSplitter (spec, ',', '%'); String type = getToken (ss, "type"); int typeLen = type.length(); int style = 0; if (typeLen == 0) throw new SysException ("empty type '" + type + "' in format spec '" + spec + "'"); char firstCh = type.charAt(0); switch (firstCh) { case 's': if (typeLen == 1) { // s,maxLen(,style) int maxLen = getIntToken2 (ss, "maxLen"); if (ss.hasMoreTokens()) style = getStyle (ss); return AStringFmt.getInstance (maxLen, style); } else if (type.equals ("sm")) { // s,minLen,maxLen(,style) int minLen = getIntToken2 (ss, "minLen"); int maxLen = getIntToken2 (ss, "maxLen"); if (ss.hasMoreTokens()) style = getStyle (ss); AStringFmt fmt = AStringFmt.getInstance (maxLen, style); fmt.setMinLen(minLen); return fmt; } else if (type.equals("sr")) { // sr,minLen,maxLen,range(,style) int minLen = getIntToken2 (ss, "minLen"); int maxLen = getIntToken2 (ss, "maxLen"); String range = getToken (ss, "range"); if (ss.hasMoreTokens()) style = getStyle (ss); AStringFmt fmt = AStringFmt.getInstance(maxLen, range, style); fmt.setMinLen(minLen); return fmt; } else if (type.equals("sre")) { // sre,(minLen),(maxLen),(range),(m&ul&lc),bundleKey,resBundle,regex int minLen = getIntToken2 (ss, "minLen"); int maxLen = getIntToken2 (ss, "maxLen"); String range = getToken (ss, "range"); style = getStyle (ss); String bundleKey = getToken (ss, "bundleKey"); String resBundle = getToken (ss, "resBundle"); String regex = getToken (ss, "regex"); AStringFmt fmt = AStringFmt.getInstance(maxLen, range, style,regex,bundleKey,resBundle); fmt.setMinLen(minLen); return fmt; } break; case 'n': if (typeLen == 1) { // n,beforeC,afterC(,style) int beforeC = getIntToken2 (ss, "beforeC"); int afterC = getIntToken2 (ss, "afterC"); if (ss.hasMoreTokens()) style = getStyle (ss); return ABcdFmt.getInstance(beforeC, afterC, style, l); } else if (type.equals("np")) { // np,pattern(,style) String pattern = getToken(ss, "pattern"); int beforeC = getIntToken2 (ss, "beforeC"); int afterC = getIntToken2 (ss, "afterC"); if (ss.hasMoreTokens()) style = getStyle (ss); return ABcdFmt.getInstance (pattern, beforeC, afterC, style, l); } else if (type.equals("nr")) { // nr,beforeC,afterC,min,max(,style) int beforeC = getIntToken2 (ss, "beforeC"); int afterC = getIntToken2 (ss, "afterC"); double minVal, maxVal; minVal = getDoubleToken (ss, "min"); if (minVal == Double.POSITIVE_INFINITY) minVal = -Double.MAX_VALUE; maxVal = getDoubleToken (ss, "max"); if (maxVal == Double.POSITIVE_INFINITY) maxVal = Double.MAX_VALUE; if (ss.hasMoreTokens()) style = getStyle (ss); return ABcdFmt.getInstance (beforeC, afterC, style, minVal, maxVal, l); } break; case 'd': if (typeLen == 1) { // d(,style) if (ss.hasMoreTokens()) style = getStyle (ss); return ADateFmt.getInstance(style, l); } else if (type.equals("dp")) { // dp,pattern(,style) String pattern = getToken(ss, "pattern"); if (ss.hasMoreTokens()) style = getStyle (ss); return ADateFmt.getInstance(pattern, style,l); // } else if (type.equals("dr")) { // // dr,min,max(,style) // int min = getIntToken (ss, "min"); // if (min == Integer.MIN_VALUE) min = Integer.MAX_VALUE; // int max = getIntToken (ss, "max"); // if (max == Integer.MIN_VALUE) max = Integer.MAX_VALUE; // if (ss.hasMoreTokens()) style = getStyle (ss); // return ADateFmt.getInstance (style, l, min, max); // } else if (type.equals("dr")) { // dr,min,max(,style) int min = Integer.MAX_VALUE; int max = Integer.MAX_VALUE; String minDate=null; String maxDate=null; if(ss.peekNextToken()!=null&&ss.peekNextToken().length()==8) { minDate = getToken(ss,"min"); } else { min = getIntToken (ss, "min"); if (min == Integer.MIN_VALUE) min = Integer.MAX_VALUE; } if(ss.peekNextToken()!=null&&ss.peekNextToken().length()==8) { maxDate = getToken (ss, "max"); } else { max = getIntToken (ss, "max"); if (max == Integer.MIN_VALUE) max = Integer.MAX_VALUE; } if (ss.hasMoreTokens()) style = getStyle (ss); return ADateFmt.getInstance (style, l, min, max, minDate, maxDate); } break; case 't': if (type.equals("ts")) { // ts(,dateStyle(,timeStyle)) int dStyle = 0; int tStyle = 0; if (ss.hasMoreTokens()) dStyle = getStyle (ss); if (ss.hasMoreTokens()) tStyle = getStyle (ss); return ATimeStampFmt.getInstance(dStyle, tStyle, l); } else if (type.equals("tsp")) { // tsp,pattern(,style) String pattern = getToken (ss, "pattern"); if (ss.hasMoreTokens()) style = getStyle (ss); return ATimeStampFmt.getInstance(pattern, style, l); } break; case 'b': if (ss.hasMoreTokens()) style = getStyle (ss); return ABooleanFmt.getInstance(style,l); } // fallthrough throw new RuntimeException ("unknown type '" + type + "' in format spec '" + spec + "'"); } /** * Helper method to extract the next token or to throw an exception if there is no more token. */ protected String getToken (StringSplitter ss, String expected) { if (!ss.hasMoreTokens()) throw new RuntimeException (expected + " expected in " + ss.getString()); return ss.nextToken(); } /** * Helper method that extracts the next token that must be an integer and throws an exception * if there is no more token. * * @param ss the input stream where the next token has to be extracted * @param expected text to be used in error messages * @return an parsed integer; the integer is Integer.MAX_VALUE if the token is inf (infinity). * the integer is Integer.MIN_VALUE if the token is empty. */ protected int getIntToken (StringSplitter ss, String expected) { String tok = getToken (ss, expected); if (tok.length() == 0) return Integer.MIN_VALUE; if (tok.equals("inf")) return Integer.MAX_VALUE; int toReturn = 0; try { toReturn = Integer.parseInt(tok); } catch (Exception ex) { throw new SysException (ex, expected + " expected in " + ss.getString() + " (must be a number)"); } return toReturn; } /** * Same as getIntToken, except that if the token is inf or empty, * then -1 is returned. */ protected int getIntToken2 (StringSplitter ss, String expected) { int rv = getIntToken (ss, expected); if (rv == Integer.MAX_VALUE || rv == Integer.MAX_VALUE) return -1; return rv; } /** * Helper method that extracts the next token that must be a double as required by Double.parseDouble * and throws an Exception if there are no more tokens. * * @param ss the input stream * @param expected text to be used in error messages * @return a parsed double; the returned value is Double.POSITIVE_INFINITY if the token is inf. */ protected double getDoubleToken (StringSplitter ss, String expected) { String tok = getToken (ss, expected); if (tok.equals("inf")) return Double.POSITIVE_INFINITY; double toReturn = 0.0; try { toReturn = Double.parseDouble(tok); } catch (Exception ex) { throw new SysException (ex, expected + " expected in " + ss.getString() + " (must be a double as required by Double.parseDouble)"); } return toReturn; } /** * Extracts the next token from ss, expects a list * of style-strings that are separated by ampersands. If * there is no more token in ss, zero is returned. * * @param ss the input StringSplitter whose next token is optionally extracted. * @return bit-or combination of styles. */ protected int getStyle (StringSplitter ss) { if (!ss.hasMoreTokens()) return 0; String styleString = ss.nextToken(); if (styleString.length() == 0) return 0; if (styleString.indexOf('&') >= 0) { // the hard part; consists of many atomic styles ArrayList lst = StringUtil.split (styleString, "&"); int toReturn = 0; for (int i=0,size=lst.size(); i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy