/******************************************************************************* * Copyright (c) 2010-present 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(); } }