001: /*
002: * $Id: FlexibleStringExpander.java,v 1.2 2003/09/21 05:58:51 jonesde Exp $
003: *
004: * Copyright (c) 2003 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: */
024: package org.ofbiz.base.util;
025:
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Locale;
030: import java.util.Map;
031:
032: /**
033: * Expands string values with in a Map context supporting the ${} syntax for
034: * variable placeholders and the "." (dot) and "[]" (square-brace) syntax
035: * elements for accessing Map entries and List elements in the context.
036: *
037: * @author <a href="mailto:jonesde@ofbiz.org">David E. Jones</a>
038: * @version $Revision: 1.2 $
039: * @since 2.2
040: */
041: public class FlexibleStringExpander {
042:
043: public static final String module = FlexibleStringExpander.class
044: .getName();
045:
046: protected String original;
047: protected List stringElements = new LinkedList();
048:
049: public FlexibleStringExpander(String original) {
050: this .original = original;
051:
052: ParseElementHandler handler = new PreParseHandler(
053: stringElements);
054: parseString(original, handler);
055: }
056:
057: public boolean isEmpty() {
058: if (this .original == null || this .original.length() == 0) {
059: return true;
060: } else {
061: return false;
062: }
063: }
064:
065: public String getOriginal() {
066: return this .original;
067: }
068:
069: /**
070: * This expands the pre-parsed String given the context passed in. Note that
071: * pre-parsing can only parse the top-level place-holders and if there are
072: * nested expansions they will be done on the fly instead of pre-parsed because
073: * they are dependent on the context which isn't known until expansion time.
074: *
075: * @param context A context Map containing the variable values
076: * @return The original String expanded by replacing varaible place holders.
077: */
078: public String expandString(Map context) {
079: return this .expandString(context, null);
080: }
081:
082: /**
083: * This expands the pre-parsed String given the context passed in. Note that
084: * pre-parsing can only parse the top-level place-holders and if there are
085: * nested expansions they will be done on the fly instead of pre-parsed because
086: * they are dependent on the context which isn't known until expansion time.
087: *
088: * @param context A context Map containing the variable values
089: * @return The original String expanded by replacing varaible place holders.
090: */
091: public String expandString(Map context, Locale locale) {
092: StringBuffer expanded = new StringBuffer();
093:
094: Iterator stringElementIter = stringElements.iterator();
095: while (stringElementIter.hasNext()) {
096: StringElement element = (StringElement) stringElementIter
097: .next();
098: element.appendElement(expanded, context, locale);
099: }
100:
101: //call back into this method with new String to take care of any/all nested expands
102: return expandString(expanded.toString(), context);
103: }
104:
105: /**
106: * Does on-the-fly parsing and expansion of the original String using
107: * varaible values from the passed context. Variables are denoted with
108: * the "${}" syntax and the variable name inside the curly-braces can use
109: * the "." (dot) syntax to access sub-Map entries and the "[]" square-brace
110: * syntax to access List elements.
111: *
112: * @param original The original String that will be expanded
113: * @param context A context Map containing the variable values
114: * @return The original String expanded by replacing varaible place holders.
115: */
116: public static String expandString(String original, Map context) {
117: return expandString(original, context, null);
118: }
119:
120: /**
121: * Does on-the-fly parsing and expansion of the original String using
122: * varaible values from the passed context. Variables are denoted with
123: * the "${}" syntax and the variable name inside the curly-braces can use
124: * the "." (dot) syntax to access sub-Map entries and the "[]" square-brace
125: * syntax to access List elements.
126: *
127: * @param original The original String that will be expanded
128: * @param context A context Map containing the variable values
129: * @return The original String expanded by replacing varaible place holders.
130: */
131: public static String expandString(String original, Map context,
132: Locale locale) {
133: // if null or less than 3 return original; 3 chars because that is the minimum necessary for a ${}
134: if (original == null || original.length() < 3) {
135: return original;
136: }
137:
138: // start by checking to see if expansion is necessary for better performance
139: // this is also necessary for the nested stuff since this will be the stopping point for nested expansions
140: int start = original.indexOf("${");
141: if (start == -1) {
142: return original;
143: } else {
144: if (original.indexOf("}", start) == -1) {
145: //no ending for the start, so we also have a stop condition
146: Debug.logWarning(
147: "Found a ${ without a closing } (curly-brace) in the String: "
148: + original, module);
149: return original;
150: }
151: }
152:
153: StringBuffer expanded = new StringBuffer();
154: ParseElementHandler handler = new OnTheFlyHandler(expanded,
155: context, locale);
156: parseString(original, handler);
157:
158: //call back into this method with new String to take care of any/all nested expands
159: return expandString(expanded.toString(), context);
160: }
161:
162: public static void parseString(String original,
163: ParseElementHandler handler) {
164: if (original == null || original.length() == 0) {
165: return;
166: }
167:
168: int start = original.indexOf("${");
169: if (start == -1) {
170: handler.handleConstant(original, 0);
171: return;
172: }
173: int currentInd = 0;
174: int end = -1;
175: while (start != -1) {
176: end = original.indexOf("}", start);
177: if (end == -1) {
178: Debug.logWarning(
179: "Found a ${ without a closing } (curly-brace) in the String: "
180: + original, module);
181: break;
182: }
183:
184: // check to see if there is a nested ${}, ie something like ${foo.${bar}} or ${foo[$bar}]}
185: // since we are only handling one at a time, and then recusively looking for nested ones, just look backward from the } for another ${ and if found and is not the same start spot, update the start spot
186: int possibleNestedStart = original.lastIndexOf("${", end);
187: if (start != possibleNestedStart) {
188: // found a nested one, could print something here, but just do the simple thing...
189: start = possibleNestedStart;
190: }
191:
192: // append everything from the current index to the start of the var
193: handler.handleConstant(original, currentInd, start);
194:
195: // get the environment value and append it
196: handler.handleVariable(original, start + 2, end);
197:
198: // reset the current index to after the var, and the start to the beginning of the next var
199: currentInd = end + 1;
200: start = original.indexOf("${", currentInd);
201: }
202:
203: // append the rest of the original string, ie after the last variable
204: if (currentInd < original.length()) {
205: handler.handleConstant(original, currentInd);
206: }
207: }
208:
209: public static interface StringElement {
210: public void appendElement(StringBuffer buffer, Map context,
211: Locale locale);
212: }
213:
214: public static class ConstantElement implements StringElement {
215: protected String value;
216:
217: public ConstantElement(String value) {
218: this .value = value;
219: }
220:
221: public void appendElement(StringBuffer buffer, Map context,
222: Locale locale) {
223: buffer.append(this .value);
224: }
225: }
226:
227: public static class VariableElement implements StringElement {
228: protected FlexibleMapAccessor fma;
229:
230: public VariableElement(String valueName) {
231: this .fma = new FlexibleMapAccessor(valueName);
232: }
233:
234: public void appendElement(StringBuffer buffer, Map context,
235: Locale locale) {
236: Object retVal = fma.get(context, locale);
237: if (retVal != null) {
238: buffer.append(retVal.toString());
239: } else {
240: // otherwise do nothing
241: }
242: }
243: }
244:
245: public static interface ParseElementHandler {
246: public void handleConstant(String original, int start);
247:
248: public void handleConstant(String original, int start, int end);
249:
250: public void handleVariable(String original, int start, int end);
251: }
252:
253: public static class PreParseHandler implements ParseElementHandler {
254: protected List stringElements;
255:
256: public PreParseHandler(List stringElements) {
257: this .stringElements = stringElements;
258: }
259:
260: public void handleConstant(String original, int start) {
261: stringElements.add(new ConstantElement(original
262: .substring(start)));
263: }
264:
265: public void handleConstant(String original, int start, int end) {
266: stringElements.add(new ConstantElement(original.substring(
267: start, end)));
268: }
269:
270: public void handleVariable(String original, int start, int end) {
271: stringElements.add(new VariableElement(original.substring(
272: start, end)));
273: }
274: }
275:
276: public static class OnTheFlyHandler implements ParseElementHandler {
277: protected StringBuffer targetBuffer;
278: protected Map context;
279: protected Locale locale;
280:
281: public OnTheFlyHandler(StringBuffer targetBuffer, Map context,
282: Locale locale) {
283: this .targetBuffer = targetBuffer;
284: this .context = context;
285: this .locale = locale;
286: }
287:
288: public void handleConstant(String original, int start) {
289: targetBuffer.append(original.substring(start));
290: }
291:
292: public void handleConstant(String original, int start, int end) {
293: targetBuffer.append(original.substring(start, end));
294: }
295:
296: public void handleVariable(String original, int start, int end) {
297: //get the environment value and append it
298: String envName = original.substring(start, end);
299: FlexibleMapAccessor fma = new FlexibleMapAccessor(envName);
300: Object envVal = fma.get(context, locale);
301: if (envVal != null) {
302: targetBuffer.append(envVal.toString());
303: } else {
304: Debug.logWarning(
305: "Could not find value in environment for the name ["
306: + envName + "], inserting nothing.",
307: module);
308: }
309: }
310: }
311: }
|