/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2008 Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.KeyStroke;
import bibliothek.extension.gui.dock.preference.preferences.KeyStrokeValidator;
import bibliothek.extension.gui.dock.preference.preferences.choice.Choice;
import bibliothek.gui.dock.control.ModifierMask;
/**
* A path is a description of the position of some resource. A path consists
* of segments where a segment can be any kind of string (preferably a segment is a
* valid java-identifier). A path can be converted into a string, the segments will
* be {@link #encodeSegment(String) encoded} when doing that.
* @author Benjamin Sigg
*/
public final class Path {
/** standard path for {@link Integer} */
public static final Path TYPE_INT_PATH = new Path( "java.lang.Integer" );
/** standard path for {@link String} */
public static final Path TYPE_STRING_PATH = new Path( "java.lang.String" );
/** standard path for {@link Boolean} */
public static final Path TYPE_BOOLEAN_PATH = new Path( "java.lang.Boolean" );
/** standard path for {@link KeyStroke}, can use {@link KeyStrokeValidator} as information */
public static final Path TYPE_KEYSTROKE_PATH = new Path( "javax.swing.KeyStroke" );
/** standard path for {@link ModifierMask} */
public static final Path TYPE_MODIFIER_MASK_PATH = new Path( "dock.modifier_mask" );
/** standard path for a choice using a {@link String} as value and a {@link Choice} as information */
public static final Path TYPE_STRING_CHOICE_PATH = new Path( "dock.choice" );
/** standard path for a label, a label is not shown in an enabled editor */
public static final Path TYPE_LABEL = new Path( "dock.label" );
/**
* Puts an escape character before any illegal character of <code>segment</code>, thus
* creating a valid segment.
* @param segment the segment to encode
* @return the valid segment
*/
public static String encodeSegment( String segment ){
StringBuilder builder = new StringBuilder( segment.length() );
for( int i = 0, n = segment.length(); i<n; i++ ){
char c = segment.charAt( i );
boolean escape = false;
if( c == '.' ){
escape = true;
}
else if( c == '\\' ){
escape = true;
}
else if( i == 0 ){
if( !Character.isJavaIdentifierStart( c )){
escape = true;
}
}
else{
if( !Character.isJavaIdentifierPart( c )){
escape = true;
}
}
if( escape ){
builder.append( '\\' );
}
builder.append( c );
}
return builder.toString();
}
/**
* The opposite of {@link #encodeSegment(String)}.
* @param segment some segment with escape characters
* @return the original form of the segment
*/
public static String decodeSegment( String segment ){
StringBuilder builder = new StringBuilder( segment.length() );
boolean escape = false;
for( int i = 0, n = segment.length(); i<n; i++ ){
char c = segment.charAt( i );
if( escape ){
escape = false;
builder.append( c );
}
else if( c == '\\'){
escape = true;
}
else{
builder.append( c );
}
}
return builder.toString();
}
/** the segments of this path */
private String[] segments;
/**
* Tells whether <code>path</code> is a valid path or not
* @param path the path to test
* @return <code>true</code> if the segment is valid
*/
public static boolean isValidPath( String path ){
try{
new Path( path );
return true;
}
catch( IllegalArgumentException ex ){
return false;
}
}
/**
* Creates a new path with the given segments.
* @param segments the path
*/
public Path( String... segments ){
this.segments = new String[ segments.length ];
System.arraycopy( segments, 0, this.segments, 0, segments.length );
for( String check : this.segments ){
if( check == null ){
throw new IllegalArgumentException( "null segments are not allowed" );
}
}
}
/**
* Creates a new root path.
*/
public Path(){
segments = new String[]{};
}
/**
* Creates a new path.
* @param path the dot-separated segments of this path, each segment
* must be a valid Java-identifier. Note that no segment should start with
* "_". Clients may use {@link #encodeSegment(String)} to use any character
* within a single segment.
*/
public Path( String path ){
if( path == null )
throw new IllegalArgumentException( "path must not be null" );
List<String> list = new ArrayList<String>();
int lastDot = -1;
boolean escape = false;
for( int i = 0, n = path.length(); i <= n; i++ ){
char c;
if( i == n ){
escape = false;
c = '.';
}
else{
c = path.charAt( i );
}
if( escape ){
escape = false;
}
else{
if( c == '.' ){
if( lastDot+1 == i )
throw new IllegalArgumentException( "not a path: empty segment" );
list.add( decodeSegment( path.substring( lastDot+1, i ) ) );
lastDot = i;
}
else if( c == '\\' ){
escape = true;
}
else if( lastDot+1 == i ){
if( !Character.isJavaIdentifierStart( c ))
throw new IllegalArgumentException( "not a valid start of a segment: '" + c + "'" );
}
else{
if( !Character.isJavaIdentifierPart( c ))
throw new IllegalArgumentException( "not a valid character of a segment: '" + c + "'" );
}
}
}
segments = list.toArray( new String[ list.size()] );
}
/**
* Gets the number of segments of this path.
* @return the number of segments
*/
public int getSegmentCount(){
return segments.length;
}
/**
* Gets the <code>index</code>'th segment of this path.
* @param index the location of the segment
* @return the segment
*/
public String getSegment( int index ){
return segments[index];
}
/**
* Gets the last segment of this path or <code>null</code> if this is
* the root path.
* @return the last segment or <code>null</code>
*/
public String getLastSegment(){
if( segments.length == 0 )
return null;
return segments[ segments.length-1 ];
}
/**
* Creates a new path that is a subset of this path.
* @param offset the begin of the new path
* @param length the length of the new path, at least 1
* @return the new path
*/
public Path subPath( int offset, int length ){
if( length < 1 )
throw new IllegalArgumentException( "length must be at least 1: " + length );
String[] result = new String[ length ];
System.arraycopy( segments, offset, result, 0, length );
return new Path( result );
}
/**
* Creates a new path which is a combination of <code>this</code> and <code>path</code>.
* @param path the path to add
* @return the new path
*/
public Path append( Path path ){
String[] segments = new String[ this.segments.length + path.segments.length ];
System.arraycopy( this.segments, 0, segments, 0, this.segments.length );
System.arraycopy( path.segments, 0, segments, this.segments.length, path.segments.length );
return new Path( segments );
}
/**
* Creates a new path which is not only a combination of <code>this</code>
* and <code>path</code>, but is also unique in the way that
* <code>x+y.z</code> would not yield the same as <code>x.y+z</code>. This
* implies also that <code>(x+y)+z</code> would result in another path
* than <code>x+(y+z)</code>. Note that the result of this method differs
* from {@link #append(Path)}. Note also that the new path has a different
* prefix than <code>this</code>.
* @param path the additional path
* @return the new path
*/
public Path uniqueAppend( Path path ){
String[] segments = new String[ this.segments.length + 2 + path.segments.length ];
segments[0] = "_f" + this.segments.length;
System.arraycopy( this.segments, 0, segments, 1, this.segments.length );
segments[ this.segments.length+1 ] = "_s" + path.segments.length;
System.arraycopy( path.segments, 0, segments, this.segments.length+2, path.segments.length );
return new Path( segments );
}
/**
* Creates a new path appending <code>segments</code> to this path.
* @param segments the additional segments
* @return the new path
*/
public Path append( String segments ){
return append( new Path( segments ));
}
/**
* Returns the parent of this path.
* @return the parent or <code>null</code> if this is the root
*/
public Path getParent(){
if( segments.length == 0 )
return null;
String[] result = new String[ segments.length-1 ];
System.arraycopy( segments, 0, result, 0, result.length );
return new Path( result );
}
/**
* Tells whether the first segments of this {@link Path} matches
* the segments of <code>path</code>.
* @param path some other path
* @return <code>true</code> if this path is either equal to <code>path</code> or
* if this path starts with <code>path</code>
*/
public boolean startsWith( Path path ){
if( path.getSegmentCount() > getSegmentCount() ){
return false;
}
for( int i = 0, n = path.getSegmentCount(); i<n; i++ ){
if( !path.getSegment( i ).equals( getSegment( i ) )){
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode( segments );
}
@Override
public boolean equals( Object obj ) {
if( this == obj )
return true;
if( obj == null )
return false;
if( getClass() != obj.getClass() )
return false;
final Path other = (Path)obj;
if( !Arrays.equals( segments, other.segments ) )
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for( int i = 0, n = segments.length; i<n; i++ ){
if( i > 0 )
builder.append( "." );
builder.append( encodeSegment( segments[i] ) );
}
return builder.toString();
}
}