| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.openide.util; |
| |
| 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( |
| NbBundle.getBundle(MapFormat.class).getString("MSG_TooManyArguments") |
| ); |
| } |
| |
| if (ridx < 0) { |
| if (exactmatch) { |
| throw new IllegalArgumentException( |
| NbBundle.getBundle(MapFormat.class).getString("MSG_UnmatchedBraces") + " " + lidx |
| ); |
| } 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( |
| MessageFormat.format( |
| NbBundle.getBundle(MapFormat.class).getString("MSG_FMT_ObjectForKey"), |
| new Object[] { new Integer(key) } |
| ) |
| ); |
| } 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(); |
| } |
| } |
| */ |
| } |