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: package org.apache.jetspeed.util;
018:
019: import java.io.Serializable;
020: import java.util.Arrays;
021: import java.util.HashMap;
022:
023: /**
024: * <h2>Overview</h2>
025: * <p>
026: * The Path object is used to standard used to standardize the creation of
027: * mutation of path-like structures. For: example /foo/bar/index.html.
028: * </p>
029: * <h2>Rules for Interperting Pathes</h2>
030: * <p>
031: * Below are the rules for how the constructor interprets literal paths.
032: * <strong>NOTE</strong> the {@link addSegment(String)} interprets string
033: * pathes in a somewhat different manner. <table>
034: * <tr>
035: * <th>Literal Path</th>
036: * <th>Interpretation</th>
037: * </tr>
038: * <td> <i>/foo/bar/index.html</i> </td>
039: * <td> <code>foo</code> and <code>bar</code> will be considered directory
040: * segments while <code>index.html</code> will be considered a file segment.
041: * This means that the <code>baseName</code> will be set to <i>index</i> and
042: * the <code>fileExtension</code> will be set to <i>.html</i> </td>
043: * <tr>
044: * <td> <i>/foo/bar/</i>, <i>/foo/bar</i>, <i>foo/bar/</i> <i>foo/bar</i>
045: * </td>
046: * <td>
047: * <p>
048: * <code>foo</code> and <code>bar</code> will be considered directory
049: * segments. <code>baseName</code> and <code>fileExtension</code> will be
050: * left as <code>null</code>.
051: * <p>
052: * I cases where a file has no extension you must use the
053: * {@link setFileSegment(String))} to manually set the file. This causes the
054: * <code>baseName</code> to be set to the file name specified and the
055: * <code>fileExtension</code> will be set to the empty string ("").
056: * </p>
057: * </td>
058: * </tr>
059: * </table>
060: *
061: * @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a>
062: */
063: public class Path implements Serializable, Cloneable {
064: /** The serial version uid. */
065: private static final long serialVersionUID = 6890966283704092945L;
066:
067: public static final String PATH_SEPERATOR = "/";
068:
069: private static final String[] EMPTY_SEGMENTS = new String[0];
070:
071: private static HashMap childrenMap = new HashMap();
072:
073: private final String path;
074: private final String[] segments;
075:
076: private final String fileName;
077:
078: private final String baseName;
079:
080: private final String fileExtension;
081:
082: private final String queryString;
083:
084: private final int hashCode;
085:
086: public Path() {
087: segments = EMPTY_SEGMENTS;
088: fileName = null;
089: baseName = null;
090: fileExtension = null;
091: queryString = null;
092: hashCode = 0;
093: path = "";
094: }
095:
096: private Path(Path parent, String childSegment, boolean pathOnly) {
097: this (parent, splitPath(childSegment), pathOnly);
098: }
099:
100: private Path(Path parent, String[] children, boolean pathOnly) {
101: int code = 0;
102: if (!pathOnly) {
103: this .fileName = parent.fileName;
104: this .baseName = parent.baseName;
105: this .fileExtension = parent.fileExtension;
106: this .queryString = parent.queryString;
107: if (queryString != null) {
108: code += queryString.hashCode();
109: }
110: } else {
111: fileName = null;
112: baseName = null;
113: fileExtension = null;
114: queryString = null;
115: }
116:
117: int size = parent.segments.length;
118: if (pathOnly && parent.fileName != null) {
119: size--;
120: }
121:
122: int index = 0;
123:
124: segments = new String[size + children.length];
125: for (index = 0; index < size; index++) {
126: segments[index] = parent.segments[index];
127: code += segments[index].hashCode();
128: }
129: for (int i = 0; i < children.length; i++, index++) {
130: segments[index] = children[i];
131: code += segments[index].hashCode();
132: }
133: if (fileName != null && !pathOnly) {
134: segments[index] = fileName;
135: code += segments[index].hashCode();
136: }
137: hashCode = code;
138: path = buildPath();
139: }
140:
141: private Path(Path parent) {
142: this .fileName = parent.fileName;
143: this .baseName = parent.baseName;
144: this .fileExtension = parent.fileExtension;
145: this .queryString = parent.queryString;
146: segments = new String[parent.segments.length - 1];
147: int code = 0;
148: for (int i = 0; i < parent.segments.length - 2; i++) {
149: segments[i] = parent.segments[i];
150: code += segments.hashCode();
151: }
152: if (fileName != null) {
153: segments[segments.length - 1] = fileName;
154: } else if (parent.segments.length > 1) {
155: segments[segments.length - 1] = parent.segments[parent.segments.length - 2];
156: }
157: if (segments.length > 0) {
158: code += segments[segments.length - 1].hashCode();
159: }
160: if (queryString != null) {
161: code += queryString.hashCode();
162: }
163: hashCode = code;
164: path = buildPath();
165: }
166:
167: private Path(String[] segments, int offset, int count) {
168: this .segments = new String[count];
169: int code = 0;
170: for (int i = 0; i < count; i++) {
171: this .segments[i] = segments[offset + i];
172: code += segments[offset + i].hashCode();
173: }
174: hashCode = code;
175: if (count > 0) {
176: fileName = this .segments[count - 1];
177: int extIndex = fileName.lastIndexOf('.');
178: if (extIndex > -1) {
179: baseName = fileName.substring(0, extIndex);
180: fileExtension = fileName.substring(extIndex);
181: } else {
182: baseName = fileName;
183: fileExtension = "";
184: }
185: } else {
186: fileName = null;
187: baseName = null;
188: fileExtension = null;
189: }
190: queryString = null;
191: path = buildPath();
192: }
193:
194: public Path(String path) {
195:
196: String tmp = path.replace('\\', '/');
197:
198: if (!tmp.startsWith("/")) {
199: tmp = "/" + tmp;
200: }
201:
202: this .path = tmp;
203:
204: if (path.equals("/")) {
205: segments = new String[] { "" };
206: fileName = null;
207: baseName = null;
208: fileExtension = null;
209: queryString = null;
210: hashCode = 0;
211: } else {
212: int queryStart = path.indexOf('?');
213: int len = queryStart > -1 ? queryStart : path.length();
214: segments = split(path, 0, len, '/');
215: int code = 0;
216: for (int i = 0; i < segments.length; i++) {
217: code += segments[i].hashCode();
218: }
219: String tmpFileName = null;
220:
221: if (queryStart > 1 && path.length() > queryStart + 1) {
222: queryString = path.substring(queryStart + 1);
223: code += queryString.hashCode();
224: } else {
225: queryString = null;
226: }
227: hashCode = code;
228: int extIndex = -1;
229: if (segments.length > 0) {
230: tmpFileName = segments[segments.length - 1];
231: extIndex = tmpFileName.lastIndexOf('.');
232: }
233: if (extIndex > -1) {
234: fileName = tmpFileName;
235: baseName = tmpFileName.substring(0, extIndex);
236: fileExtension = tmpFileName.substring(extIndex);
237: } else {
238: fileName = null;
239: baseName = null;
240: fileExtension = null;
241: }
242: }
243: }
244:
245: private static String[] splitPath(String path) {
246: String[] children = null;
247: path = path.replace('\\', '/');
248:
249: if (path.startsWith("/")) {
250: path = "/" + path;
251: }
252:
253: if (path.equals("/")) {
254: children = new String[] { "" };
255: } else {
256: int index = path.indexOf('?');
257: int len = index > -1 ? index : path.length();
258: children = split(path, 0, len, '/');
259: }
260: return children;
261: }
262:
263: /**
264: * Returns the segement of the path at the specified index <code>i</code>.
265: *
266: * @param i
267: * index containing the segment to return.
268: * @return Segment at index <code>i</code>
269: * @throws ArrayIndexOutOfBoundsException
270: * if the index is not within the bounds of this Path.
271: */
272: public String getSegment(int i) {
273: return segments[i];
274: }
275:
276: /**
277: * <p>
278: * Adds this segment to the end of the path but before the current file
279: * segment, if one exists. For consistency Segments added via this method
280: * are <strong>ALWAYS</strong> considered directories even when matching a
281: * standrad file pattern i.e. <i>index.html</i>
282: * </p>
283: * <p>
284: * If you need to set the file segment, please use the setFileSegment()
285: * method.
286: * </p>
287: *
288: * @param segment
289: * @return
290: */
291: public Path addSegment(String segment) {
292: return new Path(this , segment, false);
293: }
294:
295: public Path getSubPath(int beginAtSegment) {
296: return new Path(segments, beginAtSegment, segments.length
297: - beginAtSegment);
298: }
299:
300: public Path getSubPath(int beginAtSegment, int endSegment) {
301: return new Path(segments, beginAtSegment, endSegment
302: - beginAtSegment);
303: }
304:
305: public String getBaseName() {
306: return baseName;
307: }
308:
309: public String getFileExtension() {
310: return fileExtension;
311: }
312:
313: public String getFileName() {
314: return fileName;
315: }
316:
317: public String getQueryString() {
318: return queryString;
319: }
320:
321: public int length() {
322: return segments.length;
323: }
324:
325: public String toString() {
326: return path;
327: }
328:
329: private String buildPath() {
330: int len = 0;
331: for (int i = 0; i < segments.length; i++) {
332: len += segments[i].length() + 1;
333: }
334: if (queryString != null) {
335: len += queryString.length() + 1;
336: }
337: char[] buffer = new char[len];
338: int index = 0;
339: for (int i = 0; i < segments.length; i++) {
340: buffer[index++] = '/';
341: len = segments[i].length();
342: segments[i].getChars(0, len, buffer, index);
343: index += len;
344: }
345: if (queryString != null) {
346: buffer[index++] = '?';
347: len = queryString.length();
348: queryString.getChars(0, len, buffer, index);
349: }
350: return new String(buffer);
351: }
352:
353: public boolean equals(Object obj) {
354: if (obj instanceof Path) {
355: Path other = (Path) obj;
356: if ((other.queryString != null && other.queryString
357: .equals(queryString))
358: || (other.queryString == null && queryString == null)) {
359: return Arrays.equals(other.segments, segments);
360: }
361: }
362: return false;
363: }
364:
365: public int hashCode() {
366: return hashCode;
367: }
368:
369: /**
370: * Removes the last directory segment in this path. This method <strong>WILL
371: * NOT</strong> remove the fileSegment, but path segment immediately before
372: * it.
373: *
374: * @return segment removed.
375: */
376: public Path removeLastPathSegment() {
377: if ((fileName != null && segments.length == 1)
378: || segments.length == 0) {
379: return this ;
380: }
381: return new Path(this );
382: }
383:
384: public Path getChild(String childPath) {
385: synchronized (childrenMap) {
386: Path child = null;
387: HashMap children = (HashMap) childrenMap.get(path);
388: if (children == null) {
389: children = new HashMap();
390: childrenMap.put(path, children);
391: } else {
392: child = (Path) children.get(childPath);
393: }
394: if (child == null) {
395: if (segments.length == 0) {
396: child = new Path(childPath);
397: } else {
398: child = new Path(this , childPath, true);
399: }
400: children.put(childPath, child);
401: }
402: return child;
403: }
404: }
405:
406: public Path getChild(Path childPath) {
407: return getChild(childPath.path);
408: }
409:
410: private static String[] split(String str, int start, int length,
411: char separator) {
412: String[] result;
413: char[] buffer = str.toCharArray();
414: int tokens = 0;
415: boolean token = false;
416: for (int i = start; i < length; i++) {
417: if (buffer[i] == separator) {
418: token = false;
419: } else if (!token) {
420: tokens++;
421: token = true;
422: }
423: }
424: result = new String[tokens];
425: if (tokens > 0) {
426: int begin = start;
427: int end = start;
428: token = false;
429: tokens = 0;
430: for (int i = start; i < length; i++) {
431: if (buffer[i] == separator) {
432: if (token) {
433: result[tokens++] = new String(buffer, begin,
434: end);
435: token = false;
436: }
437: } else if (!token) {
438: token = true;
439: begin = i;
440: end = 1;
441: } else {
442: end++;
443: }
444: }
445: if (token) {
446: result[tokens] = new String(buffer, begin, end);
447: }
448: }
449: return result;
450: }
451: }
|