A text format similar to MessageFormat but using string rather than numeric keys.
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ import java.text.DateFormat; import java.text.FieldPosition; import java.text.Format; import java.text.MessageFormat; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.Date; import java.util.Iterator; import java.util.Locale; import java.util.Map; /** A text format similar to <code>MessageFormat</code> * but using string rather than numeric keys. * You might use use this formatter like this: * <pre>MapFormat.format("Hello {name}", map);</pre> * Or to have more control over it: * <pre> * Map m = new HashMap (); * m.put ("KEY", "value"); * MapFormat f = new MapFormat (m); * f.setLeftBrace ("__"); * f.setRightBrace ("__"); * String result = f.format ("the __KEY__ here"); * </pre> * * @author Slavek Psenicka * @see MessageFormat */ public class MapFormat extends Format { private static final int BUFSIZE = 255; /** Array with to-be-skipped blocks */ //private RangeList skipped; static final long serialVersionUID = -7695811542873819435L; /** Locale region settings used for number and date formatting */ private Locale locale = Locale.getDefault(); /** Left delimiter */ private String ldel = "{"; // NOI18N /** Right delimiter */ private String rdel = "}"; // NOI18N /** Used formatting map */ private Map argmap; /** Offsets to {} expressions */ private int[] offsets; /** Keys enclosed by {} brackets */ private String[] arguments; /** Max used offset */ private int maxOffset; /** Should be thrown an exception if key was not found? */ private boolean throwex = false; /** Exactly match brackets? */ private boolean exactmatch = true; /** * Constructor. * For common work use <code>format(pattern, arguments) </code>. * @param arguments keys and values to use in the format */ public MapFormat(Map arguments) { super(); setMap(arguments); } /** * Designated method. It gets the string, initializes HashFormat object * and returns converted string. It scans <code>pattern</code> * for {} brackets, then parses enclosed string and replaces it * with argument's <code>get()</code> value. * @param pattern String to be parsed. * @param arguments Map with key-value pairs to replace. * @return Formatted string */ public static String format(String pattern, Map arguments) { MapFormat temp = new MapFormat(arguments); return temp.format(pattern); } // unused so removed --jglick /** * Search for comments and quotation marks. * Prepares internal structures. * @param pattern String to be parsed. * @param lmark Left mark of to-be-skipped block. * @param rmark Right mark of to-be-skipped block or null if does not exist (// comment). private void process(String pattern, String lmark, String rmark) { int idx = 0; while (true) { int ridx = -1, lidx = pattern.indexOf(lmark,idx); if (lidx >= 0) { if (rmark != null) { ridx = pattern.indexOf(rmark,lidx + lmark.length()); } else ridx = pattern.length(); } else break; if (ridx >= 0) { skipped.put(new Range(lidx, ridx-lidx)); if (rmark != null) idx = ridx+rmark.length(); else break; } else break; } } */ /** Returns the value for given key. Subclass may define its own beahvior of * this method. For example, if key is not defined, subclass can return <not defined> * string. * * @param key Key. * @return Value for this key. */ protected Object processKey(String key) { return argmap.get(key); } /** * Scans the pattern and prepares internal variables. * @param newPattern String to be parsed. * @exception IllegalArgumentException if number of arguments exceeds BUFSIZE or * parser found unmatched brackets (this exception should be switched off * using setExactMatch(false)). */ public String processPattern(String newPattern) throws IllegalArgumentException { int idx = 0; int offnum = -1; StringBuffer outpat = new StringBuffer(); offsets = new int[BUFSIZE]; arguments = new String[BUFSIZE]; maxOffset = -1; //skipped = new RangeList(); // What was this for?? //process(newPattern, "\"", "\""); // NOI18N while (true) { int ridx = -1; int lidx = newPattern.indexOf(ldel, idx); /* Range ran = skipped.getRangeContainingOffset(lidx); if (ran != null) { outpat.append(newPattern.substring(idx, ran.getEnd())); idx = ran.getEnd(); continue; } */ if (lidx >= 0) { ridx = newPattern.indexOf(rdel, lidx + ldel.length()); } else { break; } if (++offnum >= BUFSIZE) { throw new IllegalArgumentException( "TooManyArguments" ); } if (ridx < 0) { if (exactmatch) { throw new IllegalArgumentException( "UnmatchedBraces" ); } else { break; } } outpat.append(newPattern.substring(idx, lidx)); offsets[offnum] = outpat.length(); arguments[offnum] = newPattern.substring(lidx + ldel.length(), ridx); idx = ridx + rdel.length(); maxOffset++; } outpat.append(newPattern.substring(idx)); return outpat.toString(); } /** * Formats object. * @param obj Object to be formatted into string * @return Formatted object */ private String formatObject(Object obj) { if (obj == null) { return null; } if (obj instanceof Number) { return NumberFormat.getInstance(locale).format(obj); // fix } else if (obj instanceof Date) { return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format(obj); //fix } else if (obj instanceof String) { return (String) obj; } return obj.toString(); } /** * Formats the parsed string by inserting table's values. * @param pat a string pattern * @param result Buffer to be used for result. * @param fpos position * @return Formatted string */ public StringBuffer format(Object pat, StringBuffer result, FieldPosition fpos) { String pattern = processPattern((String) pat); int lastOffset = 0; for (int i = 0; i <= maxOffset; ++i) { int offidx = offsets[i]; result.append(pattern.substring(lastOffset, offsets[i])); lastOffset = offidx; String key = arguments[i]; String obj; if (key.length() > 0) { obj = formatObject(processKey(key)); } else { // else just copy the left and right braces result.append(this.ldel); result.append(this.rdel); continue; } if (obj == null) { // try less-greedy match; useful for e.g. PROP___PROPNAME__ where // 'PROPNAME' is a key and delims are both '__' // this does not solve all possible cases, surely, but it should catch // the most common ones String lessgreedy = ldel + key; int fromright = lessgreedy.lastIndexOf(ldel); if (fromright > 0) { String newkey = lessgreedy.substring(fromright + ldel.length()); String newsubst = formatObject(processKey(newkey)); if (newsubst != null) { obj = lessgreedy.substring(0, fromright) + newsubst; } } } if (obj == null) { if (throwex) { throw new IllegalArgumentException("ObjectForKey"); } else { obj = ldel + key + rdel; } } result.append(obj); } result.append(pattern.substring(lastOffset, pattern.length())); return result; } /** * Parses the string. Does not yet handle recursion (where * the substituted strings contain %n references.) */ public Object parseObject(String text, ParsePosition status) { return parse(text); } /** * Parses the string. Does not yet handle recursion (where * the substituted strings contain {n} references.) * @return New format. */ public String parse(String source) { StringBuffer sbuf = new StringBuffer(source); Iterator key_it = argmap.keySet().iterator(); //skipped = new RangeList(); // What was this for?? //process(source, "\"", "\""); // NOI18N while (key_it.hasNext()) { String it_key = (String) key_it.next(); String it_obj = formatObject(argmap.get(it_key)); int it_idx = -1; do { it_idx = sbuf.toString().indexOf(it_obj, ++it_idx); if (it_idx >= 0 /* && !skipped.containsOffset(it_idx) */ ) { sbuf.replace(it_idx, it_idx + it_obj.length(), ldel + it_key + rdel); //skipped = new RangeList(); // What was this for?? //process(sbuf.toString(), "\"", "\""); // NOI18N } } while (it_idx != -1); } return sbuf.toString(); } /** Test whether formatter will throw exception if object for key was not found. * If given map does not contain object for key specified, it could * throw an exception. Returns true if throws. If not, key is left unchanged. */ public boolean willThrowExceptionIfKeyWasNotFound() { return throwex; } /** Specify whether formatter will throw exception if object for key was not found. * If given map does not contain object for key specified, it could * throw an exception. If does not throw, key is left unchanged. * @param flag If true, formatter throws IllegalArgumentException. */ public void setThrowExceptionIfKeyWasNotFound(boolean flag) { throwex = flag; } /** Test whether both brackets are required in the expression. * If not, use setExactMatch(false) and formatter will ignore missing right * bracket. Advanced feature. */ public boolean isExactMatch() { return exactmatch; } /** Specify whether both brackets are required in the expression. * If not, use setExactMatch(false) and formatter will ignore missing right * bracket. Advanced feature. * @param flag If true, formatter will ignore missing right bracket (default = false) */ public void setExactMatch(boolean flag) { exactmatch = flag; } /** Returns string used as left brace */ public String getLeftBrace() { return ldel; } /** Sets string used as left brace * @param delimiter Left brace. */ public void setLeftBrace(String delimiter) { ldel = delimiter; } /** Returns string used as right brace */ public String getRightBrace() { return rdel; } /** Sets string used as right brace * @param delimiter Right brace. */ public void setRightBrace(String delimiter) { rdel = delimiter; } /** Returns argument map */ public Map getMap() { return argmap; } /** Sets argument map * This map should contain key-value pairs with key values used in * formatted string expression. If value for key was not found, formatter leave * key unchanged (except if you've set setThrowExceptionIfKeyWasNotFound(true), * then it fires IllegalArgumentException. * * @param map the argument map */ public void setMap(Map map) { argmap = map; } // commented out because unused --jglick /** * Range of expression in string. * Used internally to store information about quotation marks and comments * in formatted string. * * @author Slavek Psenicka * @version 1.0, March 11. 1999 * class Range extends Object { /** Offset of expression * private int offset; /** Length of expression * private int length; /** Constructor * public Range(int off, int len) { offset = off; length = len; } /** Returns offset * public int getOffset() { return offset; } /** Returns length of expression * public int getLength() { return length; } /** Returns final position of expression * public int getEnd() { return offset+length; } public String toString() { return "("+offset+", "+length+")"; // NOI18N } } /** * List of ranges. * Used internally to store information about quotation marks and comments * in formatted string. * * @author Slavek Psenicka * @version 1.0, March 11. 1999 * class RangeList { /** Map with Ranges * private HashMap hmap; /** Constructor * public RangeList() { hmap = new HashMap(); } /** Returns true if offset is enclosed by any Range object in list * public boolean containsOffset(int offset) { return (getRangeContainingOffset(offset) != null); } /** Returns enclosing Range object in list for given offset * public Range getRangeContainingOffset(int offset) { if (hmap.size() == 0) return null; int offit = offset; while (offit-- >= 0) { Integer off = new Integer(offit); if (hmap.containsKey(off)) { Range ran = (Range)hmap.get(off); if (ran.getEnd() - offset > 0) return ran; } } return null; } /** Puts new range into list * public void put(Range range) { hmap.put(new Integer(range.getOffset()), range); } public String toString() { return hmap.toString(); } } */ }