View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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 }