// ===========================================================================
// CONTENT  : CLASS FileUtil
// AUTHOR   : Manfred Duchrow
// VERSION  : 1.1 - 14/03/2003
// HISTORY  :
//  17/05/2002  duma  CREATED
//	14/03/2003	duma	added		->	standardize(), javaFilename()
//
// Copyright (c) 2002-2003, by Manfred Duchrow. All rights reserved.
// ===========================================================================
package org.pf.file ;

// ===========================================================================
// IMPORTS
// ===========================================================================
import java.io.* ;

import org.pf.text.StringUtil;

/**
 * This class provides helper methods for file and stream handling.
 * It's an add-on to the java.io package.
 * The service is implemented as a singleton, so use the
 * <b>FileUtil.current()</b> method to get the sole instance.
 *
 * @author Manfred Duchrow
 * @version 1.1
 */
public class FileUtil
{
  // =========================================================================
  // CONSTANTS
  // =========================================================================
  /** The lines.separator from the system properties as a constant */
	public static final String LINE_SEPARATOR	= System.getProperty( "line.separator" ) ;
	protected static final int DEFAULT_BUFFER_SIZE	= 1024 ;

  // =========================================================================
  // CLASS VARIABLES
  // =========================================================================
	private static FileUtil current = new FileUtil() ;

  // =========================================================================
  // CLASS METHODS
  // =========================================================================

  // =========================================================================
  // CONSTRUCTORS
  // =========================================================================
  /**
   * Initialize the new instance with default values.
   */
  private FileUtil()
  {
    super() ;
  } // FileUtil()
 
  // =========================================================================
  // PUBLIC CLASS METHODS
  // =========================================================================
	public static FileUtil current()
	{
		return current ;
	} // current()
 
  // =========================================================================
  // PUBLIC INSTANCE METHODS
  // =========================================================================

	/**
	 * Copies all data from the iniput stream to the output stream using
	 * a buffer with the default size (1024 bytes).
	 * After all data is copied both streams will be closed !
	 */
	public void copyStream( InputStream inStream, OutputStream outStream )
			throws IOException
	{
		this.copyStream( inStream, outStream, DEFAULT_BUFFER_SIZE ) ;
	} // copyStream()
 
  // -------------------------------------------------------------------------

	/**
	 * Copies all data from the iniput stream to the output stream using
	 * a buffer of the given size in bytes.
	 * After all data is copied both streams will be closed !
	 */
	public void copyStream( InputStream inStream, OutputStream outStream, 
													int bufSize )
			throws IOException
	{
		byte[] buffer = new byte[bufSize] ;
		int count ;
		
		try
		{
			count = inStream.read(buffer) ;
			while ( count > -1 )
			{
				outStream.write( buffer, 0, count ) ;
				count = inStream.read(buffer) ;
			}
		}
		finally
		{
			this.close(inStream) ;
			this.close(outStream) ;
		}
	} // copyStream()
 
  // -------------------------------------------------------------------------

	/**
	 * Reads the whole content of the given input stream and returns 
	 * it as a string.
	 * The stream will be closed after calling this method. Even if an exception
	 * occured!
	 * 
	 * @param inStream The input stream to read
	 * @return The text content of the given stream
	 */
	public String readTextFrom( InputStream inStream )
		throws IOException
	{
		StringWriter writer ;
		
		writer = new StringWriter( 1024 ) ;
		this.copyText( inStream, writer ) ;
		return writer.toString() ;
	} // readTextFrom()
 
	// -------------------------------------------------------------------------

	/**
	 * Reads the whole content of the file with the given name and returns 
	 * it as a string.
	 * 
	 * @param filename The name of the text containing file
	 */
	public String readTextFrom( String filename )
		throws IOException
	{
		FileInputStream inStream ;

		inStream = new FileInputStream( filename ) ;		
		return this.readTextFrom( inStream ) ;
	} // readTextFrom()
 
	// -------------------------------------------------------------------------

	/**
	 * Reads the whole content of the specified file and returns 
	 * it as a string.
	 */
	public String readTextFrom( File file )
		throws IOException
	{
		FileInputStream inStream ;

		inStream = new FileInputStream( file ) ;		
		return this.readTextFrom( inStream ) ;
	} // readTextFrom()
 
	// -------------------------------------------------------------------------

	/**
	 * Copies all text lines from the specified reader to the given writer.
	 * After that the reader will be closed. Even if an exception occurs.
	 * 
	 * @param reader The reader which provides the text to copy
	 * @param writer The writer to which the text will be copied
	 */
	public void copyText( Reader reader, final StringWriter writer )
		throws IOException
	{
		BufferedReader bufReader ;
		String line ;
		LineProcessor processor ;
		
		bufReader = new BufferedReader( reader ) ;
		try
		{
			processor = new LineProcessor()
				{
					public boolean processLine( String line, int lineNo )
					{
						if ( lineNo > 1 )
							writer.write( LINE_SEPARATOR ) ;				
				
						writer.write( line ) ;
						return true ;
					}
				} ;
			this.processTextLines( bufReader, processor ) ;
		}
		finally
		{ 
			bufReader.close() ;
		}
	} // copyText()
 
	// ------------------------------------------------------------------------

	/**
	 * Reads all text lines from the file with the specified name and passes them 
	 * one by one to the given line processor.
	 * The processing will be terminated, if the end of the text is reached or
	 * if the processor returns <b>false</b>.<br>
	 * 
	 * @param filename The name of the text file to read
	 * @param processor The processor that receives the lines from the text
	 */
	public void processTextLines( String filename, LineProcessor processor )
		throws IOException
	{
		FileInputStream inStream ;
		
		if ( filename == null )
			throw new IllegalArgumentException( "filename must not be null" ) ;
		
		inStream = new FileInputStream( filename ) ;
		try{
			this.processTextLines( inStream, processor ) ;
		}finally{
			inStream.close();
		}
	} // processTextLines()
	
	// -------------------------------------------------------------------------
	
	/**
	 * Reads all text lines from the specified input stream and passes them 
	 * one by one to the given line processor.
	 * The processing will be terminated, if the end of the text is reached or
	 * if the processor returns <b>false</b>.<br>
	 * The given input stream will be closed after the execution of this method.
	 * Even if an exception occured.
	 * 
	 * @param inStream The input stream that contains the text
	 * @param processor The processor that receives the lines from the text
	 */
	public void processTextLines( InputStream inStream, LineProcessor processor )
		throws IOException
	{
		InputStreamReader reader ;

		if ( inStream == null )
			throw new IllegalArgumentException( "inStream must not be null" ) ;
		
		reader = new InputStreamReader( inStream ) ;
		this.processTextLines( reader, processor ) ; 
	} // processTextLines()
	
	// -------------------------------------------------------------------------
	
	/**
	 * Reads all text lines from the specified reader and passes them one by one
	 * to the given line processor.
	 * The processing will be terminated, if the end of the text is reached or
	 * if the processor returns <b>false</b>.
	 * 
	 * @param reader The reader that contains a text stream
	 * @param processor The processor that receives the lines from the text
	 */
	public void processTextLines( Reader reader, LineProcessor processor )
		throws IOException
	{
		BufferedReader bufReader ;
		String line ;
		int counter = 0 ;
		boolean continue_reading = true ;
		
		if ( reader == null )
			throw new IllegalArgumentException( "reader must not be null" ) ;

		if ( processor == null )
			throw new IllegalArgumentException( "processor must not be null" ) ;
				
		bufReader = new BufferedReader( reader ) ;
		while ( continue_reading && bufReader.ready() )
		{
			line = bufReader.readLine() ;
			if ( line == null )
				break ;

			counter++ ;
			continue_reading = processor.processLine( line, counter ) ;
		} 
	} // processTextLines()
 
	// ------------------------------------------------------------------------

	/**
	 * Close the given stream ignoring any exception.
	 * Returns true, if the stream was closed successfully, false otherwise
	 */
	public boolean close( InputStream stream )
	{
		if ( stream == null )
		{
			return false ;
		}
		try
		{
			stream.close() ;
			return true ;
		}
		catch (IOException e)
		{
			return false ;
		}
	} // close()
 
	// -------------------------------------------------------------------------

	/**
	 * Close the given stream ignoring any exception.
	 * Returns true, if the stream was closed successfully, false otherwise
	 */
	public boolean close( OutputStream stream )
	{
		if ( stream == null )
		{
			return false ;
		}
		try
		{
			stream.close() ;
			return true ;
		}
		catch (IOException e)
		{
			return false ;
		}
	} // close()
 
	// -------------------------------------------------------------------------

	/**
	 * Convert the filename to a canonical (see java.io.File.getCanonicalPath())
	 * format and replace any backslashes '\' by slashes ('/').
	 * If possible all "." and ".." elements in the path are eliminated.
	 * 
	 * @param filename The filename which has to be standardized
	 * @return An absolute filename that uses slashes to separate its elements
	 */
	public String standardize( String filename )
	{
		if ( filename == null )
			return null ;

		return this.standardizeFilename( filename ) ;
	} // standardize()
 	
	// -------------------------------------------------------------------------

	/**
	 * Returns the given filename in the platform independent way that Java 
	 * understands. That is all elements are separated by a forward slash rather
	 * than back slashes as on Windows systems.
	 * 
	 * @param filename The name to be modified
	 */
	public String javaFilename( String filename )
	{
		if ( filename == null )
			return null ;
			
		return filename.replace( '\\', '/' ) ;
	} // javaFilename()
 
	// -------------------------------------------------------------------------

  // =========================================================================
  // PROTECTED INSTANCE METHODS
  // =========================================================================
	protected void copyText( InputStream inStream, StringWriter writer )
		throws IOException
	{
		this.copyText( new InputStreamReader( inStream ), writer ) ;
	} // copyText()
 
	// ------------------------------------------------------------------------

	protected String standardizeFilename( String filename )
	{
		String[] nameElements ;
		boolean hasDriveLetter ;
		boolean startedFromRoot ;
		boolean isAbsolute ;
		int index ;
		
		filename = this.javaFilename(filename) ;
		startedFromRoot = filename.startsWith( "/" ) ;
		nameElements = this.str().parts( filename, "/" ) ;
		if ( nameElements.length > 0 )
		{
			hasDriveLetter = nameElements[0].endsWith( ":" ) ;
			if ( hasDriveLetter )
			{
				nameElements[0] = nameElements[0].toUpperCase() ;
			}
			else
			{
				if ( startedFromRoot )
				{
					nameElements = this.str().append( new String[] { "" }, nameElements );
				}
			}
			isAbsolute = hasDriveLetter || startedFromRoot ;
			for (int i = 0; i < nameElements.length; i++)
			{
				if ( ".".equals( nameElements[i] ) )
				{
					nameElements[i] = null ;
				}
				else
				{
					if ( "..".equals( nameElements[i] ) )
					{
						index = this.indexOfPreceedingNotNullElement( nameElements, i-1 ) ;
						if ( index >= 0 )
						{
							if ( ( index > 0 ) || ( ! isAbsolute ) )
							{
								nameElements[i] = null ;
								nameElements[index] = null ;
							}
						}
					}
				}	
			}
			nameElements = this.str().removeNull( nameElements ) ;
			return this.str().asString( nameElements, "/" ) ;
		}
		else
		{
			return "" ;
		}
	} // standardizeFilename()
 
	// -------------------------------------------------------------------------

	protected int indexOfPreceedingNotNullElement( String[] elements, int start )
	{
		for (int i = start; i >= 0 ; i--)
		{
			if ( elements[i] != null )
			{
				if ( "..".equals( elements[i] ) ) // This is not a valid not null element
				{
					return -1 ;
				}
				else
				{
					return i ;				
				}
			}
		}
		return -1 ;
	} // indexOfPreceedingNotNullElement()
 
	// -------------------------------------------------------------------------

	protected StringUtil str()
	{
		return StringUtil.current() ;
	} // str()
 
	// -------------------------------------------------------------------------

} // class FileUtil