Mega Code Archive

 
Categories / Java / Internationalization
 

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();         }     }      */ }