001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.util;
042:
043: import java.text.DateFormat;
044: import java.text.FieldPosition;
045: import java.text.Format;
046: import java.text.MessageFormat;
047: import java.text.NumberFormat;
048: import java.text.ParsePosition;
049:
050: import java.util.Date;
051: import java.util.Iterator;
052: import java.util.Locale;
053: import java.util.Map;
054:
055: /** A text format similar to <code>MessageFormat</code>
056: * but using string rather than numeric keys.
057: * You might use use this formatter like this:
058: * <pre>MapFormat.format("Hello {name}", map);</pre>
059: * Or to have more control over it:
060: * <pre>
061: * Map m = new HashMap ();
062: * m.put ("KEY", "value");
063: * MapFormat f = new MapFormat (m);
064: * f.setLeftBrace ("__");
065: * f.setRightBrace ("__");
066: * String result = f.format ("the __KEY__ here");
067: * </pre>
068: *
069: * @author Slavek Psenicka
070: * @see MessageFormat
071: */
072: public class MapFormat extends Format {
073: private static final int BUFSIZE = 255;
074:
075: /** Array with to-be-skipped blocks */
076:
077: //private RangeList skipped;
078: static final long serialVersionUID = -7695811542873819435L;
079:
080: /** Locale region settings used for number and date formatting */
081: private Locale locale = Locale.getDefault();
082:
083: /** Left delimiter */
084: private String ldel = "{"; // NOI18N
085:
086: /** Right delimiter */
087: private String rdel = "}"; // NOI18N
088:
089: /** Used formatting map */
090: private Map argmap;
091:
092: /** Offsets to {} expressions */
093: private int[] offsets;
094:
095: /** Keys enclosed by {} brackets */
096: private String[] arguments;
097:
098: /** Max used offset */
099: private int maxOffset;
100:
101: /** Should be thrown an exception if key was not found? */
102: private boolean throwex = false;
103:
104: /** Exactly match brackets? */
105: private boolean exactmatch = true;
106:
107: /**
108: * Constructor.
109: * For common work use <code>format(pattern, arguments) </code>.
110: * @param arguments keys and values to use in the format
111: */
112: public MapFormat(Map arguments) {
113: super ();
114: setMap(arguments);
115: }
116:
117: /**
118: * Designated method. It gets the string, initializes HashFormat object
119: * and returns converted string. It scans <code>pattern</code>
120: * for {} brackets, then parses enclosed string and replaces it
121: * with argument's <code>get()</code> value.
122: * @param pattern String to be parsed.
123: * @param arguments Map with key-value pairs to replace.
124: * @return Formatted string
125: */
126: public static String format(String pattern, Map arguments) {
127: MapFormat temp = new MapFormat(arguments);
128:
129: return temp.format(pattern);
130: }
131:
132: // unused so removed --jglick
133:
134: /**
135: * Search for comments and quotation marks.
136: * Prepares internal structures.
137: * @param pattern String to be parsed.
138: * @param lmark Left mark of to-be-skipped block.
139: * @param rmark Right mark of to-be-skipped block or null if does not exist (// comment).
140: private void process(String pattern, String lmark, String rmark)
141: {
142: int idx = 0;
143: while (true) {
144: int ridx = -1, lidx = pattern.indexOf(lmark,idx);
145: if (lidx >= 0) {
146: if (rmark != null) {
147: ridx = pattern.indexOf(rmark,lidx + lmark.length());
148: } else ridx = pattern.length();
149: } else break;
150: if (ridx >= 0) {
151: skipped.put(new Range(lidx, ridx-lidx));
152: if (rmark != null) idx = ridx+rmark.length();
153: else break;
154: } else break;
155: }
156: }
157: */
158: /** Returns the value for given key. Subclass may define its own beahvior of
159: * this method. For example, if key is not defined, subclass can return <not defined>
160: * string.
161: *
162: * @param key Key.
163: * @return Value for this key.
164: */
165: protected Object processKey(String key) {
166: return argmap.get(key);
167: }
168:
169: /**
170: * Scans the pattern and prepares internal variables.
171: * @param newPattern String to be parsed.
172: * @exception IllegalArgumentException if number of arguments exceeds BUFSIZE or
173: * parser found unmatched brackets (this exception should be switched off
174: * using setExactMatch(false)).
175: */
176: public String processPattern(String newPattern)
177: throws IllegalArgumentException {
178: int idx = 0;
179: int offnum = -1;
180: StringBuffer outpat = new StringBuffer();
181: offsets = new int[BUFSIZE];
182: arguments = new String[BUFSIZE];
183: maxOffset = -1;
184:
185: //skipped = new RangeList();
186: // What was this for??
187: //process(newPattern, "\"", "\""); // NOI18N
188: while (true) {
189: int ridx = -1;
190: int lidx = newPattern.indexOf(ldel, idx);
191:
192: /*
193: Range ran = skipped.getRangeContainingOffset(lidx);
194: if (ran != null) {
195: outpat.append(newPattern.substring(idx, ran.getEnd()));
196: idx = ran.getEnd(); continue;
197: }
198: */
199: if (lidx >= 0) {
200: ridx = newPattern.indexOf(rdel, lidx + ldel.length());
201: } else {
202: break;
203: }
204:
205: if (++offnum >= BUFSIZE) {
206: throw new IllegalArgumentException(NbBundle.getBundle(
207: MapFormat.class).getString(
208: "MSG_TooManyArguments"));
209: }
210:
211: if (ridx < 0) {
212: if (exactmatch) {
213: throw new IllegalArgumentException(NbBundle
214: .getBundle(MapFormat.class).getString(
215: "MSG_UnmatchedBraces")
216: + " " + lidx);
217: } else {
218: break;
219: }
220: }
221:
222: outpat.append(newPattern.substring(idx, lidx));
223: offsets[offnum] = outpat.length();
224: arguments[offnum] = newPattern.substring(lidx
225: + ldel.length(), ridx);
226: idx = ridx + rdel.length();
227: maxOffset++;
228: }
229:
230: outpat.append(newPattern.substring(idx));
231:
232: return outpat.toString();
233: }
234:
235: /**
236: * Formats object.
237: * @param obj Object to be formatted into string
238: * @return Formatted object
239: */
240: private String formatObject(Object obj) {
241: if (obj == null) {
242: return null;
243: }
244:
245: if (obj instanceof Number) {
246: return NumberFormat.getInstance(locale).format(obj); // fix
247: } else if (obj instanceof Date) {
248: return DateFormat.getDateTimeInstance(DateFormat.SHORT,
249: DateFormat.SHORT, locale).format(obj); //fix
250: } else if (obj instanceof String) {
251: return (String) obj;
252: }
253:
254: return obj.toString();
255: }
256:
257: /**
258: * Formats the parsed string by inserting table's values.
259: * @param pat a string pattern
260: * @param result Buffer to be used for result.
261: * @param fpos position
262: * @return Formatted string
263: */
264: public StringBuffer format(Object pat, StringBuffer result,
265: FieldPosition fpos) {
266: String pattern = processPattern((String) pat);
267: int lastOffset = 0;
268:
269: for (int i = 0; i <= maxOffset; ++i) {
270: int offidx = offsets[i];
271: result.append(pattern.substring(lastOffset, offsets[i]));
272: lastOffset = offidx;
273:
274: String key = arguments[i];
275: String obj;
276: if (key.length() > 0) {
277: obj = formatObject(processKey(key));
278: } else {
279: // else just copy the left and right braces
280: result.append(this .ldel);
281: result.append(this .rdel);
282: continue;
283: }
284:
285: if (obj == null) {
286: // try less-greedy match; useful for e.g. PROP___PROPNAME__ where
287: // 'PROPNAME' is a key and delims are both '__'
288: // this does not solve all possible cases, surely, but it should catch
289: // the most common ones
290: String lessgreedy = ldel + key;
291: int fromright = lessgreedy.lastIndexOf(ldel);
292:
293: if (fromright > 0) {
294: String newkey = lessgreedy.substring(fromright
295: + ldel.length());
296: String newsubst = formatObject(processKey(newkey));
297:
298: if (newsubst != null) {
299: obj = lessgreedy.substring(0, fromright)
300: + newsubst;
301: }
302: }
303: }
304:
305: if (obj == null) {
306: if (throwex) {
307: throw new IllegalArgumentException(MessageFormat
308: .format(NbBundle.getBundle(MapFormat.class)
309: .getString("MSG_FMT_ObjectForKey"),
310: new Object[] { new Integer(key) }));
311: } else {
312: obj = ldel + key + rdel;
313: }
314: }
315:
316: result.append(obj);
317: }
318:
319: result.append(pattern.substring(lastOffset, pattern.length()));
320:
321: return result;
322: }
323:
324: /**
325: * Parses the string. Does not yet handle recursion (where
326: * the substituted strings contain %n references.)
327: */
328: public Object parseObject(String text, ParsePosition status) {
329: return parse(text);
330: }
331:
332: /**
333: * Parses the string. Does not yet handle recursion (where
334: * the substituted strings contain {n} references.)
335: * @return New format.
336: */
337: public String parse(String source) {
338: StringBuffer sbuf = new StringBuffer(source);
339: Iterator key_it = argmap.keySet().iterator();
340:
341: //skipped = new RangeList();
342: // What was this for??
343: //process(source, "\"", "\""); // NOI18N
344: while (key_it.hasNext()) {
345: String it_key = (String) key_it.next();
346: String it_obj = formatObject(argmap.get(it_key));
347: int it_idx = -1;
348:
349: do {
350: it_idx = sbuf.toString().indexOf(it_obj, ++it_idx);
351:
352: if (it_idx >= 0 /* && !skipped.containsOffset(it_idx) */) {
353: sbuf.replace(it_idx, it_idx + it_obj.length(), ldel
354: + it_key + rdel);
355:
356: //skipped = new RangeList();
357: // What was this for??
358: //process(sbuf.toString(), "\"", "\""); // NOI18N
359: }
360: } while (it_idx != -1);
361: }
362:
363: return sbuf.toString();
364: }
365:
366: /** Test whether formatter will throw exception if object for key was not found.
367: * If given map does not contain object for key specified, it could
368: * throw an exception. Returns true if throws. If not, key is left unchanged.
369: */
370: public boolean willThrowExceptionIfKeyWasNotFound() {
371: return throwex;
372: }
373:
374: /** Specify whether formatter will throw exception if object for key was not found.
375: * If given map does not contain object for key specified, it could
376: * throw an exception. If does not throw, key is left unchanged.
377: * @param flag If true, formatter throws IllegalArgumentException.
378: */
379: public void setThrowExceptionIfKeyWasNotFound(boolean flag) {
380: throwex = flag;
381: }
382:
383: /** Test whether both brackets are required in the expression.
384: * If not, use setExactMatch(false) and formatter will ignore missing right
385: * bracket. Advanced feature.
386: */
387: public boolean isExactMatch() {
388: return exactmatch;
389: }
390:
391: /** Specify whether both brackets are required in the expression.
392: * If not, use setExactMatch(false) and formatter will ignore missing right
393: * bracket. Advanced feature.
394: * @param flag If true, formatter will ignore missing right bracket (default = false)
395: */
396: public void setExactMatch(boolean flag) {
397: exactmatch = flag;
398: }
399:
400: /** Returns string used as left brace */
401: public String getLeftBrace() {
402: return ldel;
403: }
404:
405: /** Sets string used as left brace
406: * @param delimiter Left brace.
407: */
408: public void setLeftBrace(String delimiter) {
409: ldel = delimiter;
410: }
411:
412: /** Returns string used as right brace */
413: public String getRightBrace() {
414: return rdel;
415: }
416:
417: /** Sets string used as right brace
418: * @param delimiter Right brace.
419: */
420: public void setRightBrace(String delimiter) {
421: rdel = delimiter;
422: }
423:
424: /** Returns argument map */
425: public Map getMap() {
426: return argmap;
427: }
428:
429: /** Sets argument map
430: * This map should contain key-value pairs with key values used in
431: * formatted string expression. If value for key was not found, formatter leave
432: * key unchanged (except if you've set setThrowExceptionIfKeyWasNotFound(true),
433: * then it fires IllegalArgumentException.
434: *
435: * @param map the argument map
436: */
437: public void setMap(Map map) {
438: argmap = map;
439: }
440:
441: // commented out because unused --jglick
442:
443: /**
444: * Range of expression in string.
445: * Used internally to store information about quotation marks and comments
446: * in formatted string.
447: *
448: * @author Slavek Psenicka
449: * @version 1.0, March 11. 1999
450: *
451: class Range extends Object
452: {
453: /** Offset of expression *
454: private int offset;
455:
456: /** Length of expression *
457: private int length;
458:
459: /** Constructor *
460: public Range(int off, int len)
461: {
462: offset = off;
463: length = len;
464: }
465:
466: /** Returns offset *
467: public int getOffset()
468: {
469: return offset;
470: }
471:
472: /** Returns length of expression *
473: public int getLength()
474: {
475: return length;
476: }
477:
478: /** Returns final position of expression *
479: public int getEnd()
480: {
481: return offset+length;
482: }
483:
484: public String toString()
485: {
486: return "("+offset+", "+length+")"; // NOI18N
487: }
488: }
489:
490: /**
491: * List of ranges.
492: * Used internally to store information about quotation marks and comments
493: * in formatted string.
494: *
495: * @author Slavek Psenicka
496: * @version 1.0, March 11. 1999
497: *
498: class RangeList
499: {
500: /** Map with Ranges *
501: private HashMap hmap;
502:
503: /** Constructor *
504: public RangeList()
505: {
506: hmap = new HashMap();
507: }
508:
509: /** Returns true if offset is enclosed by any Range object in list *
510: public boolean containsOffset(int offset)
511: {
512: return (getRangeContainingOffset(offset) != null);
513: }
514:
515: /** Returns enclosing Range object in list for given offset *
516: public Range getRangeContainingOffset(int offset)
517: {
518: if (hmap.size() == 0) return null;
519: int offit = offset;
520: while (offit-- >= 0) {
521: Integer off = new Integer(offit);
522: if (hmap.containsKey(off)) {
523: Range ran = (Range)hmap.get(off);
524: if (ran.getEnd() - offset > 0) return ran;
525: }
526: }
527:
528: return null;
529: }
530:
531: /** Puts new range into list *
532: public void put(Range range)
533: {
534: hmap.put(new Integer(range.getOffset()), range);
535: }
536:
537: public String toString()
538: {
539: return hmap.toString();
540: }
541: }
542: */
543: }
|