001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.util;
019:
020: import org.apache.oro.text.perl.MalformedPerl5PatternException;
021: import org.apache.oro.text.perl.Perl5Util;
022: import org.w3c.dom.Attr;
023: import org.w3c.dom.Document;
024: import org.w3c.dom.Element;
025: import org.w3c.dom.NamedNodeMap;
026: import org.w3c.dom.NodeList;
027:
028: import javax.xml.parsers.DocumentBuilder;
029: import javax.xml.parsers.DocumentBuilderFactory;
030: import java.io.File;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.Map;
034:
035: /*
036: * This is derived from the SQLResources.java class that we have been
037: * using to provide SQL strings based upon the particular drive being
038: * used.
039: *
040: * The format is indentical to the format used by SQLResources. The
041: * only difference are the names of the elements and attributes. A
042: * mapping is as follows:
043: *
044: * sqlResources xmlResources
045: * ------------ ------------
046: * sqlResources resources
047: * dbMatchers matchers
048: * dbMatcher matcher
049: * db for
050: * databaseProductName match
051: * sqlDefs group
052: * sql resource
053: *
054: * This class provides String resources defined in XML. Resources are
055: * organized into groups, and identified by name. For each resource
056: * there can be a standard value, and custom values matched by regular
057: * expression.
058: *
059: * The structure of the XML file is:
060: *
061: * <resources>
062: * <matchers>
063: * <matcher for="<i>label</i>" match="<i>regular expression</i>"/>
064: * ...
065: * </matchers>
066: * <group name="<i>group name</i>">
067: * <resource name="<i>resouce name</i>" [for="<i>match label</i>"]><i>text, including ${placeholders}, which will be replaced at runtime.</i></resource>
068: * ...
069: * </group>
070: * <i>... more <group> elements ...</i>
071: * </resources>
072: *
073: */
074: public class XMLResources {
075: /**
076: * A map of statement types to resource strings
077: */
078: private Map m_resource = new HashMap();
079:
080: /**
081: * A set of all used String values
082: */
083: static private Map stringTable = java.util.Collections
084: .synchronizedMap(new HashMap());
085:
086: /**
087: * A Perl5 regexp matching helper class
088: */
089: private Perl5Util m_perl5Util = new Perl5Util();
090:
091: /**
092: * Configures an XMLResources object to provide string statements from a file.
093: *
094: * Parameters encoded as $(parameter} in the input file are
095: * replace by values from the parameters Map, if the named parameter exists.
096: * Parameter values may also be specified in the resourceSection element.
097: *
098: * @param xmlFile the input file containing the string definitions
099: * @param group xml element containing the strings to be used
100: * @param select if customized elements exist for this value, use them instead of the default
101: * @param configParameters a map of parameters (name-value string pairs) which are
102: * replaced where found in the input strings
103: */
104: public void init(File xmlFile, String group, String select,
105: Map configParameters) throws Exception {
106: // Parse the xmlFile as an XML document.
107: DocumentBuilderFactory factory = DocumentBuilderFactory
108: .newInstance();
109: DocumentBuilder builder = factory.newDocumentBuilder();
110: Document doc = builder.parse(xmlFile);
111:
112: // First process the matchers, to select the statements to use.
113: Element matcherElement = (Element) (doc
114: .getElementsByTagName("matchers").item(0));
115: String selectTag = null;
116: if (matcherElement != null) {
117: selectTag = match(select, matcherElement);
118: m_perl5Util = null; // release the PERL matcher!
119: }
120:
121: // Now get the section defining strings for the group required.
122: NodeList sections = doc.getElementsByTagName("group");
123: int sectionsCount = sections.getLength();
124: Element sectionElement = null;
125: for (int i = 0; i < sectionsCount; i++) {
126: sectionElement = (Element) (sections.item(i));
127: String sectionName = sectionElement.getAttribute("name");
128: if (sectionName != null && sectionName.equals(group)) {
129: break;
130: }
131:
132: }
133: if (sectionElement == null) {
134: StringBuffer exceptionBuffer = new StringBuffer(64).append(
135: "Error loading string definition file. ").append(
136: "The element named \'").append(group).append(
137: "\' does not exist.");
138: throw new RuntimeException(exceptionBuffer.toString());
139: }
140:
141: // Get parameters defined within the file as defaults,
142: // and use supplied parameters as overrides.
143: Map parameters = new HashMap();
144: // First read from the <params> element, if it exists.
145: Element parametersElement = (Element) (sectionElement
146: .getElementsByTagName("parameters").item(0));
147: if (parametersElement != null) {
148: NamedNodeMap params = parametersElement.getAttributes();
149: int paramCount = params.getLength();
150: for (int i = 0; i < paramCount; i++) {
151: Attr param = (Attr) params.item(i);
152: String paramName = param.getName();
153: String paramValue = param.getValue();
154: parameters.put(paramName, paramValue);
155: }
156: }
157: // Then copy in the parameters supplied with the call.
158: parameters.putAll(configParameters);
159:
160: // 2 maps - one for storing default statements,
161: // the other for statements with a "for" attribute matching this
162: // connection.
163: Map defaultStrings = new HashMap();
164: Map selectTagStrings = new HashMap();
165:
166: // Process each string resource, replacing string parameters,
167: // and adding to the appropriate map..
168: NodeList resDefs = sectionElement
169: .getElementsByTagName("resource");
170: int resCount = resDefs.getLength();
171: for (int i = 0; i < resCount; i++) {
172: // See if this needs to be processed (is default or product specific)
173: Element resElement = (Element) (resDefs.item(i));
174: String resSelect = resElement.getAttribute("for");
175: Map resMap;
176: if (resSelect.equals("")) {
177: // default
178: resMap = defaultStrings;
179: } else if (resSelect.equals(selectTag)) {
180: // Specific to this product
181: resMap = selectTagStrings;
182: } else {
183: // for a different product
184: continue;
185: }
186:
187: // Get the key and value for this string resource.
188: String resKey = resElement.getAttribute("name");
189: if (resKey == null) {
190: // ignore elements without a "name" attribute.
191: continue;
192: }
193: String resString = resElement.getFirstChild()
194: .getNodeValue();
195:
196: // Do parameter replacements for this string resource.
197: Iterator paramNames = parameters.keySet().iterator();
198: while (paramNames.hasNext()) {
199: String paramName = (String) paramNames.next();
200: String paramValue = (String) parameters.get(paramName);
201:
202: StringBuffer replaceBuffer = new StringBuffer(64)
203: .append("${").append(paramName).append("}");
204: resString = substituteSubString(resString,
205: replaceBuffer.toString(), paramValue);
206: }
207:
208: // See if we already have registered a string of this value
209: String shared = (String) stringTable.get(resString);
210: // If not, register it -- we will use it next time
211: if (shared == null) {
212: stringTable.put(resString, resString);
213: } else {
214: resString = shared;
215: }
216:
217: // Add to the resMap - either the "default" or the "product" map
218: resMap.put(resKey, resString);
219: }
220:
221: // Copy in default strings, then overwrite product-specific ones.
222: m_resource.putAll(defaultStrings);
223: m_resource.putAll(selectTagStrings);
224: }
225:
226: /**
227: * Compares the "select" value against a set of regular expressions
228: * defined in XML. The first successful match defines the name of a
229: * selector tag. This value is then used to choose the specific
230: * expressions to use.
231: *
232: * @param select the String to be checked
233: * @param matchersElement the XML element containing selector patterns
234: *
235: * @return the selector tag that will be used to select custom resources
236: *
237: */
238: private String match(String select, Element matchersElement)
239: throws MalformedPerl5PatternException {
240: String selectTagName = select;
241:
242: NodeList matchers = matchersElement
243: .getElementsByTagName("matcher");
244: for (int i = 0; i < matchers.getLength(); i++) {
245: // Get the values for this matcher element.
246: Element matcher = (Element) matchers.item(i);
247: String matchName = matcher.getAttribute("for");
248: StringBuffer selectTagPatternBuffer = new StringBuffer(64)
249: .append("/").append(matcher.getAttribute("match"))
250: .append("/i");
251:
252: // If the select string matches the pattern, use the match
253: // name from this matcher.
254: if (m_perl5Util.match(selectTagPatternBuffer.toString(),
255: selectTagName)) {
256: return matchName;
257: }
258: }
259: return null;
260: }
261:
262: /**
263: * Replace substrings of one string with another string and return altered string.
264: * @param input input string
265: * @param find the string to replace
266: * @param replace the string to replace with
267: * @return the substituted string
268: */
269: static private String substituteSubString(String input,
270: String find, String replace) {
271: int find_length = find.length();
272: int replace_length = replace.length();
273:
274: StringBuffer output = new StringBuffer(input);
275: int index = input.indexOf(find);
276: int outputOffset = 0;
277:
278: while (index > -1) {
279: output.replace(index + outputOffset, index + outputOffset
280: + find_length, replace);
281: outputOffset = outputOffset
282: + (replace_length - find_length);
283:
284: index = input.indexOf(find, index + find_length);
285: }
286:
287: String result = output.toString();
288: return result;
289: }
290:
291: /**
292: * Returns a named string for the specified key.
293: *
294: * @param name the name of the String resource required.
295: * @return the requested resource
296: */
297: public String getString(String name) {
298: return (String) m_resource.get(name);
299: }
300:
301: /**
302: * Returns a named string for the specified key.
303: *
304: * @param name the name of the String resource required.
305: * @param required true if the resource is required
306: * @return the requested resource
307: * @throws ConfigurationException
308: * if a required resource cannot be found.
309: */
310: public String getString(String name, boolean required) {
311: String str = getString(name);
312:
313: if (str == null && required) {
314: StringBuffer exceptionBuffer = new StringBuffer(64).append(
315: "Required String resource: '").append(name).append(
316: "' was not found.");
317: throw new IllegalArgumentException(exceptionBuffer
318: .toString());
319: }
320: return str;
321: }
322:
323: /**
324: * Returns a named string, replacing parameters with the values set in a Map.
325: *
326: * @param name the name of the String resource required.
327: * @param parameters a map of parameters (name-value string pairs) which are
328: * replaced where found in the input strings
329: * @return the requested resource
330: */
331: public String getString(String name, Map parameters) {
332: return replaceParameters(getString(name), parameters);
333: }
334:
335: /**
336: * Returns a named string, replacing parameters with the values set in a Map.
337: *
338: * @param name the name of the String resource required.
339: * @param parameters a map of parameters (name-value string pairs) which are
340: * replaced where found in the input strings
341: * @return the requested resource
342: */
343: public String getString(String name, Map parameters,
344: boolean required) {
345: return replaceParameters(getString(name, required), parameters);
346: }
347:
348: /**
349: * Returns a named string, replacing parameters with the values set.
350: *
351: * @param name the name of the String resource required.
352: * @param parameters a map of parameters (name-value string pairs) which are
353: * replaced where found in the input strings
354: * @return the requested resource
355: */
356: static public String replaceParameters(String str, Map parameters) {
357: if (str != null && parameters != null) {
358: // Do parameter replacements for this string resource.
359: Iterator paramNames = parameters.keySet().iterator();
360: StringBuffer replaceBuffer = new StringBuffer(64);
361: while (paramNames.hasNext()) {
362: String paramName = (String) paramNames.next();
363: String paramValue = (String) parameters.get(paramName);
364: replaceBuffer.append("${").append(paramName)
365: .append("}");
366: str = substituteSubString(str,
367: replaceBuffer.toString(), paramValue);
368: if (paramNames.hasNext())
369: replaceBuffer.setLength(0);
370: }
371: }
372:
373: return str;
374: }
375: }
|