# Outputs.java

    /*
     * Copyright (c) 2010, Rickard Öberg. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     */
    package org.qi4j.io;

    import java.io.BufferedOutputStream;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Writer;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    import java.util.Collection;
    import java.util.zip.GZIPOutputStream;

    /**
     * Utility methods for creating standard Outputs
     */
    public class Outputs
    {
        // START SNIPPET: method

        /**
         * Write lines to a text file with UTF-8 encoding. Separate each line with a newline ("\n" character). If the writing or sending fails,
         * the file is deleted.
         * <p>
         * If the filename ends with .gz, then the data is automatically GZipped.
         * </p>
         * @param file the file to save the text to
         *
         * @return an Output for storing text in a file
         */
        public static Output<String, IOException> text( final File file )
        // END SNIPPET: method
        {
            return text( file, "UTF-8" );
        }

        // START SNIPPET: method

        /**
         * Write lines to a text file. Separate each line with a newline ("\n" character). If the writing or sending fails,
         * the file is deleted.
         * <p>
         * If the filename ends with .gz, then the data is automatically GZipped.
         * </p>
         * @param file the file to save the text to
         *
         * @return an Output for storing text in a file
         */
        public static Output<String, IOException> text( final File file, final String encoding )
        // END SNIPPET: method
        {
            return new Output<String, IOException>()
            {
                @Override
                @SuppressWarnings( "unchecked" )
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends String, SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    File tmpFile = Files.createTemporayFileOf( file );

                    OutputStream stream = new FileOutputStream( tmpFile );

                    // If file should be gzipped, do that automatically
                    if( file.getName().endsWith( ".gz" ) )
                    {
                        stream = new GZIPOutputStream( stream );
                    }

                    final BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( stream, encoding ) );

                    try
                    {
                        sender.sendTo( new Receiver<String, IOException>()
                        {
                            @Override
                            public void receive( String item )
                                throws IOException
                            {
                                writer.append( item ).append( '\n' );
                            }
                        } );
                        writer.close();

                        // Replace file with temporary file
                        if( !file.exists() || file.delete() )
                        {
                            if( ! tmpFile.renameTo( file ) )
                            {
                                // TODO: What?? Throw an Exception?
                                System.err.println( "Unable to rename file: " + tmpFile + " to " + file );
                            }
                        }
                    }
                    catch( IOException e )
                    {
                        // We failed writing - close and delete
                        writer.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }
                    }
                    catch( Throwable senderThrowableType )
                    {
                        // We failed writing - close and delete
                        writer.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }
                        throw (SenderThrowableType) senderThrowableType;
                    }
                }
            };
        }

        // START SNIPPET: method

        /**
         * Write lines to a Writer. Separate each line with a newline ("\n" character).
         *
         * @param writer the Writer to write the text to
         * @return an Output for storing text in a Writer
         */
        public static Output<String, IOException> text( final Writer writer )
        // END SNIPPET: method
        {
            return new Output<String, IOException>()
            {

                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends String, SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    sender.sendTo( new Receiver<String, IOException>()
                    {

                        @Override
                        public void receive( String item )
                            throws IOException
                        {
                            writer.append( item ).append( "\n" );
                        }

                    } );
                }

            };
        }

        // START SNIPPET: method

        /**
         * Write lines to a StringBuilder. Separate each line with a newline ("\n" character).
         *
         * @param builder the StringBuilder to append the text to
         * @return an Output for storing text in a StringBuilder
         */
        public static Output<String, IOException> text( final StringBuilder builder )
        // END SNIPPET: method
        {
            return new Output<String, IOException>()
            {

                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends String, SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    sender.sendTo( new Receiver<String, IOException>()
                    {

                        @Override
                        public void receive( String item )
                            throws IOException
                        {
                            builder.append( item ).append( "\n" );
                        }

                    } );
                }

            };
        }

        // START SNIPPET: method

        /**
         * Write ByteBuffer data to a file. If the writing or sending of data fails the file will be deleted.
         *
         * @param file The destination file.
         *
         * @return The Output ByteBuffer instance backed by a File.
         */
        public static Output<ByteBuffer, IOException> byteBuffer( final File file )
        // END SNIPPET: method
        {
            return new Output<ByteBuffer, IOException>()
            {
                @Override
                @SuppressWarnings( "unchecked" )
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends ByteBuffer, SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    File tmpFile = Files.createTemporayFileOf( file );
                    FileOutputStream stream = new FileOutputStream( tmpFile );
                    final FileChannel fco = stream.getChannel();

                    try
                    {
                        sender.sendTo( new Receiver<ByteBuffer, IOException>()
                        {
                            @Override
                            public void receive( ByteBuffer item )
                                throws IOException
                            {
                                fco.write( item );
                            }
                        } );
                        stream.close();

                        // Replace file with temporary file
                        if( !file.exists() || file.delete() )
                        {
                            if( ! tmpFile.renameTo( file ) )
                            {
                                // TODO: What can be done in this case?
                                System.err.println( "Unable to rename file: " + tmpFile + " to " + file );
                            }
                        }
                    }
                    catch( IOException e )
                    {
                        // We failed writing - close and delete
                        stream.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }

                    }
                    catch( Throwable senderThrowableType )
                    {
                        // We failed writing - close and delete
                        stream.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }
                        throw (SenderThrowableType) senderThrowableType;
                    }
                }
            };
        }

        // START SNIPPET: method

        /**
         * Write ByteBuffer data to an OutputStream.
         *
         * @param stream Destination OutputStream
         *
         * @return The Output of ByteBuffer that will be backed by the OutputStream.
         */
        public static Output<ByteBuffer, IOException> byteBuffer( final OutputStream stream )
        // END SNIPPET: method
        {
            return new Output<ByteBuffer, IOException>()
            {
                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends ByteBuffer, SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    try
                    {
                        sender.sendTo( new Receiver<ByteBuffer, IOException>()
                        {
                            @Override
                            public void receive( ByteBuffer item )
                                throws IOException
                            {
                                if( item.hasArray() )
                                {
                                    stream.write( item.array(), item.arrayOffset(), item.limit() );
                                }
                                else
                                {
                                    for( int i = 0; i < item.limit(); i++ )
                                    {
                                        stream.write( item.get( i ) );
                                    }
                                }
                            }
                        } );
                    }
                    finally
                    {
                        stream.close();
                    }
                }
            };
        }

        // START SNIPPET: method

        /**
         * Write byte array data to a file. If the writing or sending of data fails the file will be deleted.
         *
         * @param file The File to be written to.
         * @param bufferSize The size of the ByteBuffer.
         *
         * @return An Output instance that will write to the given File.
         */
        public static Output<byte[], IOException> bytes( final File file, final int bufferSize )
        // END SNIPPET: method
        {
            return new Output<byte[], IOException>()
            {
                @Override
                @SuppressWarnings( "unchecked" )
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends byte[], SenderThrowableType> sender )
                    throws IOException, SenderThrowableType
                {
                    File tmpFile = Files.createTemporayFileOf( file );
                    final OutputStream stream = new BufferedOutputStream( new FileOutputStream( tmpFile ), bufferSize );

                    try
                    {
                        sender.sendTo( new Receiver<byte[], IOException>()
                        {
                            @Override
                            public void receive( byte[] item )
                                throws IOException
                            {
                                stream.write( item );
                            }
                        } );
                        stream.close();

                        // Replace file with temporary file
                        if( !file.exists() || file.delete() )
                        {
                            if( ! tmpFile.renameTo( file ) )
                            {
                                // TODO: WHAT???
                                System.err.println( "Unable to rename " + tmpFile + " to " + file );
                            }
                        }
                    }
                    catch( IOException e )
                    {
                        // We failed writing - close and delete
                        stream.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }
                    }
                    catch( Throwable senderThrowableType )
                    {
                        // We failed writing - close and delete
                        stream.close();
                        if( ! tmpFile.delete() )
                        {
                            System.err.println("Unable to delete temporary file." );
                            tmpFile.deleteOnExit();
                        }
                        throw (SenderThrowableType) senderThrowableType;
                    }
                }
            };
        }

        // START SNIPPET: method

        /**
         * Do nothing. Use this if you have all logic in filters and/or specifications
         *
         * @param <T> The item type.
         *
         * @return An Output instance that ignores all data.
         */
        public static <T> Output<T, RuntimeException> noop()
        // END SNIPPET: method
        {
            return withReceiver( new Receiver<T, RuntimeException>()
            {
                @Override
                public void receive( T item )
                    throws RuntimeException
                {
                    // Do nothing
                }
            } );
        }

        // START SNIPPET: method

        /**
         * Use given receiver as Output. Use this if there is no need to create a "transaction" for each transfer, and no need
         * to do batch writes or similar.
         *
         * @param <T> The item type
         * @param receiver receiver for this Output
         *
         * @return An Output instance backed by a Receiver of items.
         */
        public static <T, ReceiverThrowableType extends Throwable> Output<T, ReceiverThrowableType> withReceiver( final Receiver<T, ReceiverThrowableType> receiver )
        // END SNIPPET: method
        {
            return new Output<T, ReceiverThrowableType>()
            {
                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends T, SenderThrowableType> sender )
                    throws ReceiverThrowableType, SenderThrowableType
                {
                    sender.sendTo( receiver );
                }
            };
        }

        // START SNIPPET: method

        /**
         * Write objects to System.out.println.
         *
         * @return An Output instance that is backed by System.out
         */
        public static Output<Object, RuntimeException> systemOut()
        // END SNIPPET: method
        {
            return new Output<Object, RuntimeException>()
            {
                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<?, SenderThrowableType> sender )
                    throws RuntimeException, SenderThrowableType
                {
                    sender.sendTo( new Receiver<Object, RuntimeException>()
                    {
                        @Override
                        public void receive( Object item )
                        {
                            System.out.println( item );
                        }
                    } );
                }
            };
        }

        // START SNIPPET: method

        /**
         * Write objects to System.err.println.
         *
         * @return An Output instance backed by System.in
         */
        @SuppressWarnings( "UnusedDeclaration" )
        public static Output<Object, RuntimeException> systemErr()
        // END SNIPPET: method
        {
            return new Output<Object, RuntimeException>()
            {
                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<?, SenderThrowableType> sender )
                    throws RuntimeException, SenderThrowableType
                {
                    sender.sendTo( new Receiver<Object, RuntimeException>()
                    {
                        @Override
                        public void receive( Object item )
                        {
                            System.err.println( item );
                        }
                    } );
                }
            };
        }

        // START SNIPPET: method

        /**
         * Add items to a collection
         */
        public static <T> Output<T, RuntimeException> collection( final Collection<T> collection )
        // END SNIPPET: method
        {
            return new Output<T, RuntimeException>()
            {
                @Override
                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends T, SenderThrowableType> sender )
                    throws RuntimeException, SenderThrowableType
                {
                    sender.sendTo( new Receiver<T, RuntimeException>()
                    {
                        @Override
                        public void receive( T item )
                            throws RuntimeException
                        {
                            collection.add( item );
                        }
                    } );
                }
            };
        }

        private Outputs()
        {
        }
    }