/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
public class Path
{
public static final Path ROOT = new Path( "/" );
private String pathAsStringWithoutFragment;
private String pathAsString;
private String pathAsStringInLowerCase;
private List<String> pathElements;
private String fragment;
public Path( String path )
{
this( path, false );
}
public Path( String path, boolean enforcePathStartsWithSlash )
{
if ( path == null )
{
throw new IllegalArgumentException( "Given path cannot be null" );
}
if ( enforcePathStartsWithSlash && ( path.length() > 0 && path.charAt( 0 ) != '/' ) )
{
path = "/" + path;
}
resolvePathAndFragment( path );
initPathElements();
}
public Path( Collection<String> path, boolean startWithSlash )
{
this( path, startWithSlash, null );
}
public Path( Collection<String> path, boolean startWithSlash, String fragment )
{
StringBuffer s = new StringBuffer( 25 * path.size() );
if ( startWithSlash )
{
s.append( "/" );
}
this.pathElements = new ArrayList<String>( path.size() );
int i = 0;
for ( String element : path )
{
this.pathElements.add( element );
s.append( element );
if ( i < path.size() - 1 )
{
s.append( "/" );
}
i++;
}
this.pathAsStringWithoutFragment = s.toString();
if ( fragment != null )
{
this.pathAsString = s.toString() + "#" + fragment;
}
else
{
this.pathAsString = s.toString();
}
this.fragment = fragment;
}
private void resolvePathAndFragment( String path )
{
path = path.trim();
int fragmentStart = path.indexOf( "#" );
if ( fragmentStart == -1 )
{
pathAsString = path;
pathAsStringWithoutFragment = path;
}
else
{
pathAsStringWithoutFragment = path.substring( 0, fragmentStart );
fragment = path.substring( fragmentStart + 1 );
pathAsString = pathAsStringWithoutFragment + "#" + fragment;
}
}
private void initPathElements()
{
pathElements = new ArrayList<String>();
StringTokenizer pathTokenizer = new StringTokenizer( pathAsStringWithoutFragment, "/" );
while ( pathTokenizer.hasMoreTokens() )
{
String element = pathTokenizer.nextToken();
if ( element.length() > 0 )
{
pathElements.add( element );
}
}
}
public boolean isRoot()
{
return pathElements.size() == 0;
}
public List<String> getPathElements()
{
return Collections.unmodifiableList( pathElements );
}
public int getPathElementsCount()
{
return pathElements.size();
}
public String getPathElement( int index )
{
return pathElements.get( index );
}
public int numberOfElements()
{
return pathElements.size();
}
public String getLastPathElement()
{
return pathElements.get( pathElements.size() - 1 );
}
public String getFirstPathElement()
{
return pathElements.get( 0 );
}
/**
* Returns the path element after the given series of path elements. If series is not found, null is returned.
* If not path element after the series, then null is returned.
*
* @param elements
* @return
*/
public String getPathElementAfter( final String... elements )
{
int i = 0;
while ( i < pathElements.size() )
{
if ( pathElements.get( i ).equals( elements[0] ) )
{
boolean allEqualsSuccessively = true;
for ( int j = 1; j < elements.length; j++ )
{
if ( pathElements.size() <= i + j )
{
allEqualsSuccessively = false;
break;
}
if ( !pathElements.get( i + j ).equals( elements[j] ) )
{
allEqualsSuccessively = false;
break;
}
}
if ( allEqualsSuccessively && pathElements.size() > i + elements.length )
{
return pathElements.get( i + elements.length );
}
}
i++;
}
return null;
}
public Path appendPathElement( final String pathElement )
{
if ( pathElement == null )
{
throw new IllegalArgumentException( "Given pathElement cannot be null" );
}
final String existingPathAsString = this.getPathWithoutFragmentAsString();
StringBuffer buff = new StringBuffer();
buff.append( existingPathAsString );
if ( !existingPathAsString.endsWith( "/" ) )
{
buff.append( "/" );
}
buff.append( pathElement );
if ( this.hasFragment() )
{
buff.append( "#" ).append( this.getFragment() );
}
Path newPath = new Path( buff.toString() );
return newPath;
}
public Path appendPath( Path path )
{
StringBuffer newPath = new StringBuffer( toString() );
if ( !endsWithSlash() && !isEmpty() && path.isRelative() )
{
newPath.append( "/" );
}
if ( path.isAbsolute() && endsWithSlash() )
{
newPath.append( path.toString().substring( 1 ) );
}
else
{
newPath.append( path );
}
return new Path( newPath.toString() );
}
public String getPathAsString()
{
return pathAsString;
}
public String getPathWithoutFragmentAsString()
{
return pathAsStringWithoutFragment;
}
public String getPathAsStringInLowerCase()
{
if ( pathAsStringInLowerCase == null )
{
pathAsStringInLowerCase = getPathAsString().toLowerCase();
}
return pathAsStringInLowerCase;
}
public boolean endsWithSlash()
{
return pathAsStringWithoutFragment.length() > 0 &&
pathAsStringWithoutFragment.charAt( pathAsStringWithoutFragment.length() - 1 ) == '/';
}
public boolean endsWith( String suffix )
{
return pathAsStringWithoutFragment.endsWith( suffix );
}
public boolean endsWithIgnoreCase( String suffix )
{
return getPathAsStringInLowerCase().endsWith( suffix.toLowerCase() );
}
public boolean startsWithSlash()
{
return pathAsStringWithoutFragment.startsWith( "/" );
}
public boolean isAbsolute()
{
return pathAsStringWithoutFragment.startsWith( "/" );
}
public boolean isRelative()
{
return !pathAsStringWithoutFragment.startsWith( "/" );
}
public boolean isEmpty()
{
return pathAsString.length() == 0;
}
public boolean contains( String substring )
{
return this.pathAsStringWithoutFragment.contains( substring );
}
public boolean containsSubPath( String... subPathElements )
{
int i = 0;
while ( i < pathElements.size() )
{
if ( pathElements.get( i ).equals( subPathElements[0] ) )
{
boolean allEqualsSuccessively = true;
for ( int j = 1; j < subPathElements.length; j++ )
{
if ( pathElements.size() <= i + j )
{
allEqualsSuccessively = false;
break;
}
if ( !pathElements.get( i + j ).equals( subPathElements[j] ) )
{
allEqualsSuccessively = false;
break;
}
}
if ( allEqualsSuccessively )
{
return true;
}
}
i++;
}
return false;
}
public Path substractLastPathElement()
{
final List<String> source = this.getPathElements();
final List<String> newList = new ArrayList<String>( source.size() );
int count = 0;
for ( String sourceString : source )
{
newList.add( sourceString );
count++;
if ( count >= source.size() - 1 )
{
break;
}
}
Path newPath = new Path( newList, this.startsWithSlash(), this.getFragment() );
return newPath;
}
public Path subtractPath( String path )
{
if ( !this.toString().startsWith( path ) )
{
return new Path( this.toString() );
}
String newPath = pathAsStringWithoutFragment.substring( path.length(), pathAsStringWithoutFragment.length() );
if ( hasFragment() )
{
newPath += "#" + getFragment();
}
return new Path( newPath );
}
public String subPath( int startPathElementIndex, int endPathElementIndex )
{
if ( startPathElementIndex < 0 )
{
throw new IllegalArgumentException( "Given startPathElementIndex cannot be negative" );
}
if ( endPathElementIndex > pathElements.size() )
{
throw new IllegalArgumentException(
"Given endPathElementIndex cannot be larger than the path element count: " + pathElements.size() );
}
StringBuffer pathStr = new StringBuffer();
for ( int i = startPathElementIndex; i < endPathElementIndex; i++ )
{
pathStr.append( getPathElement( i ) );
if ( i < endPathElementIndex - 1 )
{
pathStr.append( "/" );
}
}
return pathStr.toString();
}
public boolean startsWith( String s )
{
return pathAsString.startsWith( s );
}
public int length()
{
return toString().length();
}
public String getAsUrlEncoded( boolean includeFragment, String encoding )
{
StringBuffer s = new StringBuffer();
try
{
if ( startsWithSlash() )
{
s.append( "/" );
}
final boolean endsWithSlash = endsWithSlash();
for ( int i = 0; i < pathElements.size(); i++ )
{
s.append( URLEncoder.encode( pathElements.get( i ), encoding ) );
if ( i == pathElements.size() - 1 && hasFragment() && includeFragment )
{
String encodedFragment = URLEncoder.encode( fragment, encoding );
s.append( "#" );
s.append( encodedFragment );
}
if ( i < pathElements.size() - 1 || endsWithSlash )
{
s.append( "/" );
}
}
}
catch ( UnsupportedEncodingException e )
{
throw new RuntimeException( e.getMessage(), e );
}
return s.toString();
}
public boolean hasFragment()
{
return fragment != null;
}
public String getFragment()
{
return fragment;
}
public int indexOf( String pathElement )
{
int index = -1;
for ( String s : getPathElements() )
{
index++;
if ( s.equals( pathElement ) )
{
return index;
}
}
return -1;
}
public boolean equals( Object obj )
{
if ( this == obj )
{
return true;
}
if ( obj == null || getClass() != obj.getClass() )
{
return false;
}
Path other = (Path) obj;
if ( !pathAsString.equals( other.pathAsString ) )
{
return false;
}
return true;
}
public int hashCode()
{
return pathAsString.hashCode();
}
public String toString()
{
return pathAsString;
}
public Path removeTrailingSlash()
{
Path newPath = new Path( this.getPathElements(), this.startsWithSlash(), this.getFragment() );
return newPath;
}
}