001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.core.urltemplates;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022: import org.apache.beehive.netui.util.logging.Logger;
023:
024: import java.io.Serializable;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.HashMap;
028: import java.util.Map;
029:
030: /**
031: * The class to format a URL defined by url-template-config template
032: * given by values for a set of tokens.
033: */
034: public class URLTemplate implements Serializable {
035: private static final Logger _log = Logger
036: .getInstance(URLTemplate.class);
037:
038: private static final char BEGIN_TOKEN_QUALIFIER = '{';
039: private static final char END_TOKEN_QUALIFIER = '}';
040:
041: // The String form of the template.
042: private String _template;
043: private String _name;
044:
045: // Parsed representation of the template... list of literals and tokens.
046: private ArrayList/*< TemplateItem >*/_parsedTemplate = null;
047:
048: private boolean _isParsed = false;
049:
050: private HashMap/*< String, String >*/_tokenValuesMap = new HashMap/*< String, String >*/();
051:
052: /**
053: * Create a URLTemplate from a url-template-config template.
054: *
055: * @param template the string form of the template from url-template-config.
056: */
057: public URLTemplate(String template) {
058: setTemplate(template);
059: }
060:
061: /**
062: * Create a URLTemplate from a url-template-config template.
063: *
064: * @param template the string form of the template from url-template-config.
065: * @param name the name of the template
066: */
067: public URLTemplate(String template, String name) {
068: setTemplate(template);
069: setName(name);
070: }
071:
072: /**
073: * Copy constructor to create a URLTemplate from an existing URLTemplate.
074: *
075: * <p> Note that this is not truly a complete copy because the Map
076: * of the replacement values for the given tokens is not copied.
077: * This copy will just have an empty map of token values so that
078: * it is "cleared" and ready to format another URL. </p>
079: *
080: * @param template the URLTemplate to copy.
081: */
082: public URLTemplate(URLTemplate template) {
083: setTemplate(template.getTemplate());
084: setName(template.getName());
085: _parsedTemplate = template._parsedTemplate;
086: _isParsed = template._isParsed;
087: }
088:
089: /**
090: * Reset the String form of the template.
091: *
092: * <p> Should call verify after setting a new template. </p>
093: *
094: * @param template the string form of the template from url-template-config.
095: */
096: public void setTemplate(String template) {
097: if (template == null || template.length() == 0) {
098: throw new IllegalStateException(
099: "Template cannot be null or empty.");
100: }
101:
102: if (template.equals(_template)) {
103: return;
104: }
105:
106: _template = template;
107: _isParsed = false;
108: _parsedTemplate = null;
109: }
110:
111: /**
112: * Retrieve the String form of the template.
113: *
114: * @return the string form of the template.
115: */
116: public String getTemplate() {
117: return _template;
118: }
119:
120: public void setName(String name) {
121: _name = name;
122: }
123:
124: public String getName() {
125: return _name;
126: }
127:
128: /**
129: * Verification will ensure the URL template conforms to a valid format
130: * for known tokens and contains the required tokens. It will also parse
131: * the tokens and literal data into a list to improve the replacement
132: * performance when constructing the final URL string.
133: *
134: * <p> Allow clients to define a set of required and known tokens for the
135: * template verification. Tokens are expected to be qualified
136: * in braces. E.g. {url:path} </p>
137: *
138: * <p> If the template does not contain the required tokens or if the
139: * format of a known token is incorrect, this method will log the error
140: * and return false. </p>
141: *
142: * <p> Should call verify after creating a new template. </p>
143: *
144: * @param knownTokens the collection of known tokens (Strings) for a valid template.
145: * @param requiredTokens the collection of required tokens (Strings) in a valid template.
146: * @return true if the template conforms to a valid format, otherwise return false.
147: */
148: public boolean verify(Collection knownTokens,
149: Collection requiredTokens) {
150: boolean valid = true;
151: // For each known token, make sure there is a leading and trailing brace
152: if (knownTokens != null) {
153: for (java.util.Iterator ii = knownTokens.iterator(); ii
154: .hasNext();) {
155: String token = (String) ii.next();
156: if (token != null && token.length() > 2) {
157: // Strip braces from the known token
158: token = token.substring(1, token.length() - 1);
159: int index = _template.indexOf(token);
160: if (index != -1) {
161: if (_template.charAt(index - 1) != BEGIN_TOKEN_QUALIFIER
162: || _template.charAt(index
163: + token.length()) != END_TOKEN_QUALIFIER) {
164: _log
165: .error("Template token, "
166: + token
167: + ", is not correctly enclosed with braces in template: "
168: + _template);
169: valid = false;
170: }
171: }
172: }
173: }
174: }
175:
176: // Parse the template into tokens and literals
177: parseTemplate();
178:
179: // Check if the required tokens are present
180: if (requiredTokens != null) {
181: for (java.util.Iterator ii = requiredTokens.iterator(); ii
182: .hasNext();) {
183: String token = (String) ii.next();
184: TemplateItem requiredItem = new TemplateItem(token,
185: true);
186:
187: if (!_parsedTemplate.contains(requiredItem)) {
188: _log.error("Required token, " + token
189: + ", not found in template: " + _template);
190: valid = false;
191: }
192: }
193: }
194:
195: return valid;
196: }
197:
198: private void parseTemplate() {
199: if (_isParsed) {
200: return;
201: }
202:
203: _parsedTemplate = new ArrayList/*< TemplateItem >*/();
204: TemplateTokenizer tokenizer = new TemplateTokenizer(
205: getTemplate());
206: for (; tokenizer.hasNext();) {
207: boolean isToken = tokenizer.isTokenNext();
208: String tokenOrLiteral = (String) tokenizer.next();
209: if (tokenOrLiteral.equals("")) {
210: continue;
211: }
212:
213: TemplateItem item = new TemplateItem(tokenOrLiteral,
214: isToken);
215: _parsedTemplate.add(item);
216: }
217:
218: _isParsed = true;
219: }
220:
221: /**
222: * Replace a set of tokens in the template with a corresponding set of values.
223: * This assumes that there is an ordered one-to-one relationship. Tokens are
224: * expected to be qualified in braces. E.g. {url:path}
225: */
226: public void substitute(Map/*< String, String >*/tokensAndValues) {
227: if (tokensAndValues != null) {
228: _tokenValuesMap.putAll(tokensAndValues);
229: }
230: }
231:
232: /**
233: * Replace a single token in the template with a corresponding String value.
234: * Tokens are expected to be qualified in braces. E.g. {url:path}
235: */
236: public void substitute(String token, String value) {
237: _tokenValuesMap.put(token, value);
238: }
239:
240: /**
241: * Replace a single token in the template with a corresponding int value.
242: * Tokens are expected to be qualified in braces. E.g. {url:port}
243: */
244: public void substitute(String token, int value) {
245: String valueStr = Integer.toString(value);
246: _tokenValuesMap.put(token, valueStr);
247: }
248:
249: /**
250: * Return the String representation of the URL after replacing
251: * the tokens in the template with their associated values. If
252: * there is no value for a token, the token is discarded/removed.
253: * I.E. It will not be part of the returned String.
254: *
255: * @return the url
256: */
257: public String toString() {
258: return format(true);
259: }
260:
261: /**
262: * Return the String representation of the URL after replacing
263: * the tokens in the template with their associated values. If
264: * there is no value for a token, the token is discarded/removed.
265: * I.E. It will not be part of the returned String.
266: *
267: * @return the url
268: */
269: public String format() {
270: return format(true);
271: }
272:
273: /**
274: * Return the String representation of the URL after replacing
275: * the tokens in the template with their associated values.
276: * If the boolean argument is <code>true</code>, then the unset
277: * template tokens are removed. Otherwise, do not cleanup
278: * the unset tokens.
279: *
280: * @param removeUnsetTokens flag to tell URLTemplate to remove
281: * or leave the unset tokens in the URL.
282: * @return the url
283: */
284: public String format(boolean removeUnsetTokens) {
285: // template should already have been parsed with a call to
286: if (!_isParsed) {
287: // Parse the template into tokens and literals
288: parseTemplate();
289: }
290:
291: InternalStringBuilder result = new InternalStringBuilder(
292: _template.length() + 16);
293: for (java.util.Iterator ii = _parsedTemplate.iterator(); ii
294: .hasNext();) {
295: TemplateItem item = (TemplateItem) ii.next();
296: if (item.isToken()) {
297: if (_tokenValuesMap.containsKey(item.getValue())) {
298: appendToResult(result, (String) _tokenValuesMap
299: .get(item.getValue()));
300: } else {
301: // No value for the token.
302: if (!removeUnsetTokens) {
303: // treat the token as a literal
304: appendToResult(result, item.getValue());
305: }
306: }
307: } else {
308: appendToResult(result, item.getValue());
309: }
310: }
311:
312: if (result.length() > 0
313: && result.charAt(result.length() - 1) == '?') {
314: result.deleteCharAt(result.length() - 1);
315: }
316:
317: return result.toString();
318: }
319:
320: // check to make sure we don't end up with "//" between components
321: // of the URL
322: protected void appendToResult(InternalStringBuilder result,
323: String value) {
324: if (value == null || value.length() == 0) {
325: return;
326: }
327:
328: if (result.length() > 0
329: && result.charAt(result.length() - 1) == '/'
330: && value.charAt(0) == '/') {
331: result.deleteCharAt(result.length() - 1);
332: }
333: result.append(value);
334: }
335:
336: protected class TemplateItem implements Serializable {
337: private String value;
338: private boolean isToken = false;
339:
340: public TemplateItem(String value, boolean isToken) {
341: assert value != null : "TemplateItem value cannot be null.";
342: this .value = value;
343: this .isToken = isToken;
344: }
345:
346: public String getValue() {
347: return value;
348: }
349:
350: public boolean isToken() {
351: return isToken;
352: }
353:
354: public boolean equals(Object o) {
355: if (this == o) {
356: return true;
357: }
358: if (!(o instanceof TemplateItem)) {
359: return false;
360: }
361:
362: final TemplateItem templateItem = (TemplateItem) o;
363:
364: if (isToken != templateItem.isToken()) {
365: return false;
366: }
367: if (!value.equals(templateItem.getValue())) {
368: return false;
369: }
370:
371: return true;
372: }
373:
374: public int hashCode() {
375: int result;
376: result = value.hashCode();
377: result = 29 * result + (isToken ? 1 : 0);
378: return result;
379: }
380: }
381: }
|