blob: 5ffbc0a69b16eb8e8fa4837153af0dd907e9df2b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2015 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.space;
import java.io.File;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
/**
* {@link Iterator} that iterates over named entries beneath a file-system directory.
*/
final class FileEntryIterator
implements Iterator<String>
{
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final String rootPath;
private final Deque<String> entryNames = new ArrayDeque<String>();
private final boolean recurse;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
/**
* Creates an iterator that iterates over entries beneath the given file URL and sub-path.
*
* @param url The root file URL
* @param subPath The path below the root URL
* @param recurse When {@code true} include sub-directories; otherwise don't
*/
FileEntryIterator( final URL url, final String subPath, final boolean recurse )
{
rootPath = normalizePath( toFile( url ).getAbsoluteFile() );
this.recurse = recurse;
appendEntries( subPath );
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
public boolean hasNext()
{
return entryNames.size() > 0;
}
public String next()
{
final String name = entryNames.removeFirst();
if ( recurse && name.endsWith( "/" ) )
{
appendEntries( name );
}
return name;
}
public void remove()
{
throw new UnsupportedOperationException();
}
// ----------------------------------------------------------------------
// Local methods
// ----------------------------------------------------------------------
/**
* Converts a {@link URL} into a {@link File} converting slashes and encoded characters where appropriate.
*
* @param url The file URL
* @return File instance for the given URL
*/
static File toFile( final URL url )
{
final StringBuilder buf = new StringBuilder();
final String authority = url.getAuthority();
if ( null != authority && authority.length() > 0 )
{
buf.append( File.separatorChar ).append( File.separatorChar ).append( authority );
}
final String path = url.getPath();
int codePoint = 0, expectBytes = 0;
for ( int i = 0, length = path.length(); i < length; i++ )
{
final char c = path.charAt( i );
if ( '/' == c )
{
buf.append( File.separatorChar );
}
else if ( '%' == c && i < length - 2 )
{
final int hi = Character.digit( path.charAt( i + 1 ), 16 );
final int lo = Character.digit( path.charAt( i + 2 ), 16 );
if ( hi >= 0 && lo >= 0 )
{
if ( hi < 8 )
{
buf.append( (char) ( hi << 4 | lo ) );
}
else if ( hi >= 12 )
{
// prepare multi-byte UTF-8 sequence
expectBytes = 12 == hi ? 1 : hi - 12;
codePoint = ( 13 == hi ? 0x10 + lo : lo ) << 6 * expectBytes;
}
else if ( expectBytes > 0 )
{
// update multi-byte UTF-8 sequence
codePoint |= ( ( 0x03 & hi ) << 4 | lo ) << 6 * --expectBytes;
if ( expectBytes == 0 )
{
buf.appendCodePoint( codePoint );
}
}
i += 2;
}
else
{
buf.append( '%' );
}
}
else
{
buf.append( c );
}
}
return new File( buf.toString() );
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
/**
* Appends entries from the given sub-path to the cached list of named entries.
*
* @param subPath The sub path
*/
private void appendEntries( final String subPath )
{
final File[] listing = new File( rootPath + subPath ).listFiles();
if ( null != listing )
{
for ( final File f : listing )
{
entryNames.add( normalizePath( f ).substring( rootPath.length() ) );
}
}
}
/**
* Returns the normalized URI path of the given file.
*
* @param file The file to normalize
* @return Normalized URI path
*/
private static String normalizePath( final File file )
{
return file.toURI().getPath();
}
}