/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander 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 3 of the License, or
* (at your option) any later version.
*
* muCommander 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file.util;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* This class allows to break a path into filename tokens. The default separator characters are '/' and '\', but any
* other separator characters can be specified. The {@link #hasMoreFilenames()} and {@link #nextFilename()} methods
* can be used to iterate through all filename tokens. The {@link #getLastSeparator()} returns the last separator
* string that appeared before the last filename token returned by {@link #nextFilename()}. Initially, this method
* returns any leading separators the path may contain.
* The {@link #getCurrentPath()} returns the current part of the path that has been tokenized.
*
* <p>To illustrate the use of PathTokenizer, the following piece of code iterates through all filename tokens and
* reconstructs the original path :
* <code>
* PathTokenizer pt = new PathTokenizer(path);
* String reconstructedPath = pt.getLastSeparator();
* while(pt.hasMoreFilenames()) {
* String nextToken = pt.nextFilename();
* String lastSeparator = pt.getLastSeparator();
* reconstructedPath += nextToken+lastSeparator;
* }
* </code>
*
* <p>Note that PathTokenizer does not enforce valid file paths, that means a path with an incorrect syntax can still
* be tokenized. In particular:
* <ul>
* <li>A path can contain mixed separators, e.g. C:\temp/file
* <li>Filename tokens can be separated by multiple separator characters, e.g. /usr//local, {@link #getLastSeparator()}
* will return the complete separator string, in this case "//" for the 'usr' filename token.
* </ul>
*
* @author Maxence Bernard
*/
public class PathTokenizer implements Enumeration<String> {
/** Separator characters */
private String separators;
/** True if this PathTokenizer tokenizes the path in reverse order, from right to left. */
private boolean reverseOrder;
/** Path tokens: separators and filenames. */
private String[] tokens;
/** Current index in the token array. */
private int currentIndex;
/** Path part that has been tokenized. */
private StringBuffer currentPath;
/** Last separators token. */
private String lastSeparator;
/** Default separator characters. */
public final static String DEFAULT_SEPARATORS = "/\\";
/**
* Creates a new PathTokenizer using the given path and default separator characters ({@link #DEFAULT_SEPARATORS}},
* in forward order, tokenizing the path from left to right.
*
* @param path the path to break into tokens
*/
public PathTokenizer(String path) {
this(path, DEFAULT_SEPARATORS, false);
}
/**
* Creates a new PathTokenizer using the given path and separator character(s).
*
* @param path the path to break into tokens
* @param separators the character(s) that separate tokens
* @param reverseOrder if true, the path will be tokenized in reverse order, from right to left
*/
public PathTokenizer(String path, String separators, boolean reverseOrder) {
this.separators = separators;
this.reverseOrder = reverseOrder;
// Split the path into tokens
StringTokenizer st = new StringTokenizer(path, separators, true);
Vector<String> tokensV = new Vector<String>();
while(st.hasMoreTokens()) {
tokensV.add(st.nextToken());
}
// Convert Vector into array
tokens = new String[tokensV.size()];
int nbTokens = tokens.length;
if(reverseOrder) {
for(int i=0; i<nbTokens; i++)
tokens[i] = tokensV.elementAt(nbTokens-i-1);
}
else {
tokensV.toArray(tokens);
}
// Initialize current path
if(reverseOrder)
currentPath = new StringBuffer(path);
else
currentPath = new StringBuffer(path.length());
// Skip leading separator
skipSeparators();
}
/**
* Skips separator tokens and advances the internal token index, until either a filename token has been found
* or the end of the path has been reached.
*/
private void skipSeparators() {
lastSeparator = "";
String token;
while(currentIndex<tokens.length && separators.indexOf(token=tokens[currentIndex])!=-1) {
// Update last separator
lastSeparator += token;
// Update current path
handleToken(token);
currentIndex++;
}
}
/**
* Returns <code>true</code> if this PathTokenizer has more filename tokens.
* @return <code>true</code> if this PathTokenizer has more filename tokens, <code>false</code> otherwise.
*/
public boolean hasMoreFilenames() {
return currentIndex<tokens.length;
}
private void handleToken(String token) {
if(reverseOrder)
currentPath.setLength(currentPath.length()-token.length());
else
currentPath.append(token);
}
/**
* Returns the next filename token from this PathTokenizer. Throws a NoSuchElementException if no more filename
* tokens are available. After calling this method, the values returned by {@link #getLastSeparator()} and
* {@link #getCurrentPath()} will be updated.
*
* @return the next filename token from this PathTokenizer
* @throws NoSuchElementException if no more tokens are available
*/
public String nextFilename() throws NoSuchElementException {
if(currentIndex<tokens.length) {
String token = tokens[currentIndex++];
// Update current path
handleToken(token);
// Skip separators after the filename
skipSeparators();
return token;
}
else
throw new NoSuchElementException();
}
/**
* Returns the current path part that has been tokenized, i.e. that ends with the last filename token returned by
* {@link #nextFilename()} and separator string returned by {@link #getLastSeparator()}.<br>
* If this PathTokenizer operates in reverse order, the returned path is the path part that has not yet been
* tokenized.
* @return the current path part that has been tokenized.
*/
public String getCurrentPath() {
return currentPath.toString();
}
/**
* Returns the last separator string that appeared in the path after the last filename token returned by
* {@link #nextFilename()} and before the next filename, or an empty string "" if there isn't any separator
* character after the filename (path ends without a trailing separator).<br>
* Note: the returned string can be made of several consecutive separator characters.
*
* <p>Initially, before any calls to {@link #nextFilename()} have been made, this method will return any leading
* separator string in the path string, or an empty string if the path doesn't start with a separator.
* @return the last separator string that appeared in the path.
*/
public String getLastSeparator() {
return lastSeparator;
}
////////////////////////////////
// Enumeration implementation //
////////////////////////////////
/**
* Enumeration implementation, returns the same value as {@link #hasMoreFilenames()}.
*/
public boolean hasMoreElements() {
return hasMoreFilenames();
}
/**
* Enumeration implementation, returns the same value as {@link #nextFilename()}.
*/
public String nextElement() throws NoSuchElementException {
return nextFilename();
}
}