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

de.tsl2.nano.format.RegExpFormat Maven / Gradle / Ivy

Go to download

TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)

There is a newer version: 2.5.1
Show newest version
/*
 * 
 * 
 * Copyright © 2002-2008 Thomas Schneider
 * Alle Rechte vorbehalten.
 * Weiterverbreitung, Benutzung, Vervielfältigung oder Offenlegung,
 * auch auszugsweise, nur mit Genehmigung.
 *
 */
package de.tsl2.nano.format;

import static de.tsl2.nano.core.util.FormatUtil.getCurrencyFormatNoFraction;
import static de.tsl2.nano.core.util.FormatUtil.getCurrencyFormatNoSymbol;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementMap;
import org.simpleframework.xml.core.Commit;
import org.simpleframework.xml.core.Complete;
import org.simpleframework.xml.core.Persist;

import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.collection.ReferenceMap;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.BeanAttribute;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.FormatUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.currency.CurrencyUtil;
import de.tsl2.nano.util.operation.IConverter;

/**
 * provides standard formatting through regular expressions. usable to verify input in a text component. the
 * parseObject() isn't able to return the desired instance!
 * 
 * if you only want to format or parse standard numbers or dates, then use the standard formatters like NumberFormat and
 * DateFormat.
 * 
 * This formatter is intended to be used to constrain or verify user input trough an input mask.
 * 
 * four standard expressions are available:
 * 
    *
  • standard text
  • *
  • numbers
  • *
  • dates
  • *
  • length
  • *
* * @author ts 18.09.2008 * @version $Revision: 1.0 $ */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class RegExpFormat extends Format implements INumberFormatCheck { private static final long serialVersionUID = 1L; private static final Log LOG = LogFactory.getLog(RegExpFormat.class); @Element(data = true) private String pattern; @Attribute private int regExpFlags; @Attribute private int maxCharacterCount = -1; private transient Pattern compiledPattern; /** needed to format objects */ @Element(name = "parser", required = false) protected GenericParser parser = null; /** default: the full regexp must be matched on format or parse! */ @Attribute boolean fullMatch = true; /** on default, this class is not able to parse a string to an object */ @Attribute boolean isAbleToParse = false; public static final char DECIMAL_SEPARATOR = new DecimalFormatSymbols(Locale.getDefault()).getDecimalSeparator(); public static final char GROUPING_SEPARATOR = new DecimalFormatSymbols(Locale.getDefault()).getGroupingSeparator(); /** characters only to format an expression (like german date '01.01.2001' the dots) */ public static final String FORMAT_CHARACTERS = " .,;_/*#|-:"; /** alphanumeric character with lowest ascii code (=32) */ public static final char MIN_CHAR = ' '; /** alphanumeric character with highest ascii code (=126) */ public static final char MAX_CHAR = '~'; /** * this pattern is not only a number format, but a pattern from nothing up to the desired number. E.g.: -999,999 --> * you start with '-', than you type the numbers! */ public static final String FORMAT_DECIMAL = "[-][0-9]{0,2}[" + DECIMAL_SEPARATOR + "][0-9]{0,2}"; /** * All single byte characters, for applications / RDBMS that do not support multibyte/unicode characters.
* Note that the Posix pattern matcher "\\p{ASCII}" only covers characters up to \x7F, so for example no German * "Umlaute". */ public static final String PATTERN_SINGLE_BYTE = "[\\x00-\\xFF]"; public static final String PATTERN_SINGLE_BYTE_SPACE = "[\\x00-\\xFF €]"; // TODO how to get the date format from locale? public static final String FORMAT_DATE_SQL = "[1-2]\\d\\d\\d\\-[0-1]\\d\\-[0-3]\\d"; public static final String FORMAT_DATE_DE = "[0-3]\\d\\.[0-1]\\d(\\.[1-2]\\d\\d\\d)?"; public static final String FORMAT_TIME = "[0-2]\\d\\:[0-5]\\d(\\:[0-5]\\d)?"; public static final String FORMAT_DATETIME = FORMAT_DATE_SQL + "( " + FORMAT_TIME + ")?"; public static final String FORMAT_DATETIME_DE = FORMAT_DATE_DE + "( " + FORMAT_TIME + ")?"; public static final String FORMAT_NAME_ALPHA_DE = "[a-zA-ZäöüÄÖÜß]+"; public static final String FORMAT_NAME_ALPHA_EXT_DE = FORMAT_NAME_ALPHA_DE + PATTERN_SINGLE_BYTE_SPACE; public static final String FORMAT_NAME_ALPHA = "[a-zA-Z]*"; public static final String FORMAT_NAME_ALPHA_NUM = "[a-zA-Z0-9]*"; public static final String FORMAT_NUMBER = "[0-9]*"; private static final Map systemInitMap = new Hashtable(); @ElementMap(attribute = true, inline = true, required = false, name = "initmask", key = "pattern") Map initMap = null; static { try { /* * standard german date format mask */ systemInitMap.put(FORMAT_DATE_DE, DateFormat.getDateInstance(DateFormat.MEDIUM).format(getInitialDate())); /* * on german dates with month-day 31, the standard-initmask '01.01.CURRENTYEAR' does not work. * example: input: 31.1 and mask: 01.01.2011 ==> 31.11.2011 ==> error */ systemInitMap.put("31.1", DateFormat.getDateInstance(DateFormat.MEDIUM).format(getInitialDate_Nov())); /* * standard decimal number */ systemInitMap.put(FORMAT_DECIMAL, "00" + DECIMAL_SEPARATOR + "00"); /* * standard time */ systemInitMap.put(FORMAT_TIME, "00:00:00"); } catch (final Exception e) { throw new RuntimeException(e); } } static Date getInitialDate() { // return new Date(); /* * current date may be a problem on months, having less than 31 days. * we give 01.01.CURRENT_YEAR */ return DateUtil.getDate(DateUtil.getYear(new Date()), 1, 1); } static Date getInitialDate_Nov() { // return new Date(); /* * current date may be a problem on months, having less than 31 days. * we give 01.10.CURRENT_YEAR */ return DateUtil.getDate(DateUtil.getYear(new Date()), 10, 1); } /** * constructor to be de-serializable */ public RegExpFormat() { super(); } /** * simple constructor without length argument. only usable on simple patterns without length definitions (like * {0,5}). * * @param pattern regexp without length def. */ protected RegExpFormat(String pattern) { this(pattern, null, calcLength(pattern, null), 0); } /** * simple constructor without length argument. only usable on simple patterns without length definitions (like * {0,5}). * * @param pattern regexp without length def. * @param init init string */ public RegExpFormat(String pattern, String init) { this(pattern, init, calcLength(pattern, init), 0); } /** * Constructor * * @param pattern regular expression to check * @param maxCharacterCount maximum allowed count of characters * @param regExpFlags additional Flags for the regular expression * @see Pattern Pattern for Flags description */ public RegExpFormat(String pattern, int maxCharacterCount, int regExpFlags) { this(pattern, null, maxCharacterCount, regExpFlags); } /** * Constructor * * @param pattern regular expression to check * @param maxCharacterCount maximum allowed count of characters * @see Pattern Pattern for Flags description */ public RegExpFormat(String pattern, int maxCharacterCount) { this(pattern, null, maxCharacterCount, 0); } /** * Constructor * * @param pattern regular expression to check * @param maxCharacterCount maximum allowed count of characters * @param regExpFlags additional Flags for the regular expression * @see Pattern Pattern for Flags description */ public RegExpFormat(String pattern, String init, int maxCharacterCount, int regExpFlags) { this(pattern, init, maxCharacterCount, regExpFlags, (Format) null); } /** * Constructor * * @param pattern regular expression to check * @param init default init string * @param maxCharacterCount maximum allowed count of characters * @param regExpFlags additional Flags for the regular expression * @param parser (optional) format instance to be set as default format and parser * @see Pattern Pattern for Flags description */ public RegExpFormat(String pattern, String init, int maxCharacterCount, int regExpFlags, Format parser) { super(); this.pattern = pattern; if (init != null) { initMap = new Hashtable(); initMap.putAll(systemInitMap); initMap.put(pattern, init); } else {//to avoid using more resources, we use the static map initMap = systemInitMap; } this.maxCharacterCount = maxCharacterCount; this.regExpFlags = regExpFlags; if (parser != null && !(parser instanceof RegExpFormat)) { isAbleToParse = true; this.parser = (GenericParser) (parser instanceof GenericParser ? parser : new GenericParser(parser)); } } /** * init * * @param pattern regular expression to check * @param maxCharacterCount maximum allowed count of characters * @param regExpFlags additional Flags for the regular expression * @see Pattern Pattern for Flags description */ protected void init(String pattern, String init, int maxCharacterCount, int regExpFlags) { this.pattern = pattern; if (init != null) { if (initMap == systemInitMap) { initMap = new HashMap(); } initMap.put(pattern, init); } this.maxCharacterCount = maxCharacterCount; this.regExpFlags = regExpFlags; compiledPattern = null; } /** * tries to calculate the length through the given pattern. if the pattern contains brackets, no length will be * calculated * * @param pattern pattern to evaluate * @return pattern length, or throw exception, if containing brackets */ private static int calcLength(String pattern, String init) { if (init != null && init.length() > 0) { return init.length(); } if (pattern.indexOf('{') == -1 && pattern.indexOf('[') == -1 && pattern.indexOf('(') == -1) { return pattern.length(); } // throw new ManagedException("tsl2nano.implementationerror", new Object[] { pattern, // "only simple patterns without length definitions (like {0,5}) are allowed!" }); int length = ENV.get("field.pattern.regexp.default.length", 64); LOG.warn("can't calculate pattern lenght for " + pattern + " --> using default length of " + length); return length; } /** * directly call to format with fullMatch parameter * * @param source object to format * @param fullMatch if true, the source must exactly match the format pattern * @return string representation of the given source */ public String format(Object source, boolean fullMatch) { final boolean lastFullMatch = setFullMatch(fullMatch); final String result = format(source); setFullMatch(lastFullMatch); return result; } /** * converts the given object to a string, using the default formatter. Then the string will be formatted through the * pattern. set {@link #fullMatch} to false, if you only need a find - not an exact match. * * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) */ @Override public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { String o; //first: do a standard formatting, before checking against the regular expression if (obj instanceof String) { o = (String) obj; } else { if (parser == null) { setDefaultFormatter(obj); } o = parser.format(obj); } if (compiledPattern == null) { compiledPattern = Pattern.compile(pattern, regExpFlags); } //second: fill separation characters o = getTextFormatted(o); //third: check against the regular expression final Matcher matcher = compiledPattern.matcher(o); final boolean matches = fullMatch ? matcher.matches() : matcher.find(); if (matches) { return toAppendTo.append(matcher.group()); } else { throw new ManagedException("tsl2nano.regexpfailure", new Object[] { o, pattern }); } } /** * The RegExpFormat doesn't know the object type - it will never do a true parsing! The method will return a * formatted string, like the format() do. If not the whole text matches the pattern, null will be returned. But you * have the possibility to set the {@link #fullMatch} to false, to only find (not match) the pattern. * * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) */ @Override public Object parseObject(String source, ParsePosition pos) { if (Util.isEmpty(source)) { // set the pos > 0, otherwise the calling method will throw a parseexception. pos.setIndex(1); return null; } final String init = getInitMask(source); // if pattern requires more characters, fill the source with a defined initialization. if (init != null && source.length() <= init.length()) { source = source + init.substring(source.length()); } if (compiledPattern == null) { compiledPattern = Pattern.compile(pattern, regExpFlags); } final Matcher matcher = compiledPattern.matcher(source); final boolean matches = fullMatch ? matcher.matches() : matcher.find(); // set the pos > 0, otherwise the calling method will throw a parseexception. pos.setIndex(source.length()); //perhaps real parsing Object obj = null; if (matches) { try { obj = parser != null && isAbleToParse(parser) ? parser.parseObject(source + getParsingSuffix(parser, source)) : matcher.group(); } catch (final ParseException e) { // ManagedException.forward(e); LOG.error(e); } } else { LOG.warn("text '" + source + "' doesn't match regular expression '" + pattern + "' (flags: " + regExpFlags + ", fullmatch: " + fullMatch + ", defaultFormatter: " + parser + ")"); } return matches ? obj : null; } /** * @return Returns the maxCharacterCount of this formatter. */ public int getMaxCharacterCount() { return maxCharacterCount; } /** * @return Returns the fullMatch. */ public boolean isFullMatch() { return fullMatch; } /** * @param fullMatch The fullMatch to set. * @return lastFullMatch */ public boolean setFullMatch(boolean fullMatch) { final boolean lastFullMatch = this.fullMatch; this.fullMatch = fullMatch; return lastFullMatch; } /** * @deprecated: use {@link #numberWithGrouping(int, int, boolean)} * @see #number(int, int, boolean) */ protected static final String number(int dec, int fract) { return number(dec, fract, false); } /** * @deprecated: use {@link #numberWithGrouping(int, int, boolean)} * @param dec count of decimals * @param fract count of fracts * @param fixed if true, fixed characters will be used. * @return regular expression describing the given number. */ protected static final String number(int dec, int fract, boolean fixed) { final String p = "([-])|([-]{0,1}[0-9]{1," + dec + "}" + (fract > 0 ? "([" + DECIMAL_SEPARATOR + "]" + "[0-9]{0," + fract + "}){0,1})" : ")"); if (fixed) { systemInitMap.put(p, fixed('0', dec) + (fract > 0 ? DECIMAL_SEPARATOR + fixed('0', fract) : "")); } return p; } /** * numbers with grouping characters * * @param dec count of decimals * @param fract count of fracts * @param fixed if true, fixed characters will be used. * @return regular expression describing the given number. */ protected static final String numberWithGrouping(int dec, int fract, boolean fixed) { final StringBuilder p = new StringBuilder("([-])|([-]{0,1}"); /* * involve grouping separators (3-digits-blocks). * if text input don't contain fractions, fractions will be added automated, * so, the decimal part should not exceed length of dec. */ dec -= fract; // a len of 0 will be changed to 1! if (dec <= 0) dec = 1; final int r = dec % 3; if (r > 0) p.append("\\d{0," + r + "}" + "[" + GROUPING_SEPARATOR + "]?"); final String a = "\\d{0,3}" + "[" + GROUPING_SEPARATOR + "]?"; final int c = (dec - r) / 3; for (int i = 0; i < c; i++) { p.append(a); } p.append((fract > 0 ? "([" + DECIMAL_SEPARATOR + "]" + "[0-9]{0," + fract + "}){0,1})" : ")")); if (fixed) { systemInitMap.put(p.toString(), fixed('0', dec) + (fract > 0 ? DECIMAL_SEPARATOR + fixed('0', fract) : "")); } return p.toString(); } /** * @param count count of characters * @param alphaonly if true, only alpha numerics are allowed * @return regular expression describing the given text specifications */ public static final String alphanum(int count, boolean alphaonly) { return (alphaonly ? "[a-zA-Z]" : PATTERN_SINGLE_BYTE_SPACE) + "{0," + count + "}"; } /** * @param c characters to use * @param count count to duplicate the character c. * @return string with length count containing the characters c. */ private static final String fixed(char c, int count) { final StringBuffer buf = new StringBuffer(count); for (int i = 0; i < count; i++) { buf.append(c); } return buf.toString(); } /** * provides a pattern defining numeric blocks, separated by spaces. e.g.: '0123 4567 8901'. this is an example with * blockwidth=4 and mincount=maxcount=3. * * @param blockwidth numeric block width * @param mincount minimum count of blocks * @param maxcount maximum count of blocks * @param allowRest if true, the last block may have less than blockwith numbers (e.g.: '0123 4567 89') * @return regular expression defining numeric blocks */ public static final String patternNumericBlock(int blockwidth, int mincount, int maxcount, boolean allowRest) { String pattern = "([0-9]{blockwidth,blockwidth} ){minCount,maxCount}" + (allowRest ? "[0-9]{0,blockwidth}" : ""); pattern = StringUtil.insertProperties(pattern, MapUtil.asMap("blockwidth", blockwidth, "mincount", mincount, "maxcount", maxcount)); return pattern; } /** * creates a standard regular expression for a currency. the biggest currency will be 999,999,999.99 * * @return new formatter for a currency */ public static RegExpFormat createCurrencyRegExp() { final int dec = 11, fract = 2; return new RegExpFormat(numberWithGrouping(dec, fract, false) + getCurrencyPostfix(), null, dec + fract + 3,//28082012ts: 1 -->3 to enable a number like 123456789 --> 123.456.789,00 € 0, getCurrencyFormat()); } /** * creates a standard regular expression for a currency without showing the currency symbol * * @return new formatter for a currency */ public static RegExpFormat createCurrencyNoSymbol() { final int dec = 11, fract = 2; return new RegExpFormat(numberWithGrouping(dec, fract, false) + "", null, dec + fract + 3,//28082012ts: 1 -->3 to enable a number like 123456789 --> 123.456.789,00 € 0, getCurrencyFormatNoSymbol()); } /** * creates a standard regular expression for a number, without position after decimal point. * * @return new formatter for the given number */ public static RegExpFormat createCurrencyNoFraction() { final int dec = 11, fract = 0; return new RegExpFormat(numberWithGrouping(dec - 2, fract, false) + getCurrencyPostfix(), null, dec + fract + 3,//28082012ts: 1 -->3 to enable a number like 123456789 --> 123.456.789,00 € 0, getCurrencyFormatNoFraction()); } /** * creates a regular expression for big currencies without decimal place. the biggest currency will be * 999,999,999,999,999 * * @return new formatter for a currency */ public static RegExpFormat createBigCurrencyRegExp() { final int dec = 17, fract = 0; return new RegExpFormat(numberWithGrouping(dec - 2, fract, false) + getCurrencyPostfix(), null, dec + fract + 3,//28082012ts: 1 -->3 to enable a number like 123456789 --> 123.456.789,00 € 0, getCurrencyFormat()); } /** * creates a regular expresssion for the given type of number * * @param numberType type of number (having scale and precision) * @return new formatter */ public static RegExpFormat createNumberRegExp(BigDecimal decimal) { assert decimal != null : "decimal must not be null!"; //the scale is the scaled fraction digit count final int dec = decimal.scale(); //the precision is the unscaled fraction digit count final int fract = decimal.precision(); return createNumberRegExp(10, dec + fract); } /** * delegates to {@link #createNumberRegExp(int, int, Class)}. */ public static RegExpFormat createNumberRegExp(int dec, int fract) { return createNumberRegExp(dec, fract, null); } /** * creates a standard regular expression for a number. * * @param dec count of decimals * @param fract count of fracts * @param type type of number (Integer, Double, BigDecimal, primitives..) * @return new formatter for the given number */ public static RegExpFormat createNumberRegExp(int dec, int fract, Class type) { return new RegExpFormat(numberWithGrouping(dec, fract, false), null, dec + fract + 1, 0, new GenericParser(type, null, null, fract)); } /** * Create a new alpha-numerical RegExpFormat. * * @param count count of characters * @param alphaonly if true, only alpha numerics are allowed (but e.g. no spaces) * @return new formatter for the given text specifications */ public static RegExpFormat createAlphaNumRegExp(int count, boolean alphaonly) { return new RegExpFormat(alphanum(count, alphaonly), count, count > 100 ? Pattern.MULTILINE : 0); } /** * createDateRegExp * * @return regexp for a german date */ public static RegExpFormat createDateRegExp() { RegExpFormat regExp = new RegExpFormat(FORMAT_DATE_DE, DateFormat.getDateInstance().format(getInitialDate()), 10, 0, new GenericParser(Date.class)); return regExp; } /** * createDateRegExp * * @return regexp for a german time */ public static RegExpFormat createTimeRegExp() { RegExpFormat regExp = new RegExpFormat(FORMAT_TIME, DateFormat.getTimeInstance().format(getInitialDate()), 8, 0, new GenericParser(Time.class)); return regExp; } /** * createDateRegExp * * @return regexp for a german date-time */ public static RegExpFormat createDateTimeRegExp() { RegExpFormat regExp = new RegExpFormat(FORMAT_DATETIME_DE, DateFormat.getDateTimeInstance() .format(getInitialDate()), 19, 0, new GenericParser(Timestamp.class)); return regExp; } /** * Build a RegExpFormat for the given pattern. Note that the pattern MUST NOT contain length checks, this is added * during construction (a suffix "{minLength, maxLength}" is added to the given pattern). * * @param pattern the pattern * @param init optional init pattern * @param minLength the minimum length * @param maxLength maximum field length * @param regExpFlags additional Flags for the regular expression * @return the formatter * @see Pattern Pattern for Flags description */ public static RegExpFormat createPatternRegExp(String pattern, String init, int minLength, int maxLength, int regExpFlags) { assert minLength >= 0 : "minLength must be >= 0"; assert maxLength >= minLength : "maxLength must be >= minLength"; return new RegExpFormat(pattern + "{" + minLength + "," + maxLength + "}", init, maxLength, regExpFlags); } /** * Create a new RegExpFormat that only controls the length of the input. * * @param minLength the minimum length * @param maxLength maximum field length * @param regExpFlags additional Flags for the regular expression * @return the formatter * @see Pattern Pattern for Flags description */ public static RegExpFormat createLengthRegExp(int minLength, int maxLength, int regExpFlags) { assert minLength >= 0 : "minLength must be >= 0"; assert maxLength >= minLength : "maxLength must be >= minLength"; return new RegExpFormat(".{" + minLength + "," + maxLength + "}", maxLength, regExpFlags); } /** * {@inheritDoc} */ @Override public String toString() { return this.getClass().getSimpleName() + "[pattern: " + pattern + "]"; } /** * getDefaultFormatter * * @return */ public Format getDefaultFormatter() { return parser; } /** * @param obj object to format with a new default formatter */ protected void setDefaultFormatter(Object obj) { this.parser = new GenericParser(FormatUtil.getDefaultFormat(obj, false), obj.getClass()); } /** * append all following format characters (like ./:) of inputmask to the end of source. * * @param source source text * @param initmask initmask for source text * @return source and following format characters */ public static final String getTextWithCaretJump(String source, String initmask) { final StringBuffer textWithCaretJump = new StringBuffer(source); final int i = getNextInputPosition(source.length(), initmask); if (source.length() < initmask.length() && i > source.length()) { textWithCaretJump.append(initmask.substring(source.length(), i)); } else { LOG.warn("initmask '" + initmask + "' is to short for text '" + source + "'"); } return textWithCaretJump.toString(); } /** * tries to jump over all format characters (see initmask) * * @param currentPosition current caret position * @return the next input position */ public int getNextInputPosition(int currentPosition) { return getNextInputPosition(currentPosition, getInitMask()); } /** * tries to jump over all format characters (see initmask) * * @param currentPosition current caret position * @param initmask mask, holding the formatting characters * @return the next input position */ private static int getNextInputPosition(int currentPosition, String initmask) { if (initmask != null) { while (currentPosition < initmask.length()) { if (!FORMAT_CHARACTERS.contains(initmask.substring(currentPosition, currentPosition + 1))) { break; } currentPosition++; } } return currentPosition; } /** * getTextFormatted * * @param newText new text * @return new text formatted with caret jumps */ public String getTextFormatted(String newText) { final StringBuffer textWithCaretJump = new StringBuffer(getMaxCharacterCount()); final String initmask = getInitMask(); if (initmask != null) { int ti = 0; for (int i = 0; i < initmask.length(); i++) { final String mc = initmask.substring(i, i + 1); if (FORMAT_CHARACTERS.contains(mc) && (newText.length() <= ti || newText.charAt(ti) != mc.charAt(0))) { textWithCaretJump.append(mc); continue; } else if (newText.length() <= ti) { //no formatting char and source end reached break; } textWithCaretJump.append(newText.charAt(ti)); ti++; } } else { textWithCaretJump.append(newText); } return textWithCaretJump.toString(); } /** * formatWithCaretJump * * @param newText new text * @return new text formatted with caret jumps */ public String formatWithCaretJump(String newText) { return format(getTextFormatted(newText), false); } /** * tries to evaluate, if the formatter is a simple one, or one, containing separation characters like in a date. * * @return true, if initmask contains at least one format character defined in {@link #FORMAT_CHARACTERS}. */ public boolean hasSeparationCharacter() { if (getInitMask() == null) { return false; } final char[] initMask = getInitMask().toCharArray(); for (int i = 0; i < initMask.length; i++) { if (FORMAT_CHARACTERS.contains(String.valueOf(initMask[i]))) { return true; } } return false; } /** * @return Returns the pattern. */ public String getPattern() { return pattern; } /** * setPattern * * @param pattern The pattern to set. * @param init initializing (default) string * @param maxLength max char count * @param regExpFlags reg exp flags */ public void setPattern(String pattern, String init, int maxLength, int regExpFlags) { init(pattern, init, maxLength, regExpFlags); } /** * @return Returns the regExpFlags. */ public int getRegExpFlags() { return regExpFlags; } /** * getInitMask * * @return init mask */ public String getInitMask() { return getInitMask(null); } /** * is able to return a source specific init-mask. needed e.g. for dates. is called by parsing the source to an * object to test the correctness. * * @param source (optional) source. if null, the init-mask stored for the pattern will be returned * @return init mask */ protected String getInitMask(String source) { String mask = null; if (source != null) { mask = initMap.get(source); } return mask != null ? mask : initMap.get(pattern); } /** * @return Returns the isAbleToParse. */ public boolean isAbleToParse() { return isAbleToParse; } /** * @param isAbleToParse The isAbleToParse to set. */ public void setAbleToParse(boolean isAbleToParse) { this.isAbleToParse = isAbleToParse; } /** * if a format, like the currency (see {@link DecimalFormat#getNegativeSuffix()}, needs a number suffix, we have to * give it to the parsing process. * * @param format format to ask * @return empty or filled suffix */ protected static String getParsingSuffix(Format format, String txt) { if (format instanceof GenericParser) { format = ((GenericParser) format).getParsingFormat(); } //TODO: its a workaround to handle the currency format. use the positive/negative prefix and suffix if (format instanceof DecimalFormat) { final StringBuffer s = new StringBuffer(); final DecimalFormat decFormat = (DecimalFormat) format; final String prefix = decFormat.getNegativePrefix(); if (txt.equals(prefix)) { s.append("0"); } final String suffix = decFormat.getNegativeSuffix(); if (!txt.contains(suffix)) { return s.append(suffix).toString(); } else { return s.toString(); } } else { return ""; } } /** * isAbleToParse * * @param format format * @return true, if format can create an object from string */ public static boolean isAbleToParse(Format format) { return !(format instanceof RegExpFormat) || ((RegExpFormat) format).isAbleToParse(); } /** * on default this method returns true, if the underlying {@link #parser} is a {@link NumberFormat}. *

* override this method if you have a {@link #parser} that is not of type {@link NumberFormat} but you want a field * to displayed like a number (e.g. in a table-column as right-alignment). * * @return true, if this format instance asserts to be a number format */ @Override public boolean isNumber() { return parser.isNumber(); } /** * convenience to check, if the given format is an instance of {@link RegExpFormat} and * {@link RegExpFormat#isNumber} returns true. * * @param format format to evaluate * @return true, if format is - or contains - a number format */ public static boolean isNumber(Format format) { return (format instanceof NumberFormat) || (format instanceof INumberFormatCheck && ((INumberFormatCheck) format).isNumber()); } /** * getCurrencyPostfix * * @return regexp for currency postfix */ protected static final String getCurrencyPostfix() { return getCurrencyPostfix(NumberFormat.getCurrencyInstance().getCurrency().getSymbol()); } /** * getCurrencyPostfix * * @param locale currency locale * @return regexp for currency postfix */ protected static final String getCurrencyPostfix(Locale locale) { return getCurrencyPostfix(NumberFormat.getCurrencyInstance(locale).getCurrency().getSymbol()); } /** * getCurrencyPostfix * * @param symbol currency symbol * @return regexp for currency postfix */ protected static final String getCurrencyPostfix(String symbol) { return "( " + symbol + "){0,1}"; } /** * currency with currency default precision (normally:2). object types must be {@link BigDecimal}! * * @return standard currency regular expression for current locale */ public static final Format getCurrencyFormat(Currency c) { return CurrencyUtil.getFormat(c.getCurrencyCode(), c.getDefaultFractionDigits()); } /** * currency with currency default precision (normally:2). object types must be {@link BigDecimal}! * * @return standard currency regular expression for current locale */ public static final Format getCurrencyFormat() { Currency c = NumberFormat.getCurrencyInstance().getCurrency(); return getCurrencyFormat(c); } /** * creates a {@link Format} that is able to format and parse the given type. useful on text-fields representing an * object. *

* if you give a converter, the formatting/parsing will be done in two steps:
* - convert a string to the attributes type - search the right bean through this filled attribute *

* Example:
* A person has an id of type long. the id will be shown with a mask, containing spaces. the formatted string '100 * 000 00000' must be converted to a long 10000000000. through an example person with given id, a real database * object will be found. *

* Example-Code: * *

     *         //string to long converter
     *         IConverter converter = new IConverter() {
     *             Override
     *             public Object from(Object toValue) {
     *                 return SteuerakteUtil.getFormattedStnr(toValue);
     *             }
     * 
     *             Override
     *             public Object to(Object fromValue) {
     *                 return SteuerakteUtil.getFormattedStnrAsLong((String) fromValue);
     *             }
     *         };
     *         return RegularExpressionFormat.getParser(Person.class,
     *             PersonConst.ATTR_PERSID,
     *             "[0-9]{3,3} [0-9]{3,3} [0-9]{5,5}",
     *             "000 000 00000",
     *             converter);
     * 
* * @param field object type * @param type field type * @param uniqueIdAttribute attribute name to resolve field object * @param pattern regular expression to constrain the object-to-string representation * @param initMask default representing string for empty objects. the string length will be used to check for * completition. * @param converter (optional) converts a string to the attribute type (e.g.: the string '100 000 00000' will be * converted to long 10000000000). * @param useCache if true, all loaded objects will be cached. * @return new {@link RegExpFormat} */ public static RegExpFormat getParser(final Class type, final String uniqueIdAttribute, String pattern, final String initMask, final IConverter converter, final boolean useCache) { final RegExpFormat format = new RegExpFormat(pattern, initMask) { /** serialVersionUID */ private static final long serialVersionUID = 1L; final TYPE instance = BeanClass.getBeanClass(type).createInstance(); final BeanAttribute attribute = BeanAttribute.getBeanAttribute(type, uniqueIdAttribute); final ReferenceMap cache = useCache ? new ReferenceMap() : null; @Override public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { if (type.isAssignableFrom(obj.getClass())) { Object strObj = converter != null ? converter.from(attribute.getValue(obj)) : attribute.getValue(obj); return toAppendTo.append(strObj); } else { // on verifyText, it will be a string return super.format(obj, toAppendTo, pos); } } @Override public Object parseObject(String source) throws ParseException { final String formattedSource = (String) super.parseObject(source); if (formattedSource == null) { return null; } Object convSource = converter != null ? converter.to(formattedSource) : formattedSource; attribute.setValue(instance, convSource); if (source.length() < initMask.length() || formattedSource.equals(getInitMask())) { return instance; } if (useCache && cache.containsKey(convSource)) { return cache.get(convSource); } Collection result = BeanContainer.instance().getBeansByExample(instance); if (result.size() > 0) { Object resObject = result.iterator().next(); if (useCache) { cache.put(convSource, resObject); } return resObject; } return null; } @Override public boolean isAbleToParse() { return true; } }; format.setAbleToParse(true); return format; } /** * Extension for {@link Serializable} */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { initSerialization(); out.defaultWriteObject(); } @Persist private void initSerialization() { //remove copied entries of systeminitmap - if it is not the systemInitMap itself! if (initMap != null && initMap != systemInitMap) { MapUtil.removeAll(initMap, systemInitMap.keySet()); } else { initMap = null; } } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); initDeserialization(); } @Commit @Complete private void initDeserialization() { if (initMap == null) { initMap = systemInitMap; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy