/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
/**
* Overview
*
* The Path object is used to standard used to standardize the creation of
* mutation of path-like structures. For: example /foo/bar/index.html.
*
* Rules for Interperting Pathes
*
* Below are the rules for how the constructor interprets literal paths.
* NOTE the {@link addSegment(String)} interprets string
* pathes in a somewhat different manner.
*
* Literal Path
* Interpretation
*
* /foo/bar/index.html
* foo
and bar
will be considered directory
* segments while index.html
will be considered a file segment.
* This means that the baseName
will be set to index and
* the fileExtension
will be set to .html
*
* /foo/bar/, /foo/bar, foo/bar/ foo/bar
*
*
*
* foo
and bar
will be considered directory
* segments. baseName
and fileExtension
will be
* left as null
.
*
* I cases where a file has no extension you must use the
* {@link setFileSegment(String))} to manually set the file. This causes the
* baseName
to be set to the file name specified and the
* fileExtension
will be set to the empty string ("").
*
*
*
*
*
* @author Scott T. Weaver
*/
public class Path implements Serializable, Cloneable
{
/** The serial version uid. */
private static final long serialVersionUID = 6890966283704092945L;
public static final String PATH_SEPERATOR = "/";
private static final String[] EMPTY_SEGMENTS = new String[0];
private static HashMap childrenMap = new HashMap();
private final String path;
private final String[] segments;
private final String fileName;
private final String baseName;
private final String fileExtension;
private final String queryString;
private final int hashCode;
public Path()
{
segments = EMPTY_SEGMENTS;
fileName = null;
baseName = null;
fileExtension = null;
queryString = null;
hashCode = 0;
path = "";
}
private Path(Path parent, String childSegment, boolean pathOnly)
{
this(parent, splitPath(childSegment), pathOnly);
}
private Path(Path parent, String[] children, boolean pathOnly)
{
int code = 0;
if (!pathOnly)
{
this.fileName = parent.fileName;
this.baseName = parent.baseName;
this.fileExtension = parent.fileExtension;
this.queryString = parent.queryString;
if (queryString != null)
{
code += queryString.hashCode();
}
}
else
{
fileName = null;
baseName = null;
fileExtension = null;
queryString = null;
}
int size = parent.segments.length;
if (pathOnly && parent.fileName != null)
{
size--;
}
int index = 0;
segments = new String[size+children.length];
for (index = 0; index < size; index++)
{
segments[index] = parent.segments[index];
code += segments[index].hashCode();
}
for (int i = 0; i < children.length; i++, index++)
{
segments[index] = children[i];
code += segments[index].hashCode();
}
if (fileName != null && !pathOnly)
{
segments[index] = fileName;
code += segments[index].hashCode();
}
hashCode = code;
path = buildPath();
}
private Path(Path parent)
{
this.fileName = parent.fileName;
this.baseName = parent.baseName;
this.fileExtension = parent.fileExtension;
this.queryString = parent.queryString;
segments = new String[parent.segments.length-1];
int code = 0;
for (int i = 0; i < parent.segments.length-2; i++)
{
segments[i] = parent.segments[i];
code += segments.hashCode();
}
if (fileName != null)
{
segments[segments.length-1] = fileName;
}
else if (parent.segments.length > 1)
{
segments[segments.length-1] = parent.segments[parent.segments.length-2];
}
if ( segments.length > 0)
{
code += segments[segments.length-1].hashCode();
}
if (queryString != null)
{
code += queryString.hashCode();
}
hashCode = code;
path = buildPath();
}
private Path(String[] segments, int offset, int count)
{
this.segments = new String[count];
int code = 0;
for (int i = 0; i < count; i++)
{
this.segments[i] = segments[offset+i];
code+=segments[offset+i].hashCode();
}
hashCode = code;
if (count > 0)
{
fileName = this.segments[count-1];
int extIndex = fileName.lastIndexOf('.');
if (extIndex > -1)
{
baseName = fileName.substring(0, extIndex);
fileExtension = fileName.substring(extIndex);
}
else
{
baseName = fileName;
fileExtension = "";
}
}
else
{
fileName = null;
baseName = null;
fileExtension = null;
}
queryString = null;
path = buildPath();
}
public Path(String path)
{
String tmp = path.replace('\\', '/');
if (!tmp.startsWith("/"))
{
tmp = "/" + tmp;
}
this.path = tmp;
if (path.equals("/"))
{
segments = new String[]{""};
fileName = null;
baseName = null;
fileExtension = null;
queryString = null;
hashCode = 0;
}
else
{
int queryStart = path.indexOf('?');
int len = queryStart > -1 ? queryStart : path.length();
segments = split(path, 0, len, '/');
int code = 0;
for (int i = 0; i < segments.length; i++)
{
code += segments[i].hashCode();
}
String tmpFileName = null;
if (queryStart > 1 && path.length() > queryStart+1)
{
queryString = path.substring(queryStart+1);
code += queryString.hashCode();
}
else
{
queryString = null;
}
hashCode = code;
int extIndex = -1;
if (segments.length > 0)
{
tmpFileName = segments[segments.length-1];
extIndex = tmpFileName.lastIndexOf('.');
}
if (extIndex > -1)
{
fileName = tmpFileName;
baseName = tmpFileName.substring(0, extIndex);
fileExtension = tmpFileName.substring(extIndex);
}
else
{
fileName = null;
baseName = null;
fileExtension = null;
}
}
}
private static String[] splitPath(String path)
{
String[] children = null;
path = path.replace('\\', '/');
if (path.startsWith("/"))
{
path = "/" + path;
}
if (path.equals("/"))
{
children = new String[]{""};
}
else
{
int index = path.indexOf('?');
int len = index > -1 ? index : path.length();
children = split(path, 0, len, '/');
}
return children;
}
/**
* Returns the segement of the path at the specified index i
.
*
* @param i
* index containing the segment to return.
* @return Segment at index i
* @throws ArrayIndexOutOfBoundsException
* if the index is not within the bounds of this Path.
*/
public String getSegment(int i)
{
return segments[i];
}
/**
*
* Adds this segment to the end of the path but before the current file
* segment, if one exists. For consistency Segments added via this method
* are ALWAYS considered directories even when matching a
* standrad file pattern i.e. index.html
*
*
* If you need to set the file segment, please use the setFileSegment()
* method.
*
*
* @param segment
* @return
*/
public Path addSegment(String segment)
{
return new Path(this, segment, false);
}
public Path getSubPath(int beginAtSegment)
{
return new Path(segments, beginAtSegment, segments.length-beginAtSegment);
}
public Path getSubPath(int beginAtSegment, int endSegment)
{
return new Path(segments, beginAtSegment, endSegment-beginAtSegment);
}
public String getBaseName()
{
return baseName;
}
public String getFileExtension()
{
return fileExtension;
}
public String getFileName()
{
return fileName;
}
public String getQueryString()
{
return queryString;
}
public int length()
{
return segments.length;
}
public String toString()
{
return path;
}
private String buildPath()
{
int len = 0;
for (int i = 0; i < segments.length; i++)
{
len+=segments[i].length()+1;
}
if (queryString!=null)
{
len+=queryString.length()+1;
}
char[] buffer = new char[len];
int index = 0;
for (int i = 0; i < segments.length; i++ )
{
buffer[index++] = '/';
len = segments[i].length();
segments[i].getChars(0, len, buffer, index);
index+= len;
}
if (queryString != null)
{
buffer[index++] = '?';
len = queryString.length();
queryString.getChars(0, len, buffer, index);
}
return new String(buffer);
}
public boolean equals(Object obj)
{
if (obj instanceof Path)
{
Path other = (Path)obj;
if ( (other.queryString != null && other.queryString.equals(queryString)) ||
(other.queryString == null && queryString == null) )
{
return Arrays.equals(other.segments,segments);
}
}
return false;
}
public int hashCode()
{
return hashCode;
}
/**
* Removes the last directory segment in this path. This method WILL
* NOT remove the fileSegment, but path segment immediately before
* it.
*
* @return segment removed.
*/
public Path removeLastPathSegment()
{
if ((fileName != null && segments.length == 1) || segments.length == 0)
{
return this;
}
return new Path(this);
}
public Path getChild(String childPath)
{
synchronized (childrenMap)
{
Path child = null;
HashMap children = (HashMap)childrenMap.get(path);
if (children == null)
{
children = new HashMap();
childrenMap.put(path, children);
}
else
{
child = (Path)children.get(childPath);
}
if ( child == null )
{
if (segments.length == 0)
{
child = new Path(childPath);
}
else
{
child = new Path(this, childPath, true);
}
children.put(childPath, child);
}
return child;
}
}
public Path getChild(Path childPath)
{
return getChild(childPath.path);
}
private static String[] split(String str, int start, int length, char separator)
{
String[] result;
char[] buffer = str.toCharArray();
int tokens = 0;
boolean token = false;
for (int i = start; i < length; i++)
{
if (buffer[i]==separator)
{
token = false;
}
else if (!token)
{
tokens++;
token = true;
}
}
result = new String[tokens];
if (tokens > 0)
{
int begin = start;
int end = start;
token = false;
tokens = 0;
for (int i = start; i < length; i++)
{
if (buffer[i]==separator)
{
if (token)
{
result[tokens++] = new String(buffer,begin,end);
token = false;
}
}
else if (!token)
{
token = true;
begin = i;
end = 1;
}
else
{
end++;
}
}
if (token)
{
result[tokens] = new String(buffer,begin, end);
}
}
return result;
}
}