1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.util;
18
19 import java.io.Serializable;
20 import java.util.Arrays;
21 import java.util.HashMap;
22
23 /***
24 * <h2>Overview</h2>
25 * <p>
26 * The Path object is used to standard used to standardize the creation of
27 * mutation of path-like structures. For: example /foo/bar/index.html.
28 * </p>
29 * <h2>Rules for Interperting Pathes</h2>
30 * <p>
31 * Below are the rules for how the constructor interprets literal paths.
32 * <strong>NOTE</strong> the {@link addSegment(String)} interprets string
33 * pathes in a somewhat different manner. <table>
34 * <tr>
35 * <th>Literal Path</th>
36 * <th>Interpretation</th>
37 * </tr>
38 * <td> <i>/foo/bar/index.html</i> </td>
39 * <td> <code>foo</code> and <code>bar</code> will be considered directory
40 * segments while <code>index.html</code> will be considered a file segment.
41 * This means that the <code>baseName</code> will be set to <i>index</i> and
42 * the <code>fileExtension</code> will be set to <i>.html</i> </td>
43 * <tr>
44 * <td> <i>/foo/bar/</i>, <i>/foo/bar</i>, <i>foo/bar/</i> <i>foo/bar</i>
45 * </td>
46 * <td>
47 * <p>
48 * <code>foo</code> and <code>bar</code> will be considered directory
49 * segments. <code>baseName</code> and <code>fileExtension</code> will be
50 * left as <code>null</code>.
51 * <p>
52 * I cases where a file has no extension you must use the
53 * {@link setFileSegment(String))} to manually set the file. This causes the
54 * <code>baseName</code> to be set to the file name specified and the
55 * <code>fileExtension</code> will be set to the empty string ("").
56 * </p>
57 * </td>
58 * </tr>
59 * </table>
60 *
61 * @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a>
62 */
63 public class Path implements Serializable, Cloneable
64 {
65 /*** The serial version uid. */
66 private static final long serialVersionUID = 6890966283704092945L;
67
68 public static final String PATH_SEPERATOR = "/";
69
70 private static final String[] EMPTY_SEGMENTS = new String[0];
71
72 private static HashMap childrenMap = new HashMap();
73
74 private final String path;
75 private final String[] segments;
76
77 private final String fileName;
78
79 private final String baseName;
80
81 private final String fileExtension;
82
83 private final String queryString;
84
85 private final int hashCode;
86
87 public Path()
88 {
89 segments = EMPTY_SEGMENTS;
90 fileName = null;
91 baseName = null;
92 fileExtension = null;
93 queryString = null;
94 hashCode = 0;
95 path = "";
96 }
97
98 private Path(Path parent, String childSegment, boolean pathOnly)
99 {
100 this(parent, splitPath(childSegment), pathOnly);
101 }
102
103 private Path(Path parent, String[] children, boolean pathOnly)
104 {
105 int code = 0;
106 if (!pathOnly)
107 {
108 this.fileName = parent.fileName;
109 this.baseName = parent.baseName;
110 this.fileExtension = parent.fileExtension;
111 this.queryString = parent.queryString;
112 if (queryString != null)
113 {
114 code += queryString.hashCode();
115 }
116 }
117 else
118 {
119 fileName = null;
120 baseName = null;
121 fileExtension = null;
122 queryString = null;
123 }
124
125 int size = parent.segments.length;
126 if (pathOnly && parent.fileName != null)
127 {
128 size--;
129 }
130
131 int index = 0;
132
133 segments = new String[size+children.length];
134 for (index = 0; index < size; index++)
135 {
136 segments[index] = parent.segments[index];
137 code += segments[index].hashCode();
138 }
139 for (int i = 0; i < children.length; i++, index++)
140 {
141 segments[index] = children[i];
142 code += segments[index].hashCode();
143 }
144 if (fileName != null && !pathOnly)
145 {
146 segments[index] = fileName;
147 code += segments[index].hashCode();
148 }
149 hashCode = code;
150 path = buildPath();
151 }
152
153 private Path(Path parent)
154 {
155 this.fileName = parent.fileName;
156 this.baseName = parent.baseName;
157 this.fileExtension = parent.fileExtension;
158 this.queryString = parent.queryString;
159 segments = new String[parent.segments.length-1];
160 int code = 0;
161 for (int i = 0; i < parent.segments.length-2; i++)
162 {
163 segments[i] = parent.segments[i];
164 code += segments.hashCode();
165 }
166 if (fileName != null)
167 {
168 segments[segments.length-1] = fileName;
169 }
170 else if (parent.segments.length > 1)
171 {
172 segments[segments.length-1] = parent.segments[parent.segments.length-2];
173 }
174 if ( segments.length > 0)
175 {
176 code += segments[segments.length-1].hashCode();
177 }
178 if (queryString != null)
179 {
180 code += queryString.hashCode();
181 }
182 hashCode = code;
183 path = buildPath();
184 }
185
186 private Path(String[] segments, int offset, int count)
187 {
188 this.segments = new String[count];
189 int code = 0;
190 for (int i = 0; i < count; i++)
191 {
192 this.segments[i] = segments[offset+i];
193 code+=segments[offset+i].hashCode();
194 }
195 hashCode = code;
196 if (count > 0)
197 {
198 fileName = this.segments[count-1];
199 int extIndex = fileName.lastIndexOf('.');
200 if (extIndex > -1)
201 {
202 baseName = fileName.substring(0, extIndex);
203 fileExtension = fileName.substring(extIndex);
204 }
205 else
206 {
207 baseName = fileName;
208 fileExtension = "";
209 }
210 }
211 else
212 {
213 fileName = null;
214 baseName = null;
215 fileExtension = null;
216 }
217 queryString = null;
218 path = buildPath();
219 }
220
221 public Path(String path)
222 {
223
224 String tmp = path.replace('//', '/');
225
226 if (!tmp.startsWith("/"))
227 {
228 tmp = "/" + tmp;
229 }
230
231 this.path = tmp;
232
233 if (path.equals("/"))
234 {
235 segments = new String[]{""};
236 fileName = null;
237 baseName = null;
238 fileExtension = null;
239 queryString = null;
240 hashCode = 0;
241 }
242 else
243 {
244 int queryStart = path.indexOf('?');
245 int len = queryStart > -1 ? queryStart : path.length();
246 segments = split(path, 0, len, '/');
247 int code = 0;
248 for (int i = 0; i < segments.length; i++)
249 {
250 code += segments[i].hashCode();
251 }
252 String tmpFileName = null;
253
254 if (queryStart > 1 && path.length() > queryStart+1)
255 {
256 queryString = path.substring(queryStart+1);
257 code += queryString.hashCode();
258 }
259 else
260 {
261 queryString = null;
262 }
263 hashCode = code;
264 int extIndex = -1;
265 if (segments.length > 0)
266 {
267 tmpFileName = segments[segments.length-1];
268 extIndex = tmpFileName.lastIndexOf('.');
269 }
270 if (extIndex > -1)
271 {
272 fileName = tmpFileName;
273 baseName = tmpFileName.substring(0, extIndex);
274 fileExtension = tmpFileName.substring(extIndex);
275 }
276 else
277 {
278 fileName = null;
279 baseName = null;
280 fileExtension = null;
281 }
282 }
283 }
284
285 private static String[] splitPath(String path)
286 {
287 String[] children = null;
288 path = path.replace('//', '/');
289
290 if (path.startsWith("/"))
291 {
292 path = "/" + path;
293 }
294
295 if (path.equals("/"))
296 {
297 children = new String[]{""};
298 }
299 else
300 {
301 int index = path.indexOf('?');
302 int len = index > -1 ? index : path.length();
303 children = split(path, 0, len, '/');
304 }
305 return children;
306 }
307
308 /***
309 * Returns the segement of the path at the specified index <code>i</code>.
310 *
311 * @param i
312 * index containing the segment to return.
313 * @return Segment at index <code>i</code>
314 * @throws ArrayIndexOutOfBoundsException
315 * if the index is not within the bounds of this Path.
316 */
317 public String getSegment(int i)
318 {
319 return segments[i];
320 }
321
322 /***
323 * <p>
324 * Adds this segment to the end of the path but before the current file
325 * segment, if one exists. For consistency Segments added via this method
326 * are <strong>ALWAYS</strong> considered directories even when matching a
327 * standrad file pattern i.e. <i>index.html</i>
328 * </p>
329 * <p>
330 * If you need to set the file segment, please use the setFileSegment()
331 * method.
332 * </p>
333 *
334 * @param segment
335 * @return
336 */
337 public Path addSegment(String segment)
338 {
339 return new Path(this, segment, false);
340 }
341
342 public Path getSubPath(int beginAtSegment)
343 {
344 return new Path(segments, beginAtSegment, segments.length-beginAtSegment);
345 }
346
347 public Path getSubPath(int beginAtSegment, int endSegment)
348 {
349 return new Path(segments, beginAtSegment, endSegment-beginAtSegment);
350 }
351
352 public String getBaseName()
353 {
354 return baseName;
355 }
356
357 public String getFileExtension()
358 {
359 return fileExtension;
360 }
361
362 public String getFileName()
363 {
364 return fileName;
365 }
366
367 public String getQueryString()
368 {
369 return queryString;
370 }
371
372 public int length()
373 {
374 return segments.length;
375 }
376
377 public String toString()
378 {
379 return path;
380 }
381
382 private String buildPath()
383 {
384 int len = 0;
385 for (int i = 0; i < segments.length; i++)
386 {
387 len+=segments[i].length()+1;
388 }
389 if (queryString!=null)
390 {
391 len+=queryString.length()+1;
392 }
393 char[] buffer = new char[len];
394 int index = 0;
395 for (int i = 0; i < segments.length; i++ )
396 {
397 buffer[index++] = '/';
398 len = segments[i].length();
399 segments[i].getChars(0, len, buffer, index);
400 index+= len;
401 }
402 if (queryString != null)
403 {
404 buffer[index++] = '?';
405 len = queryString.length();
406 queryString.getChars(0, len, buffer, index);
407 }
408 return new String(buffer);
409 }
410
411 public boolean equals(Object obj)
412 {
413 if (obj instanceof Path)
414 {
415 Path other = (Path)obj;
416 if ( (other.queryString != null && other.queryString.equals(queryString)) ||
417 (other.queryString == null && queryString == null) )
418 {
419 return Arrays.equals(other.segments,segments);
420 }
421 }
422 return false;
423 }
424
425 public int hashCode()
426 {
427 return hashCode;
428 }
429
430 /***
431 * Removes the last directory segment in this path. This method <strong>WILL
432 * NOT</strong> remove the fileSegment, but path segment immediately before
433 * it.
434 *
435 * @return segment removed.
436 */
437 public Path removeLastPathSegment()
438 {
439 if ((fileName != null && segments.length == 1) || segments.length == 0)
440 {
441 return this;
442 }
443 return new Path(this);
444 }
445
446 public Path getChild(String childPath)
447 {
448 synchronized (childrenMap)
449 {
450 Path child = null;
451 HashMap children = (HashMap)childrenMap.get(path);
452 if (children == null)
453 {
454 children = new HashMap();
455 childrenMap.put(path, children);
456 }
457 else
458 {
459 child = (Path)children.get(childPath);
460 }
461 if ( child == null )
462 {
463 if (segments.length == 0)
464 {
465 child = new Path(childPath);
466 }
467 else
468 {
469 child = new Path(this, childPath, true);
470 }
471 children.put(childPath, child);
472 }
473 return child;
474 }
475 }
476
477 public Path getChild(Path childPath)
478 {
479 return getChild(childPath.path);
480 }
481
482 private static String[] split(String str, int start, int length, char separator)
483 {
484 String[] result;
485 char[] buffer = str.toCharArray();
486 int tokens = 0;
487 boolean token = false;
488 for (int i = start; i < length; i++)
489 {
490 if (buffer[i]==separator)
491 {
492 token = false;
493 }
494 else if (!token)
495 {
496 tokens++;
497 token = true;
498 }
499 }
500 result = new String[tokens];
501 if (tokens > 0)
502 {
503 int begin = start;
504 int end = start;
505 token = false;
506 tokens = 0;
507 for (int i = start; i < length; i++)
508 {
509 if (buffer[i]==separator)
510 {
511 if (token)
512 {
513 result[tokens++] = new String(buffer,begin,end);
514 token = false;
515 }
516 }
517 else if (!token)
518 {
519 token = true;
520 begin = i;
521 end = 1;
522 }
523 else
524 {
525 end++;
526 }
527 }
528 if (token)
529 {
530 result[tokens] = new String(buffer,begin, end);
531 }
532 }
533 return result;
534 }
535 }