Browse Source

#37 javadoc: io

Ranides Atterwim 4 years ago
parent
commit
927b5ed9f7

+ 36 - 9
assira.core/src/main/java/net/ranides/assira/io/ChannelAdapter.java

@@ -6,6 +6,8 @@
  */
 package net.ranides.assira.io;
 
+import lombok.experimental.UtilityClass;
+
 import java.io.EOFException;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -15,32 +17,57 @@ import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SeekableByteChannel;
 
 /**
+ * I/O conversions allowing you to use byte array as data storage in situations when
+ * required type is ByteChannel
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class ChannelAdapter {
-    
-    private ChannelAdapter() {
-        // utility class
-    }
-    
+@UtilityClass
+public class ChannelAdapter {
+
+    /**
+     * Creates ByteChannel accessing data in array.
+     * Equivalent, but a bit more efficient than
+     * {@code BufferAdapter.asChannel(ByteBuffer.wrap(input))}
+     *
+     * @param input input
+     * @return channel
+     */
     public static SeekableByteChannel wrap(byte[] input) {
         // preferred to BufferAdapter because we can write transfers to/from array
         // in a bit more efficient way.
         return new AChannel(input);
     }
 
+    /**
+     * Creates ByteChannel accessing data in ByteBuffer
+     * Alias for {@link BufferAdapter#asChannel(ByteBuffer)}
+     *
+     * @param input input
+     * @return channel
+     */
     public static SeekableByteChannel wrap(ByteBuffer input) {
         return BufferAdapter.asChannel(input);
     }
-    
+
+    /**
+     * Creates ByteChannel accessing data in array.
+     * It does not make charset conversion: it just convert each two bytes into one "char"
+     *
+     * @param input input
+     * @return channel
+     */
     public static SeekableByteChannel wrap(char[] input) {
         // preferred to CSChannel adapter because we want to support writes
         return new CAChannel(input);
     }
-    
+
     /**
-     * read only
+     * Creates ByteChannel accessing data in sequence.
+     * It does not make charset conversion: it just convert each two bytes into one "char"
+     *
+     * It is read-only channel, becase CharSequence does not support modification.
+     *
      * @param input input
      * @return channel
      */

+ 51 - 27
assira.core/src/main/java/net/ranides/assira/io/DataAdapter.java

@@ -14,46 +14,70 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.Writer;
+
+import lombok.experimental.UtilityClass;
 import net.ranides.assira.generic.ValueUtils;
 
 /**
- * Klasa wrapująca {@link DataOutput} lub {@link DataInput} dodając stream functionality.
- * Czyli na odwrót niż to jest w gotowych klasach {@link java.io.DataOutputStream}
- * lub {@link java.io.DataInputStream}. W tamtych klasach to do istniejącego streamu
- * jest dodawana funcjonalność.
- * <p>
- * Podejście {@code java.io} byłoby sensowne: jak coś potrafi pracować z bajtami, to rozszerzmy
- na pracę z prymitywami. Podejście DataAdapter-a: jak coś potrafi pracować ze
- wszystkim, to "pseudo-rozszerzmy" żeby pracowało z bajtami... wydaje się dziwne.
- I byłoby dziwne, gdyby Input/OutputStream były interfejsami. Niestety to są klasy 
- abstrakcyjne, i czasem ktoś tworząc obiekt stream-like może nie mieć  możliwości
- "zaimplementowania" Input/OutputStream. I dostajemy wtedy taką patologię:
- obiekt stream-like czyta "wszystko", ale nie ma w interfejsie powiedziane, że
- z bajtami sobie radzi ;)
- </p><p>
- * Na przykład {@link java.io.RandomAccessFile} jest takim typem, który reprezentując
- * strumień, nie może go zaimplementować, bo jest jednocześnie input/output, a java
- * wielodziedziczenia nie obsługuje.
+ * These methods wrap {@link DataOutput} lub {@link DataInput} adding Stream functionality.
+ * It is reversed operation to {@link java.io.DataOutputStream} or {@link java.io.DataInputStream}.
+ * JDK classes allows you to read/write all primitive data types using byte operations.
+ * Those wrappers allows you to read/write bytes using stream methods.
+ *
+ * We overcome disputable decision, that OutputStream and InputStream are classes, not interfaces.
+ *
+ * If you want to implement I/O stream, you have to subclass stream class.
+ * Sometimes it is not possible, because Java does not allow multiple inheritance.
+ * In effect, if our class implements DataInput, we have strange situation, where class is able
+ * to read any primitive type (including bytes) but cannot be used as stream.
+ *
+ * This is especially obvious if you look at {@link java.io.RandomAccessFile}.
+ * This class should be usable as both input and output stream, but it isn't.
+ * If you wrap it using methods below, you can do that.
+ *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class DataAdapter {
-    
-    private DataAdapter() {
-        // utility class
-    }
+@UtilityClass
+public class DataAdapter {
 
+    /**
+     * Creates new OutputStream which delegates I/O to provided DataOutput.
+     *
+     * @param output output
+     * @return stream
+     */
     public static OutputStream asOutput(DataOutput output) {
         return new DOStream(output);
     }
-    
-    public static InputStream asInput(DataInput output) {
-        return new DIStream(output);
+
+    /**
+     * Creates new InputStream which delegates I/O to provided DataInput.
+     *
+     * @param input input
+     * @return stream
+     */
+    public static InputStream asInput(DataInput input) {
+        return new DIStream(input);
     }
-    
+
+    /**
+     * Creates new Writer which delegates I/O to provided DataOutput.
+     * We don't use any charset conversion, but use {@link DataOutput#writeChar(int)}
+     *
+     * @param output output
+     * @return writer
+     */
     public static Writer asWriter(DataOutput output) {
         return new DOWriter(output);
     }
-    
+
+    /**
+     * Creates new Reader which delegates I/O to provided DataInput.
+     * We don't use any charset conversion, but use {@link DataInput#readChar()}
+     *
+     * @param input input
+     * @return reader
+     */
     public static Reader asReader(DataInput input) {
         return new DIReader(input);
     }

+ 78 - 12
assira.core/src/main/java/net/ranides/assira/io/DataBuffer.java

@@ -23,29 +23,58 @@ import net.ranides.assira.collection.arrays.NativeArrayAllocator;
 import net.ranides.assira.text.Charsets;
 
 /**
+ * This class stores binary data in-memory and allows you to use it as both DataInput and DataOutput.
+ * Additionally, it implements "seek" operation, which can be used to move stream position.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public class DataBuffer implements DataInput, DataOutput {
-    
-    NativeArray array;
-    ByteBuffer buffer;
 
+    private static final int CAPACITY = 16;
+
+    private NativeArray array;
+    private ByteBuffer buffer;
+
+    /**
+     * Create new buffer with default capacity (size is equal to zero)
+     */
     public DataBuffer() {
-        byte[] data = new byte[16];
+        byte[] data = new byte[CAPACITY];
         this.array = NativeArray.wrap(data);
         this.buffer = ByteBuffer.wrap(data);
     }
-    
+
+    /**
+     * Returns new ByteBuffer backed by data written to this buffer.
+     * Size of returned buffer is limited by current position.
+     *
+     * @return buffer
+     */
     public ByteBuffer data() {
         return ByteBuffer.wrap(array.$array(), 0, buffer.position());
     }
-    
+
+    /**
+     * Returns new array with content copied from stream
+     * Size of returned array is limited by current position.
+     *
+     * @return array
+     */
     public byte[] bytes() {
         int n = buffer.position();
         return ArrayUtils.copy(array.$array(), 0, new byte[n], 0, n);
     }
-    
+
+    /**
+     * Changes read/write position inside current stream.
+     * Allowed values are limit by current capacity.
+     * It is possible to "seek" beyond written data (if capacity is big enough),
+     * buffer will contain zeros in unwritten fragment.
+     *
+     * @param position position
+     * @return this
+     * @throws IOException on error
+     */
     public DataBuffer seek(int position) throws IOException {
         try {
             if(position != buffer.position(position).position()) {
@@ -56,7 +85,12 @@ public class DataBuffer implements DataInput, DataOutput {
         }
         return this;
     }
-    
+
+    /**
+     * Returns current read/write position in buffer
+     *
+     * @return int
+     */
     public int seek() {
         return buffer.position();
     }
@@ -138,7 +172,15 @@ public class DataBuffer implements DataInput, DataOutput {
         checkEOF(8);
         return buffer.getDouble();
     }
-    
+
+    /**
+     * Read bytes from stream, using provided charset, until new line character is reached.
+     * It supports 3 most commont ENDL formats: \n (nix), \r (mac), \r\n (windows)
+     *
+     * @param cs cs
+     * @return line
+     * @throws IOException on error
+     */
     public String readLine(Charset cs) throws IOException {
         if(buffer.remaining() <=0) {
             return null;
@@ -206,14 +248,32 @@ public class DataBuffer implements DataInput, DataOutput {
         readFully(bytes);
         return new String(bytes, Charsets.UTF8);
     }
-    
+
+    /**
+     * Read characters from stream and stores them inside "data" array.
+     * Number of characters to read is equal to array length.
+     * If there is no enough data in stream, it won't read anything, but throw exception.
+     *
+     * It does not use any charset conversions.
+     *
+     * @param data data
+     * @throws IOException on error
+     */
     public void readChars(char[] data) throws IOException {
         checkEOF(2 * data.length);
         for(int i=0; i<data.length; i++) {
             data[i] = buffer.getChar();
         }
     }
-    
+
+    /**
+     * Reads data from stream and stores them inside "target" buffer.
+     * Number of bytes to read is equal to "target.remaining".
+     * If there is no enough data in stream, it won't read anything, but throw exception.
+     *
+     * @param target target
+     * @throws IOException on error
+     */
     public void readBuffer(ByteBuffer target) throws IOException {
         int n = target.remaining();
         checkEOF(n);
@@ -318,7 +378,13 @@ public class DataBuffer implements DataInput, DataOutput {
         buffer.putShort((short)size);
         buffer.put(data);
     }
-    
+
+    /**
+     * Writes all bytes remaining inside "data" buffer.
+     * Number of bytes to write is equal to "data.remaining"
+     *
+     * @param data data
+     */
     public void writeBuffer(ByteBuffer data) {
         reserve(data.remaining());
         buffer.put(data);

+ 46 - 9
assira.core/src/main/java/net/ranides/assira/io/FileUtils.java

@@ -6,26 +6,38 @@
  */
 package net.ranides.assira.io;
 
-import net.ranides.assira.io.IOStreams;
+import lombok.experimental.UtilityClass;
 
 import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
 /**
+ * Utility methods for basic file operations.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class FileUtils {
-    
-    private FileUtils() {
-        /* utility class */
-    }
+@UtilityClass
+public class FileUtils {
 
+    /**
+     * Deletes specified file.
+     * It works for every path, including non-empty directories.
+     *
+     * @param file file
+     * @throws IOException on error
+     */
     public static void delete(Path file) throws IOException {
         delete(file.toFile());
     }
-    
+
+    /**
+     * Deletes specified file.
+     * It works for every path, including non-empty directories.
+     *
+     * @param file file
+     * @throws IOException on error
+     */
     public static void delete(File file) throws IOException {
         if(!file.exists()) { 
 			return; 
@@ -40,11 +52,27 @@ public final class FileUtils {
 		}
 		Files.delete(file.toPath());
     }
-    
+
+    /**
+     * Reads content of provided file
+     *
+     * @param file file
+     * @return array
+     * @throws IOException on error
+     */
     public static byte[] content(Path file) throws IOException {
         return Files.readAllBytes(file);
     }
 
+    /**
+     * Copy "input" file into "output".
+     * It creates all intermediate directories, if necessary.
+     * It does not support copying directories.
+     *
+     * @param input input
+     * @param output output
+     * @throws IOException on error
+     */
     public static void copy(File input, File output) throws IOException {
         File parent = output.getParentFile();
         if(!parent.exists() && !parent.mkdirs()) {
@@ -53,6 +81,13 @@ public final class FileUtils {
         IOStreams.copy(new FileInputStream(input), new FileOutputStream(output));
     }
 
+    /**
+     * Reads content of provided file
+     *
+     * @param file file
+     * @return array
+     * @throws IOException on error
+     */
     public static byte[] content(File file) throws IOException {
         try (RandomAccessFile istream = new RandomAccessFile(file, "r")) {
             byte[] content = new byte[ (int)istream.length() ];
@@ -63,9 +98,11 @@ public final class FileUtils {
     
     /**
      * Compare content of files.
+     *
      * @param file1 file1
      * @param file2 file2
      * @return bool
+     * @throws IOException on error
      */
     public static boolean equivalent(File file1, File file2) throws IOException {
         if(file1.equals(file2)) {
@@ -81,7 +118,7 @@ public final class FileUtils {
             InputStream istream1=new FileInputStream(file1);
             InputStream istream2=new FileInputStream(file2)
         ) {
-            return IOStreams.equivalent(istream1, istream2);
+            return IOStreams.equals(istream1, istream2);
         }
     }
     

+ 192 - 27
assira.core/src/main/java/net/ranides/assira/io/IOEvent.java

@@ -16,19 +16,39 @@ import net.ranides.assira.events.Event;
 import net.ranides.assira.events.Events;
 
 /**
+ * I/O events, signalled by other assira classes.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public interface IOEvent extends Event {
-    
+
+    /**
+     * Event: stream was flushed.
+     *
+     * @param source source
+     * @return event
+     */
     static Flush flush(OutputStream source) {
         return new Flush(source);
     }
-    
+
+    /**
+     * Event: stream was resetted.
+     *
+     * @param source source
+     * @return event
+     */
     static Reset reset(InputStream source) {
         return new Reset(source);
     }
-    
+
+    /**
+     * Event: one byte was read from stream.
+     *
+     * @param source source
+     * @param data byte
+     * @return event
+     */
     static Read read(InputStream source, int data) {
         if(data == -1) {
             return new Read(source);
@@ -36,43 +56,113 @@ public interface IOEvent extends Event {
             return new Read(source, new byte[]{(byte)data}, 0, 1);
         }
     }
-    
+
+    /**
+     * Event: bytes were read from stream.
+     * Please note: only bytes limited by offset and length are meaningfull.
+     *
+     * @param source source
+     * @param data data
+     * @param offset offset
+     * @param length length
+     * @return event
+     */
     static Read read(InputStream source, byte[] data, int offset, int length) {
         return new Read(source, data, offset, length);
     }
-    
+
+    /**
+     * Event: bytes were read from stream.
+     *
+     * @param source source
+     * @param data data
+     * @return event
+     */
     static Read read(InputStream source, byte[] data) {
         return new Read(source, data, 0, data.length);
     }
-    
+
+    /**
+     * Event: one byte was written to stream.
+     *
+     * @param source source
+     * @param data data
+     * @return event
+     */
     static Write write(OutputStream source, int data) {
         return new Write(source, new byte[]{(byte)data}, 0, 1);
     }
-    
+
+    /**
+     * Event: bytes were written to stream.
+     * Please note: only bytes limited by offset and length are meaningfull.
+     *
+     * @param source source
+     * @param data data
+     * @param offset offset
+     * @param length length
+     * @return event
+     */
     static Write write(OutputStream source, byte[] data, int offset, int length) {
         return new Write(source, data, offset, length);
     }
-    
+
+    /**
+     * Event: bytes were written to stream.
+     *
+     * @param source source
+     * @param data data
+     * @return event
+     */
     static Write write(OutputStream source, byte[] data) {
         return new Write(source, data, 0, data.length);
     }
-    
+
+    /**
+     * Event: stream was closed
+     *
+     * @param source source
+     * @return event
+     */
     static Close close(Closeable source) {
         return new Close(source);
     }
-    
+
+    /**
+     * Event: some bytes were skipped.
+     *
+     * @param source source
+     * @param count count
+     * @return event
+     */
     static Skip skip(InputStream source, long count) {
         return new Skip(source, count);
     }
-    
-    static Move move(InputStream source, long position) {
-        return new Move(source, position);
+
+    /**
+     * Event: position of stream was changed.
+     *
+     * @param source source
+     * @param position position
+     * @return event
+     */
+    static Seek seek(InputStream source, long position) {
+        return new Seek(source, position);
     }
-    
+
+    /**
+     * Event: there was I/O exception
+     *
+     * @param cause cause
+     * @return event
+     */
     static Failure failure(IOException cause) {
         return new Failure(cause);
     }
-    
+
+    /**
+     * Event: reset
+     */
     class Reset implements IOEvent {
         
         private final InputStream source;
@@ -81,11 +171,19 @@ public interface IOEvent extends Event {
             this.source = source;
         }
 
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public InputStream source() {
             return source;
         }
     }
-    
+
+    /**
+     * Event: flush
+     */
     class Flush implements IOEvent {
         
         private final OutputStream source;
@@ -94,11 +192,19 @@ public interface IOEvent extends Event {
             this.source = source;
         }
 
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public OutputStream source() {
             return source;
         }
     }
-    
+
+    /**
+     * Event: read bytes
+     */
     class Read implements IOEvent {
         
         private static final ByteBuffer NONE = ByteBuffer.wrap(new byte[0]);
@@ -115,17 +221,30 @@ public interface IOEvent extends Event {
             this.source = source;
             this.data = ByteBuffer.wrap(data, offset, length);
         }
-        
+
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public InputStream source() {
             return source;
         }
-        
+
+        /**
+         * Bytes read from stream
+         *
+         * @return buffer
+         */
         public ByteBuffer data() {
             return data;
         }
         
     }
-    
+
+    /**
+     * Event: write
+     */
     class Write implements IOEvent {
         
         private final OutputStream source;
@@ -135,17 +254,29 @@ public interface IOEvent extends Event {
             this.source = source;
             this.data = ByteBuffer.wrap(data, offset, length);
         }
-        
+
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public OutputStream source() {
             return source;
         }
-        
+
+        /**
+         * Bytes written to stream
+         * @return
+         */
         public ByteBuffer data() {
             return data;
         }
         
     }
 
+    /**
+     * Event: close
+     */
     class Close implements IOEvent {
         
         private final Closeable source;
@@ -154,12 +285,20 @@ public interface IOEvent extends Event {
             this.source = source;
         }
 
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public Closeable source() {
             return source;
         }
         
     }
-    
+
+    /**
+     * Event: skip
+     */
     class Skip implements IOEvent {
         
         private final InputStream source;
@@ -170,36 +309,62 @@ public interface IOEvent extends Event {
             this.count = count;
         }
 
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public InputStream source() {
             return source;
         }
 
+        /**
+         * Number of skipped bytes
+         *
+         * @return long
+         */
         public long count() {
             return count;
         }
         
     }
-    
-    class Move implements IOEvent {
+
+    /**
+     * Event: seek
+     */
+    class Seek implements IOEvent {
         
         private final InputStream source;
         private final long position;
 
-        protected Move(InputStream source, long position) {
+        protected Seek(InputStream source, long position) {
             this.source = source;
             this.position = position;
         }
 
+        /**
+         * stream
+         *
+         * @return stream
+         */
         public InputStream source() {
             return source;
         }
 
+        /**
+         * New position in stream
+         *
+         * @return long
+         */
         public long position() {
             return position;
         }
         
     }
-    
+
+    /**
+     * Event: I/O exception
+     */
     class Failure extends Events.Failure implements IOEvent {
 		
         protected Failure(Throwable cause) {

+ 55 - 2
assira.core/src/main/java/net/ranides/assira/io/IOHandle.java

@@ -12,25 +12,78 @@ import net.ranides.assira.functional.checked.CheckedConsumer;
 import net.ranides.assira.functional.checked.CheckedSupplier;
 
 /**
+ * This class represents handle which can be use to retrieve Closeable resource.
  *
+ *
+ * Handle allows you to postpone creation of resource to the moment of first use.
+ *
+ * If you close handle, resource will be closed too and become invalid.
+ *
+ * @param <T> resource
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public interface IOHandle<T> extends Closeable, CheckedSupplier<T, IOException> {
 
+    /**
+     * Creates new thread-safe shared resource.
+     * Resource is created when you call {@link #get()} first time.
+     *
+     * It uses "close" operation to release resource.
+     * It uses "open" operation to create resource.
+     *
+     * Resource is created only once, next calls to "get" will return the same instance.
+     *
+     * @param close close
+     * @param open open
+     * @param <T> T
+     * @return handle
+     */
     static <T> IOHandle<T> shared(CheckedConsumer<T,IOException> close, CheckedSupplier<T,IOException> open) {
         return new IOUtils.SharedHandle<>(close, open);
     }
 
+    /**
+     * Creates new thread-safe shared resource.
+     * Resource is created when you call {@link #get()} first time.
+     *
+     * If resource if "Closable" it uses "close" operation to release resource.
+     * It uses "open" operation to create resource.
+     *
+     * Resource is created only once, next calls to "get" will return the same instance.
+     *
+     * @param open open open
+     * @param <T> T
+     * @return handle
+     */
     static <T> IOHandle<T> shared(CheckedSupplier<T,IOException> open) {
         return new IOUtils.SharedHandle<>(open);
     }
-    
+
+    /**
+     * Closes handle and created resource
+     *
+     * @throws IOException on error
+     */
     @Override
     void close() throws IOException;
 
+    /**
+     * Returns created resource managed by this handle.
+     * It creates resource on first call, later calls will create the same instance.
+     *
+     * Shared handle implements this method to be thread-safe: it synchronizes access to resource.
+     *
+     * @return resource
+     * @throws IOException
+     */
     @Override
     T get() throws IOException;
-    
+
+    /**
+     * Returns true if handle contains already created resource.
+     *
+     * @return boolean
+     */
     boolean opened();
     
     

+ 224 - 25
assira.core/src/main/java/net/ranides/assira/io/IOStreams.java

@@ -17,6 +17,8 @@ import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CoderResult;
 import java.util.Arrays;
 import java.util.function.Consumer;
+
+import lombok.experimental.UtilityClass;
 import net.ranides.assira.events.EventListener;
 import net.ranides.assira.functional.checked.CheckedRunnable;
 import net.ranides.assira.generic.CompareUtils;
@@ -24,13 +26,21 @@ import net.ranides.assira.trace.ExceptionUtils;
 import net.ranides.assira.trace.LoggerUtils;
 
 /**
+ * Utility methods for I/O streams
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class IOStreams {
-    
+@UtilityClass
+public class IOStreams {
+
+    /**
+     * This output stream will silently discard every written data.
+     */
     public static final OutputStream NULL = new NULLStream();
-    
+
+    /**
+     * This input stream does not contains any data and signals EOF.
+     */
     public static final InputStream EMPTY = new EmptyStream();
     
     private static final int COPY_BUFFER = 4096;
@@ -40,25 +50,62 @@ public final class IOStreams {
     private static final int WRITER_BUFFER = 512;
     
     private static final org.slf4j.Logger LOGGER = LoggerUtils.getLogger();
-    
-    private IOStreams() {
-        /* utility class */
-    }
 
+    /**
+     * Reads all bytes from stream and returns them in array.
+     * Blocks until EOF is reached.
+     *
+     * @param input input
+     * @return data
+     * @throws IOException on error
+     */
     public static byte[] read(InputStream input) throws IOException {
         ByteArrayOutputStream bos = new ByteArrayOutputStream(input.available());
         copy(input, bos, null);
         return bos.toByteArray();
     }
-    
+
+    /**
+     * Copies all bytes from input into output stream.
+     * Blocks until EOF is reached.
+     *
+     * @param input input
+     * @param output output
+     * @return number of copied bytes
+     * @throws IOException on error
+     */
     public static long copy(InputStream input, OutputStream output) throws IOException {
         return copy(input, output, null);
     }
 
+    /**
+     * Copies at most "size" bytes form input to output stream.
+     * It could copy less than "size" if EOF is reached.
+     * Blocks until "size" bytes is copied or EOF is reached.
+     *
+     * @param input input
+     * @param output output
+     * @param size maximum number of bytes to copy
+     * @return number of copied bytes
+     * @throws IOException on error
+     */
     public static long copy(InputStream input, OutputStream output, long size) throws IOException {
         return copy(input, output, size, null);
     }
 
+    /**
+     * Copies all bytes from input into output stream.
+     * Blocks until EOF is reached.
+     *
+     * Calls "callback" about every successfully copied chunk of data.
+     * Usefull if we want to be informed about progress.
+     *
+     * @param input input
+     * @param output output
+     * @param callback callback
+     * @return number of copied bytes
+     * @throws IOException on error
+     */
     public static long copy(InputStream input, OutputStream output, Consumer<Long> callback) throws IOException {
         byte[] buffer = new byte[COPY_BUFFER];
         int n;
@@ -73,6 +120,21 @@ public final class IOStreams {
         return s;
     }
 
+    /**
+     * Copies at most "size" bytes form input to output stream.
+     * It could copy less than "size" if EOF is reached.
+     * Blocks until "size" bytes is copied or EOF is reached.
+     *
+     * Calls "callback" about every successfully copied chunk of data.
+     * Usefull if we want to be informed about progress.
+     *
+     * @param input input
+     * @param output output
+     * @param size size
+     * @param callback callback
+     * @return number of copied bytes
+     * @throws IOException on error
+     */
     public static long copy(InputStream input, OutputStream output, final long size, Consumer<Long> callback) throws IOException {
         byte[] buffer = new byte[COPY_BUFFER];
 
@@ -97,12 +159,23 @@ public final class IOStreams {
             }
         }
     }
-    
-    public static boolean equivalent(InputStream stream1, InputStream stream2) throws IOException {
-        return equivalent(stream1, stream2, COPY_BUFFER);
+
+    /**
+     * Compares content of two streams and returns "tue" if they are equal.
+     *
+     * Please note, that content of both streams will be consumed.
+     * It stops reading before EOF if difference is found.
+     *
+     * @param stream1 stream1
+     * @param stream2 stream2
+     * @return boolean
+     * @throws IOException on error
+     */
+    public static boolean equals(InputStream stream1, InputStream stream2) throws IOException {
+        return equals(stream1, stream2, COPY_BUFFER);
     }
     
-    static boolean equivalent(InputStream stream1, InputStream stream2, int bufsize) throws IOException {
+    private static boolean equals(InputStream stream1, InputStream stream2, int bufsize) throws IOException {
         byte[] bleft  = new byte[bufsize];
         byte[] bright = new byte[bufsize];
         while (true) {
@@ -120,18 +193,48 @@ public final class IOStreams {
         }
     }
 
+    /**
+     * Creates new stream which delegates all I/O operations to "is" and
+     * sends event to listener after every I/O call.
+     *
+     * @param is is
+     * @param listener listener
+     * @return stream
+     */
     public static InputStream observe(InputStream is, EventListener<? super IOEvent> listener) {
         return new InputObserver(is, listener);
     }
-    
+
+    /**
+     * Creates new stream which delegates all I/O operations to "os" and
+     * sends event to listener after every I/O call.
+     *
+     * @param os os
+     * @param listener listener
+     * @return stream
+     */
     public static OutputStream observe(OutputStream os, EventListener<? super IOEvent> listener) {
         return new OutputObserver(os, listener);
     }
-    
+
+    /**
+     * Creates multiplexed stream which writes data to all provided streams.
+     *
+     * @param streams streams
+     * @return stream
+     */
     public static OutputStream join(OutputStream... streams) {
         return new OutputJoiner(streams);
     }
-    
+
+    /**
+     * Binds provided stream to stdout.
+     * Every call to "System.out" will send data to stdout and provided stream.
+     *
+     * @param ostream ostream
+     * @param <S> S
+     * @return binded stream
+     */
     public static <S extends OutputStream> S tee(S ostream) {
         try {
             System.setOut(new PrintStream(join(System.out, ostream), false, Charset.defaultCharset().name()));
@@ -140,15 +243,37 @@ public final class IOStreams {
             throw ExceptionUtils.rethrow(cause);
         }
     }
-    
+
+    /**
+     * Creates new stream binded to stdout.
+     * Every call to "System.out" will send data to stdout and created stream.
+     *
+     * @return binded stream
+     */
     public static StringOutput tee() {
         return tee(new StringOutput());
     }
-    
+
+    /**
+     * Creates new stream which will write all binary data into provided writer,
+     * using provided charset to encode characters on the fly.
+     *
+     * @param writer writer
+     * @param cs cs
+     * @return stream
+     */
     public static OutputStream stream(Writer writer, Charset cs) {
         return new WriterStream(writer, cs);
     }
-    
+
+    /**
+     * Creates new stream which will read all characters from provided reader,
+     * and convert them on the fly into bytes using provided charset.
+     *
+     * @param reader reader
+     * @param cs cs
+     * @return stream
+     */
     public static InputStream stream(Reader reader, Charset cs) {
         CharsetEncoder ce = cs.newEncoder();
         float bpc = ce.maxBytesPerChar();
@@ -167,11 +292,36 @@ public final class IOStreams {
                 return new ReaderStream(reader, ce);
         }
     }
-    
+
+    /**
+     * Creates new background thread which will copy all data from "input" to "output".
+     * You should always terminate created "pipe" threads.
+     * Created thread closes correctly both streams when terminated.
+     *
+     * Please note that returned thread is already started.
+     *
+     * @param input input
+     * @param output output
+     * @return thread
+     */
     public static Thread pipe(InputStream input, OutputStream output) {
         return pipe(input, output, (Exception e) -> LOGGER.error("Exception ignored in #pipe", e));
     }
-    
+
+    /**
+     * Creates new background thread which will copy all data from "input" to "output".
+     * You should always terminate created "pipe" threads.
+     * Created thread closes correctly both streams when terminated.
+     *
+     * It sends information about exceptions to "handler".
+     *
+     * Please note that returned thread is already started.
+     *
+     * @param input input
+     * @param output output
+     * @param handler handler
+     * @return thread
+     */
     public static Thread pipe(InputStream input, OutputStream output, Consumer<Exception> handler) {
         Thread thread = new Thread(() -> {
             try {
@@ -186,14 +336,32 @@ public final class IOStreams {
         thread.start();
         return thread;
     }
-    
+
+    /**
+     * Creates new background thread which will copy all data from "input" to "output".
+     * You should always terminate created "pipe" threads.
+     * Created thread closes correctly both streams when terminated.
+     *
+     * Please note that returned thread is already started.
+     *
+     * It sends information about operations to "listener".
+     * It sends "seek" events when copy.
+     * It sends "flush" event after copy.
+     * It sends "failure" event on error.
+     * It sends "close" event before termination.
+     *
+     * @param input input
+     * @param output output
+     * @param listener listener
+     * @return thread
+     */
     public static Thread pipe(InputStream input, OutputStream output, EventListener<? super IOEvent> listener) {
         if(null == listener) {
             return pipe(input, output);
         }
         Thread thread = new Thread(() -> {
             try {
-                copy(input, output, p -> listener.handleEvent(IOEvent.move(input, p)));
+                copy(input, output, p -> listener.handleEvent(IOEvent.seek(input, p)));
                 listener.handleEvent(IOEvent.flush(output));
             } catch(IOException cause) {
                 listener.handleEvent(IOEvent.failure(cause));
@@ -209,7 +377,11 @@ public final class IOStreams {
     }
     
     /**
-     * Wraps stream (wrapper does not close stream after use).
+     * Creates wrapper stream with limited content to specified range.
+     * Skips first "begin" bytes and signals EOF after reaching "end".
+     *
+     * Please note that it does not close "istream"!
+     *
      * @param istream istream
      * @param begin begin
      * @param end end
@@ -222,9 +394,13 @@ public final class IOStreams {
         }
         return new SubInput(istream, begin, end, false);
     }
-    
+
     /**
-     * Wraps stream (wrapper closes stream after use).
+     * Creates wrapper stream with limited content to specified range.
+     * Skips first "begin" bytes and signals EOF after reaching "end".
+     *
+     * Please note that it closes "istream"!
+     *
      * @param istream istream
      * @param begin begin
      * @param end end
@@ -238,6 +414,13 @@ public final class IOStreams {
         return new SubInput(istream, begin, end, true);
     }
 
+    /**
+     * Skips "n" bytes in stream.
+     *
+     * @param istream istream
+     * @param n n
+     * @throws IOException on error
+     */
     public static void skip(InputStream istream, long n) throws IOException {
         long skipped = istream.skip(n);
         if(skipped != n) {
@@ -245,6 +428,14 @@ public final class IOStreams {
         }
     }
 
+    /**
+     * Creates new stream wrapper which delegates all operations to "ostream" and
+     * additionally calls provided callback when "close" is called.
+     *
+     * @param ostream ostream
+     * @param onClose onClose
+     * @return stream
+     */
     public static OutputStream onClose(OutputStream ostream, CheckedRunnable<IOException> onClose) {
         return new OutputWrapper(ostream){
             @Override
@@ -255,6 +446,14 @@ public final class IOStreams {
         };
     }
 
+    /**
+     * Creates new stream wrapper which delegates all operations to "ostream" and
+     * additionally calls provided callback when "close" is called.
+     *
+     * @param istream istream
+     * @param onClose onClose
+     * @return stream
+     */
     public static InputStream onClose(InputStream istream, CheckedRunnable<IOException> onClose) {
         return new InputWrapper(istream){
             @Override

+ 125 - 8
assira.core/src/main/java/net/ranides/assira/io/IOUtils.java

@@ -8,9 +8,11 @@ package net.ranides.assira.io;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
+import lombok.experimental.UtilityClass;
 import net.ranides.assira.events.EventListener;
 import net.ranides.assira.functional.checked.CheckedConsumer;
 import net.ranides.assira.functional.checked.CheckedSupplier;
+import net.ranides.assira.reflection.util.ClassUtils;
 import net.ranides.assira.trace.ExceptionUtils;
 import net.ranides.assira.trace.LoggerUtils;
 
@@ -20,25 +22,45 @@ import java.io.IOException;
 import java.util.function.Consumer;
 
 /**
+ * Utility methods for I/O operations, with rudimentary exception handling.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class IOUtils {
+@UtilityClass
+public class IOUtils {
     
     private static final org.slf4j.Logger LOGGER = LoggerUtils.getLogger();
-    
-    private IOUtils() {
-        /* utility class */
-    }
 
+    /**
+     * If provided exception is IOException then returns it.
+     * In other cases it returns IOException with provided cause.
+     *
+     * @param cause cause
+     * @return IOException
+     */
     public static IOException wrap(Throwable cause) {
         return (cause instanceof IOException) ? (IOException)cause : new IOException(cause);
     }
-    
+
+    /**
+     * Throws provided exception, converting them to IOException if necessary.
+     * It uses sneaky throw.
+     *
+     * @see ExceptionUtils#rethrow(Throwable)
+     * @param cause cause
+     * @return exception
+     */
     public static RuntimeException rethrow(Throwable cause) {
         return ExceptionUtils.rethrow(wrap(cause));
     }
 
+    /**
+     * Adds "current" exception to suppressed list stored in "first"
+     *
+     * @param first first
+     * @param current current
+     * @return "first"
+     */
     public static IOException addSuppressed(IOException first, Throwable current) {
         if(null == first) {
             first = new IOException("There is one or more internal IOException. Check suppressed exceptions.");
@@ -47,6 +69,15 @@ public final class IOUtils {
         return first;
     }
 
+    /**
+     * Closes provided resource and returns "true" if resource is not "null".
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @return boolean
+     * @throws IOException on error
+     */
     public static boolean close(Closeable object) throws IOException {
         if(null != object) {
             object.close();
@@ -55,6 +86,16 @@ public final class IOUtils {
         return false;
     }
 
+    /**
+     * Closes provided resource and returns "true" if resource is not "null".
+     * Sends exception to provided "handler". If handler is "null", it uses default logger.
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @param handler handler
+     * @return boolean
+     */
     public static boolean close(Closeable object, Consumer<IOException> handler) {
         try {
             return close(object);
@@ -68,6 +109,17 @@ public final class IOUtils {
         }
     }
 
+    /**
+     * Closes provided resource and returns "true" if at least one resource is not null.
+     * It does not stop processing after first exception.
+     * If more than one exception is thrown then it is added to supressed list.
+     *
+     * This method is null-safe.
+     *
+     * @param objects objects
+     * @return boolean
+     * @throws IOException on error
+     */
     public static boolean closeAll(Closeable... objects) throws IOException {
         IOException error = null;
         boolean closed = false;
@@ -86,6 +138,16 @@ public final class IOUtils {
         return closed;
     }
 
+    /**
+     * Closes provided resource and returns "true" if resource is not "null".
+     * It supports Closeable and AutoCloseable objects.
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @return boolean
+     * @throws Exception on error
+     */
     public static boolean closeAny(Object object) throws Exception {
         if(object instanceof Closeable) {
             ((Closeable)object).close();
@@ -97,7 +159,18 @@ public final class IOUtils {
         }
         return false;
     }
-    
+
+    /**
+     * Closes provided resource and returns "true" if resource is not "null".
+     * Sends exception to provided "handler". If handler is "null", it uses default logger.
+     * It supports Closeable and AutoCloseable objects.
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @param handler handler
+     * @return boolean
+     */
     public static boolean closeAny(Object object, Consumer<Exception> handler) {
         try {
             return closeAny(object);
@@ -111,6 +184,15 @@ public final class IOUtils {
         }
     }
 
+    /**
+     * Flushes provided resource and returns "true" if resource is not "null".
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @return boolean
+     * @throws IOException on error
+     */
     public static boolean flush(Flushable object) throws IOException {
         if(null != object) {
             object.flush();
@@ -119,6 +201,16 @@ public final class IOUtils {
         return false;
     }
 
+    /**
+     * Flushes provided resource and returns "true" if resource is not "null".
+     * Sends exception to provided "listener". If listener is "null", it uses default logger.
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @param listener listener
+     * @return boolean
+     */
     public static boolean flush(Flushable object, EventListener<? super IOEvent.Failure> listener) {
         try {
             return flush(object);
@@ -132,6 +224,16 @@ public final class IOUtils {
         }
     }
 
+    /**
+     * Flushes provided resource and returns "true" if resource is not "null".
+     * It supports Flushable objects.
+     *
+     * This method is null-safe.
+     *
+     * @param object object
+     * @return boolean
+     * @throws Exception on error
+     */
     public static boolean flush(Object object) throws Exception {
         if(object instanceof Flushable) {
             ((Flushable)object).flush();
@@ -140,6 +242,17 @@ public final class IOUtils {
         return false;
     }
 
+    /**
+     * Flushes provided resource and returns "true" if resource is not "null".
+     * Sends exception to provided "handler". If handler is "null", it uses default logger.
+     * It supports Flushable objects.
+     * 
+     * This method is null-safe.
+     *
+     * @param object object
+     * @param handler handler
+     * @return boolean
+     */
     public static boolean flush(Object object, Consumer<Exception> handler) {
         try {
             return flush(object);
@@ -165,7 +278,11 @@ public final class IOUtils {
 
         @SuppressWarnings("unchecked")
         SharedHandle(CheckedSupplier<T,IOException> open) {
-            this.close = (CheckedConsumer)(CheckedConsumer<Closeable, IOException>)Closeable::close;
+            this.close = resource -> {
+                if(resource instanceof Closeable) {
+                    ((Closeable)resource).close();
+                }
+            };
             this.open = open;
         }
 

+ 30 - 18
assira.core/src/main/java/net/ranides/assira/io/PathUtils.java

@@ -6,8 +6,10 @@
  */
 package net.ranides.assira.io;
 
+import lombok.experimental.UtilityClass;
 import net.ranides.assira.collection.iterators.IterableUtils;
 import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.functional.checked.CheckedSupplier;
 import net.ranides.assira.text.Wildcard;
 
 import java.io.File;
@@ -20,21 +22,21 @@ import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Locale;
+import java.util.function.Supplier;
 
 /**
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class PathUtils {
+@UtilityClass
+public class PathUtils {
 
+    // @todo #56
     private static final boolean WALKER_JDK7 = true;
 
+    // @todo #56
     private static final boolean CASE_SENSITIVE_FILES = !new File( "a" ).equals( new File( "A" ) );
-    
-    private PathUtils() {
-        /* utility class */
-    }
-    
+
     public static Path fromURL(URL url) {
         try {
             return Paths.get(url.toURI());
@@ -43,21 +45,31 @@ public final class PathUtils {
         }
     }
     
-    public static String getExtension(String path) {
-        return getExtension(Paths.get(path));
+    public static String getLongExtension(String path) {
+        return getLongExtension(Paths.get(path));
     }
     
-    public static String getExtension(Path path) {
+    public static String getLongExtension(Path path) {
         String name = path.getFileName().toString();
         int index = name.indexOf(".");
         return (-1==index) ? "" : name.substring(index);
     }
+
+    public static String getShortExtension(String path) {
+        return getShortExtension(Paths.get(path));
+    }
+
+    public static String getShortExtension(Path path) {
+        String name = path.getFileName().toString();
+        int index = name.lastIndexOf(".");
+        return (-1==index) ? "" : name.substring(index);
+    }
     
-    public static Path changeExtension(Path path, String extension) {
-        return changeExtension(path.toString(), extension);
+    public static Path changeLongExtension(Path path, String extension) {
+        return changeLongExtension(path.toString(), extension);
     }
     
-    public static Path changeExtension(String path, String extension) {
+    public static Path changeLongExtension(String path, String extension) {
         int from = Math.max(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')));
         int index = path.substring(from).indexOf(".");
         if(-1==index) {
@@ -67,11 +79,11 @@ public final class PathUtils {
         }
     }
 
-    public static Path changeSuffix(Path path, String extension) {
-        return changeSuffix(path.toString(), extension);
+    public static Path changeShortExtension(Path path, String extension) {
+        return changeShortExtension(path.toString(), extension);
     }
 
-    public static Path changeSuffix(String path, String extension) {
+    public static Path changeShortExtension(String path, String extension) {
         int from = Math.max(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')));
         int index = path.substring(from).lastIndexOf(".");
         if(-1==index) {
@@ -119,7 +131,7 @@ public final class PathUtils {
         }
     }
 
-    public static CQuery<Path> listJDK7(Path dir) {
+    private static CQuery<Path> listJDK7(Path dir) {
         return CQuery.from().iterable(() -> {
             try {
                 return IterableUtils.safe(Files.newDirectoryStream(dir));
@@ -129,12 +141,12 @@ public final class PathUtils {
         });
     }
 
-    public static CQuery<Path> listJDK8(Path dir) {
+    private static CQuery<Path> listJDK8(Path dir) {
         return CQuery.from().stream(() -> Files.list(dir));
     }
 
     public static CQuery<Path> list(Path dir, String wildcard) {
-        return PathUtils.list(dir).filter(Wildcard.compile(wildcard).asPathFilter());
+        return list(dir).filter(Wildcard.compile(wildcard).asPathFilter());
     }
 
     public static CQuery<Path> walk(Path dir) {

+ 133 - 27
assira.core/src/main/java/net/ranides/assira/io/ResourceUtils.java

@@ -6,6 +6,7 @@
  */
 package net.ranides.assira.io;
 
+import lombok.experimental.UtilityClass;
 import net.ranides.assira.reflection.util.ClassLoaderUtils;
 import net.ranides.assira.text.IOStrings;
 
@@ -22,20 +23,34 @@ import java.nio.charset.Charset;
 import java.util.MissingResourceException;
 
 /**
+ * Utility methods for reading resource files
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
-public final class ResourceUtils {
-    
-    private ResourceUtils() {
-        /* utility class */
-    }
-    
-    
-    public static void extract(String resource, File target) throws IOException, MissingResourceException {    
+@UtilityClass public class ResourceUtils {
+
+    /**
+     * Extracts resource and saves it into specified file
+     * It uses {@linkplain ClassLoaderUtils#getDefault() default class loader}.
+     *
+     * @param resource resource
+     * @param target target
+     * @throws IOException on error
+     * @throws MissingResourceException if there is no resource
+     */
+    public static void extract(String resource, File target) throws IOException, MissingResourceException {
         extract(ClassLoaderUtils.getDefault(), resource, target);
     }
-    
+
+    /**
+     * Extracts resource and saves it into specified file
+     *
+     * @param loader loader
+     * @param resource resource
+     * @param target target
+     * @throws IOException on error
+     * @throws MissingResourceException if there is no resource
+     */
     public static void extract(ClassLoader loader, String resource, File target) throws IOException, MissingResourceException {
         try(InputStream istream = stream(loader, resource)) {
             try(OutputStream ostream = new BufferedOutputStream(new FileOutputStream(target))) {
@@ -44,59 +59,150 @@ public final class ResourceUtils {
         }
     }
 
-    public static URL url(String resource) throws IOException {
+    /**
+     * Returns URL to provided resource.
+     * Most probably returned URL will use JAR scheme, or FILE (in unit tests)
+     * It uses {@linkplain ClassLoaderUtils#getDefault() default class loader}.
+     *
+     * @param resource resource
+     * @return URL
+     * @throws MissingResourceException if there is no resource
+     */
+    public static URL url(String resource) throws MissingResourceException {
         return url(ClassLoaderUtils.getDefault(), resource);
     }
 
-    public static URL url(ClassLoader loader, String resource) throws IOException {
+    /**
+     * Returns URL to provided resource.
+     * Most probably returned URL will use JAR scheme, or FILE (in unit tests)
+     *
+     * @param loader loader
+     * @param resource resource
+     * @return URL
+     * @throws MissingResourceException if there is no resource
+     */
+    public static URL url(ClassLoader loader, String resource) throws MissingResourceException {
         URL url = loader.getResource(resource);
         if(null==url) {
-            throw new IOException("Resource not found: " + resource);
+            throw new MissingResourceException("Resource not found", "ResourceUtils", resource);
         }
         return url;
     }
 
-    public static URL url(Class<?> loader, String resource) throws IOException {
+    /**
+     * Returns URL to provided resource.
+     * Most probably returned URL will use JAR scheme, or FILE (in unit tests)
+     * It uses parent class loader of provided class.
+     *
+     * @param loader loader
+     * @param resource resource
+     * @return URL
+     * @throws MissingResourceException if there is no resource
+     */
+    public static URL url(Class<?> loader, String resource) throws MissingResourceException {
         URL url = loader.getResource(resource);
         if(null==url) {
-            throw new IOException("Resource not found: " + resource);
+            throw new MissingResourceException("Resource not found", "ResourceUtils", resource);
         }
         return url;
     }
-    
-    public static InputStream stream(String resource) throws IOException {
+
+    /**
+     * Opens stream for provided resource.
+     * It uses {@linkplain ClassLoaderUtils#getDefault() default class loader}.
+     *
+     * @param resource resource
+     * @return stream
+     * @throws MissingResourceException if there is no resource
+     */
+    public static InputStream stream(String resource) throws MissingResourceException {
         return stream(ClassLoaderUtils.getDefault(), resource);
     }
-    
-    public static InputStream stream(ClassLoader loader, String resource) throws IOException {
+
+    /**
+     * Opens stream for provided resource.
+     *
+     * @param loader loader
+     * @param resource resource
+     * @return stream
+     * @throws MissingResourceException if there is no resource
+     */
+    public static InputStream stream(ClassLoader loader, String resource) throws MissingResourceException {
         InputStream stream = loader.getResourceAsStream(resource);
         if(null==stream) {
-            throw new IOException("Resource not found: " + resource);
+            throw new MissingResourceException("Resource not found", "ResourceUtils", resource);
         }
         return stream;
     }
 
-    public static InputStream stream(Class<?> loader, String resource) throws IOException {
+    /**
+     * Opens stream for provided resource.
+     * It uses parent class loader of provided class.
+     *
+     * @param loader loader
+     * @param resource resource
+     * @return stream
+     * @throws MissingResourceException if there is no resource
+     */
+    public static InputStream stream(Class<?> loader, String resource) throws MissingResourceException {
         InputStream stream = loader.getResourceAsStream(resource);
         if(null==stream) {
-            throw new IOException("Resource not found: " + resource);
+            throw new MissingResourceException("Resource not found", "ResourceUtils", resource);
         }
         return stream;
     }
-    
-    public static Reader reader(String resource, Charset charset) throws IOException {
+
+    /**
+     * Opens reader for provided resource, it uses "charset" for encoding.
+     * It uses {@linkplain ClassLoaderUtils#getDefault() default class loader}.
+     *
+     * @param resource resource
+     * @param charset charset
+     * @return reader
+     * @throws MissingResourceException if there is no resource
+     */
+    public static Reader reader(String resource, Charset charset) throws MissingResourceException {
         return reader(ClassLoaderUtils.getDefault(), resource, charset);
     }
-    
-    public static Reader reader(ClassLoader loader, String resource, Charset charset) throws IOException {
+
+    /**
+     * Opens reader for provided resource, it uses "charset" for encoding.
+     *
+     * @param loader loader
+     * @param resource resource
+     * @param charset charset
+     * @return reader
+     * @throws MissingResourceException if there is no resource
+     */
+    public static Reader reader(ClassLoader loader, String resource, Charset charset) throws MissingResourceException {
         return new InputStreamReader(stream(loader, resource), charset);
     }
 
-    public static String text(String resource, Charset charset) throws IOException {
+    /**
+     * Reads all text content from provided resource using "charset" encoding.
+     * It uses {@linkplain ClassLoaderUtils#getDefault() default class loader}.
+     *
+     * @param resource resource
+     * @param charset charset
+     * @return string
+     * @throws IOException on error
+     * @throws MissingResourceException if there is no resource
+     */
+    public static String text(String resource, Charset charset) throws IOException, MissingResourceException {
         return IOStrings.read(reader(resource, charset));
     }
 
-    public static String text(ClassLoader loader, String resource, Charset charset) throws IOException {
+    /**
+     * Reads all text content from provided resource using "charset" encoding.
+     *
+     * @param loader loader
+     * @param resource resource
+     * @param charset charset
+     * @return string
+     * @throws IOException on error
+     * @throws MissingResourceException if there is no resource
+     */
+    public static String text(ClassLoader loader, String resource, Charset charset) throws IOException, MissingResourceException {
         return IOStrings.read(reader(loader, resource, charset));
     }
 

+ 25 - 1
assira.core/src/main/java/net/ranides/assira/io/StringInput.java

@@ -12,6 +12,8 @@ import java.nio.charset.Charset;
 import net.ranides.assira.text.Charsets;
 
 /**
+ * Extension of ByteArrayInputStream.
+ * It uses provided String as source of binary data.
  *
  * @author ranides
  */
@@ -19,19 +21,41 @@ public class StringInput extends ByteArrayInputStream {
     
     private final Charset cs;
 
+    /**
+     * Creates new stream which converts provided string into bytes using provided charset.
+     *
+     * @param text text
+     * @param charset charset
+     */
     public StringInput(String text, Charset charset) {
         super(text.getBytes(charset));
         this.cs = charset;
     }
 
+    /**
+     * Creates new stream which converts provided string into bytes using provided charset.
+     *
+     * @param text text
+     * @param charset charset
+     */
     public StringInput(String text, String charset) {
         this(text, Charset.forName(charset));
     }
 
+    /**
+     * Creates new stream which converts provided string into bytes using UTF8 charset.
+     *
+     * @param text text
+     */
     public StringInput(String text) {
         this(text, Charsets.UTF8);
     }
-    
+
+    /**
+     * Returns charset used to encode input string
+     *
+     * @return charset
+     */
     public Charset charset() {
         return cs;
     }

+ 29 - 7
assira.core/src/main/java/net/ranides/assira/io/StringOutput.java

@@ -14,29 +14,37 @@ import java.nio.charset.UnsupportedCharsetException;
 import net.ranides.assira.text.Charsets;
 
 /**
+ * Extension of ByteArrayOutputStream.
+ * It converts stored binary data into string using provided charset
  *
  * @author ranides
  */
 public class StringOutput extends ByteArrayOutputStream {
     
     private final Charset cs;
-    
+
     /**
-     * Use UTF8 charset as default
+     * Creates new stream using UTF8 charset as default
      */
     public StringOutput() {
         this(Charsets.UTF8);
     }
-            
 
+
+    /**
+     * Creates new stream using provided charset as default
+     *
+     * @param cs cs
+     */
     public StringOutput(Charset cs) {
         super();
         this.cs = cs;
     }
 
     /**
-     * 
-     * @return String decoded from the buffer's contents.
+     * Decodes buffer's contents into String using stream's default charset.
+     *
+     * @return String
      */
     @Override
     public String toString() {
@@ -44,8 +52,9 @@ public class StringOutput extends ByteArrayOutputStream {
     }
 
     /**
-     * 
-     * @return String decoded from the buffer's contents.
+     * Decodes buffer's contents into String using provided charset.
+     *
+     * @return String
      */
     @Override
     public String toString(String charset) throws UnsupportedCharsetException {
@@ -56,10 +65,23 @@ public class StringOutput extends ByteArrayOutputStream {
         }
     }
 
+    /**
+     * Decodes buffer's contents into String using provided charset.
+     *
+     * @param charset charset
+     * @return String
+     */
     public String toString(Charset charset) {
         return toString(charset.name());
     }
 
+    /**
+     * Default charset used by {@link #toString()} method.
+     * It is configured at creation time.
+     * It is UTF8 if not defined explicitly.
+     *
+     * @return charset
+     */
     public Charset charset() {
         return cs;
     }

+ 4 - 4
assira.core/src/test/java/net/ranides/assira/io/IOStreamsTest.java

@@ -83,10 +83,10 @@ public class IOStreamsTest {
         byte[] data2 = random(7*4096+33, 777L);
         byte[] data3 = random(7*4096+33, 778L);
         byte[] data4 = random(7*4096+34, 777L);
-        assertTrue(IOStreams.equivalent(new ByteArrayInputStream(data1), new ByteArrayInputStream(data1)));
-        assertTrue(IOStreams.equivalent(new ByteArrayInputStream(data1), new ByteArrayInputStream(data2)));
-        assertFalse(IOStreams.equivalent(new ByteArrayInputStream(data1), new ByteArrayInputStream(data3)));
-        assertFalse(IOStreams.equivalent(new ByteArrayInputStream(data1), new ByteArrayInputStream(data4)));
+        assertTrue(IOStreams.equals(new ByteArrayInputStream(data1), new ByteArrayInputStream(data1)));
+        assertTrue(IOStreams.equals(new ByteArrayInputStream(data1), new ByteArrayInputStream(data2)));
+        assertFalse(IOStreams.equals(new ByteArrayInputStream(data1), new ByteArrayInputStream(data3)));
+        assertFalse(IOStreams.equals(new ByteArrayInputStream(data1), new ByteArrayInputStream(data4)));
     }
     
     private static void assertCopy(int size) throws IOException {

+ 22 - 24
assira.core/src/test/java/net/ranides/assira/io/PathUtilsTest.java

@@ -11,14 +11,12 @@ import static org.junit.Assert.assertEquals;
 
 import java.io.File;
 import java.net.MalformedURLException;
-import java.net.URI;
 import java.net.URL;
 import java.nio.file.FileSystemNotFoundException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
-import java.util.List;
 
 import net.ranides.assira.collection.query.CQuery;
 import org.junit.Assume;
@@ -34,10 +32,10 @@ public class PathUtilsTest {
     
     @Test
     public void testGetExtension() {
-        assertEquals(".txt", PathUtils.getExtension("file.txt"));
-        assertEquals(".txt", PathUtils.getExtension("\\base\\file.txt"));
-        assertEquals(".txt", PathUtils.getExtension("/base/file.txt"));
-        assertEquals(".log.txt", PathUtils.getExtension("/base/file.log.txt"));
+        assertEquals(".txt", PathUtils.getLongExtension("file.txt"));
+        assertEquals(".txt", PathUtils.getLongExtension("\\base\\file.txt"));
+        assertEquals(".txt", PathUtils.getLongExtension("/base/file.txt"));
+        assertEquals(".log.txt", PathUtils.getLongExtension("/base/file.log.txt"));
     }
 
     @Test
@@ -77,36 +75,36 @@ public class PathUtilsTest {
 
     @Test
     public void testChangeExtensionString() {
-        assertEquals(Paths.get("file.txt"), PathUtils.changeExtension("file.txt", ".txt"));
-        assertEquals(Paths.get("file.dat"), PathUtils.changeExtension("file.txt", ".dat"));
-        assertEquals(Paths.get("file.txt.dat"), PathUtils.changeExtension("file.txt.info", ".txt.dat"));
-        assertEquals(Paths.get("file.dat"), PathUtils.changeExtension("file.txt.info", ".dat"));
-        assertEquals(Paths.get("file.dat"), PathUtils.changeExtension("file", ".dat"));
+        assertEquals(Paths.get("file.txt"), PathUtils.changeLongExtension("file.txt", ".txt"));
+        assertEquals(Paths.get("file.dat"), PathUtils.changeLongExtension("file.txt", ".dat"));
+        assertEquals(Paths.get("file.txt.dat"), PathUtils.changeLongExtension("file.txt.info", ".txt.dat"));
+        assertEquals(Paths.get("file.dat"), PathUtils.changeLongExtension("file.txt.info", ".dat"));
+        assertEquals(Paths.get("file.dat"), PathUtils.changeLongExtension("file", ".dat"));
 
-        assertEquals(Paths.get("base","name","file.txt"), PathUtils.changeExtension("base/name/file.txt", ".txt"));
+        assertEquals(Paths.get("base","name","file.txt"), PathUtils.changeLongExtension("base/name/file.txt", ".txt"));
     }
 
     @Test
     public void testChangeExtensionString_W32() {
         Assume.assumeTrue(HostSystem.WINDOWS.detected());
 
-        assertEquals(Paths.get("base","name","file.dat"), PathUtils.changeExtension("base\\name\\file.txt", ".dat"));
-        assertEquals(Paths.get("c:","base","name","file.dat"), PathUtils.changeExtension("c:\\base\\name\\file.txt", ".dat"));
-        assertEquals(Paths.get("/base","name","file.dat"), PathUtils.changeExtension("/base/name\\file.txt", ".dat"));
+        assertEquals(Paths.get("base","name","file.dat"), PathUtils.changeLongExtension("base\\name\\file.txt", ".dat"));
+        assertEquals(Paths.get("c:","base","name","file.dat"), PathUtils.changeLongExtension("c:\\base\\name\\file.txt", ".dat"));
+        assertEquals(Paths.get("/base","name","file.dat"), PathUtils.changeLongExtension("/base/name\\file.txt", ".dat"));
     }
 
     @Test
     public void testChangeExtension_NIX() {
         Assume.assumeFalse(HostSystem.WINDOWS.detected());
 
-        assertEquals(Paths.get("base","name","file.dat"), PathUtils.changeExtension("base/name/file.txt", ".dat"));
-        assertEquals(Paths.get("c:","base","name","file.dat"), PathUtils.changeExtension("c:/base/name/file.txt", ".dat"));
-        assertEquals(Paths.get("/base","name","file.dat"), PathUtils.changeExtension("/base/name/file.txt", ".dat"));
+        assertEquals(Paths.get("base","name","file.dat"), PathUtils.changeLongExtension("base/name/file.txt", ".dat"));
+        assertEquals(Paths.get("c:","base","name","file.dat"), PathUtils.changeLongExtension("c:/base/name/file.txt", ".dat"));
+        assertEquals(Paths.get("/base","name","file.dat"), PathUtils.changeLongExtension("/base/name/file.txt", ".dat"));
 
-        assertEquals(Paths.get("base\\fame\\file.dat"), PathUtils.changeExtension("base\\fame\\file.txt", ".dat"));
-        assertEquals(Paths.get("base\\name\\file.dat"), PathUtils.changeExtension("base\\name\\file.txt", ".dat"));
-        assertEquals(Paths.get("c:\\base\\name\\file.dat"), PathUtils.changeExtension("c:\\base\\name\\file.txt", ".dat"));
-        assertEquals(Paths.get("/base","name\\file.dat"), PathUtils.changeExtension("/base/name\\file.txt", ".dat"));
+        assertEquals(Paths.get("base\\fame\\file.dat"), PathUtils.changeLongExtension("base\\fame\\file.txt", ".dat"));
+        assertEquals(Paths.get("base\\name\\file.dat"), PathUtils.changeLongExtension("base\\name\\file.txt", ".dat"));
+        assertEquals(Paths.get("c:\\base\\name\\file.dat"), PathUtils.changeLongExtension("c:\\base\\name\\file.txt", ".dat"));
+        assertEquals(Paths.get("/base","name\\file.dat"), PathUtils.changeLongExtension("/base/name\\file.txt", ".dat"));
     }
 
     @Test
@@ -145,7 +143,7 @@ public class PathUtilsTest {
 
     @Test
     public void testChangeSuffix() {
-        assertEquals("c:\\ms\\root\\data.exe", PathUtils.changeSuffix("c:\\ms\\root\\data.if", ".exe").toString() );
+        assertEquals("c:\\ms\\root\\data.exe", PathUtils.changeShortExtension("c:\\ms\\root\\data.if", ".exe").toString() );
     }
 
     @Test
@@ -263,7 +261,7 @@ public class PathUtilsTest {
     }
 
     private static Path pce(String path, String ext) {
-        return PathUtils.changeExtension(Paths.get(path), ext);
+        return PathUtils.changeLongExtension(Paths.get(path), ext);
     }
 
     @Test