001: /*
002: * Title: PathMapper
003: * Description:
004: *
005: * This software is published under the terms of the OpenSymphony Software
006: * License version 1.1, of which a copy has been included with this
007: * distribution in the LICENSE.txt file.
008: */
009:
010: package com.opensymphony.module.sitemesh.mapper;
011:
012: import java.util.HashMap;
013: import java.util.Map;
014: import java.util.Iterator;
015:
016: /**
017: * The PathMapper is used to map file patterns to keys, and find an approriate
018: * key for a given file path. The pattern rules are consistent with those defined
019: * in the Servlet 2.3 API on the whole. Wildcard patterns are also supported, using
020: * any combination of * and ?.
021: *
022: * <h3>Example</h3>
023: *
024: * <blockquote><code>
025: * PathMapper pm = new PathMapper();<br>
026: * <br>
027: * pm.put("one","/");<br>
028: * pm.put("two","/mydir/*");<br>
029: * pm.put("three","*.xml");<br>
030: * pm.put("four","/myexactfile.html");<br>
031: * pm.put("five","/*\/admin/*.??ml");<br>
032: * <br>
033: * String result1 = pm.get("/mydir/myfile.xml"); // returns "two";<br>
034: * String result2 = pm.get("/mydir/otherdir/admin/myfile.html"); // returns "five";<br>
035: * </code></blockquote>
036: *
037: * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
038: * @author <a href="mailto:mcannon@internet.com">Mike Cannon-Brookes</a>
039: * @author <a href="mailto:hani@formicary.net">Hani Suleiman</a>
040: * @version $Revision: 1.3 $
041: */
042: public class PathMapper {
043: private Map mappings = new HashMap();
044:
045: /** Add a key and appropriate matching pattern. */
046: public void put(String key, String pattern) {
047: if (key != null) {
048: mappings.put(pattern, key);
049: }
050: }
051:
052: /** Retrieve appropriate key by matching patterns with supplied path. */
053: public String get(String path) {
054: if (path == null)
055: path = "/";
056: String mapped = findKey(path, mappings);
057: if (mapped == null)
058: return null;
059: return (String) mappings.get(mapped);
060: }
061:
062: /** Find exact key in mappings. */
063: private static String findKey(String path, Map mappings) {
064: String result = findExactKey(path, mappings);
065: if (result == null)
066: result = findComplexKey(path, mappings);
067: if (result == null)
068: result = findDefaultKey(mappings);
069: return result;
070: }
071:
072: /** Check if path matches exact pattern ( /blah/blah.jsp ). */
073: private static String findExactKey(String path, Map mappings) {
074: if (mappings.containsKey(path))
075: return path;
076: return null;
077: }
078:
079: private static String findComplexKey(String path, Map mappings) {
080: Iterator i = mappings.keySet().iterator();
081: String result = null, key = null;
082: while (i.hasNext()) {
083: key = (String) i.next();
084: if (key.length() > 1
085: && (key.indexOf('?') != -1 || key.indexOf('*') != -1)
086: && match(key, path, false)) {
087: if (result == null || key.length() > result.length()) {
088: // longest key wins
089: result = key;
090: }
091: }
092: }
093: return result;
094: }
095:
096: /** Look for root pattern ( / ). */
097: private static String findDefaultKey(Map mappings) {
098: String[] defaultKeys = { "/", "*", "/*" };
099: for (int i = 0; i < defaultKeys.length; i++) {
100: if (mappings.containsKey(defaultKeys[i]))
101: return defaultKeys[i];
102: }
103: return null;
104: }
105:
106: private static boolean match(String pattern, String str,
107: boolean isCaseSensitive) {
108: char[] patArr = pattern.toCharArray();
109: char[] strArr = str.toCharArray();
110: int patIdxStart = 0;
111: int patIdxEnd = patArr.length - 1;
112: int strIdxStart = 0;
113: int strIdxEnd = strArr.length - 1;
114: char ch;
115:
116: boolean containsStar = false;
117: for (int i = 0; i < patArr.length; i++) {
118: if (patArr[i] == '*') {
119: containsStar = true;
120: break;
121: }
122: }
123:
124: if (!containsStar) {
125: // No '*'s, so we make a shortcut
126: if (patIdxEnd != strIdxEnd) {
127: return false; // Pattern and string do not have the same size
128: }
129: for (int i = 0; i <= patIdxEnd; i++) {
130: ch = patArr[i];
131: if (ch != '?') {
132: if (isCaseSensitive && ch != strArr[i]) {
133: return false; // Character mismatch
134: }
135: if (!isCaseSensitive
136: && Character.toUpperCase(ch) != Character
137: .toUpperCase(strArr[i])) {
138: return false; // Character mismatch
139: }
140: }
141: }
142: return true; // String matches against pattern
143: }
144:
145: if (patIdxEnd == 0) {
146: return true; // Pattern contains only '*', which matches anything
147: }
148:
149: // Process characters before first star
150: while ((ch = patArr[patIdxStart]) != '*'
151: && strIdxStart <= strIdxEnd) {
152: if (ch != '?') {
153: if (isCaseSensitive && ch != strArr[strIdxStart]) {
154: return false; // Character mismatch
155: }
156: if (!isCaseSensitive
157: && Character.toUpperCase(ch) != Character
158: .toUpperCase(strArr[strIdxStart])) {
159: return false; // Character mismatch
160: }
161: }
162: patIdxStart++;
163: strIdxStart++;
164: }
165: if (strIdxStart > strIdxEnd) {
166: // All characters in the string are used. Check if only '*'s are
167: // left in the pattern. If so, we succeeded. Otherwise failure.
168: for (int i = patIdxStart; i <= patIdxEnd; i++) {
169: if (patArr[i] != '*') {
170: return false;
171: }
172: }
173: return true;
174: }
175:
176: // Process characters after last star
177: while ((ch = patArr[patIdxEnd]) != '*'
178: && strIdxStart <= strIdxEnd) {
179: if (ch != '?') {
180: if (isCaseSensitive && ch != strArr[strIdxEnd]) {
181: return false; // Character mismatch
182: }
183: if (!isCaseSensitive
184: && Character.toUpperCase(ch) != Character
185: .toUpperCase(strArr[strIdxEnd])) {
186: return false; // Character mismatch
187: }
188: }
189: patIdxEnd--;
190: strIdxEnd--;
191: }
192: if (strIdxStart > strIdxEnd) {
193: // All characters in the string are used. Check if only '*'s are
194: // left in the pattern. If so, we succeeded. Otherwise failure.
195: for (int i = patIdxStart; i <= patIdxEnd; i++) {
196: if (patArr[i] != '*') {
197: return false;
198: }
199: }
200: return true;
201: }
202:
203: // process pattern between stars. padIdxStart and patIdxEnd point
204: // always to a '*'.
205: while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
206: int patIdxTmp = -1;
207: for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
208: if (patArr[i] == '*') {
209: patIdxTmp = i;
210: break;
211: }
212: }
213: if (patIdxTmp == patIdxStart + 1) {
214: // Two stars next to each other, skip the first one.
215: patIdxStart++;
216: continue;
217: }
218: // Find the pattern between padIdxStart & padIdxTmp in str between
219: // strIdxStart & strIdxEnd
220: int patLength = (patIdxTmp - patIdxStart - 1);
221: int strLength = (strIdxEnd - strIdxStart + 1);
222: int foundIdx = -1;
223: strLoop: for (int i = 0; i <= strLength - patLength; i++) {
224: for (int j = 0; j < patLength; j++) {
225: ch = patArr[patIdxStart + j + 1];
226: if (ch != '?') {
227: if (isCaseSensitive
228: && ch != strArr[strIdxStart + i + j]) {
229: continue strLoop;
230: }
231: if (!isCaseSensitive
232: && Character.toUpperCase(ch) != Character
233: .toUpperCase(strArr[strIdxStart
234: + i + j])) {
235: continue strLoop;
236: }
237: }
238: }
239:
240: foundIdx = strIdxStart + i;
241: break;
242: }
243:
244: if (foundIdx == -1) {
245: return false;
246: }
247:
248: patIdxStart = patIdxTmp;
249: strIdxStart = foundIdx + patLength;
250: }
251:
252: // All characters in the string are used. Check if only '*'s are left
253: // in the pattern. If so, we succeeded. Otherwise failure.
254: for (int i = patIdxStart; i <= patIdxEnd; i++) {
255: if (patArr[i] != '*') {
256: return false;
257: }
258: }
259: return true;
260: }
261: }
|