Bladeren bron

#37 javadoc: uri

Ranides Atterwim 4 jaren geleden
bovenliggende
commit
3025276d5a

+ 236 - 6
assira.core/src/main/java/net/ranides/assira/io/PathUtils.java

@@ -14,6 +14,7 @@ import net.ranides.assira.text.Wildcard;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.file.Files;
@@ -25,6 +26,7 @@ import java.util.Locale;
 import java.util.function.Supplier;
 
 /**
+ * Utility methods for working with Paths.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
@@ -37,6 +39,16 @@ public class PathUtils {
     // @todo #56
     private static final boolean CASE_SENSITIVE_FILES = !new File( "a" ).equals( new File( "A" ) );
 
+    /**
+     * Creates Path from provided URL.
+     *
+     * Method converts URL to URI and then to Path.
+     * It does not extract "path" part from URL.
+     * It resolves full URL into Path, trying to use scheme supported by JDK.
+     *
+     * @param url url
+     * @return
+     */
     public static Path fromURL(URL url) {
         try {
             return Paths.get(url.toURI());
@@ -44,31 +56,101 @@ public class PathUtils {
             throw new IllegalArgumentException(cause);
         }
     }
-    
+
+    /**
+     * Extracts long extension part from given path.
+     * Long extension is fragment of filename after first dot "." separator, including dot.
+     *
+     * For example: "c:\\windows\\file.txt.bak" will give ".txt.bak"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getLongExtension(String path) {
         return getLongExtension(Paths.get(path));
     }
-    
+
+    /**
+     * Extracts long extension part from given path.
+     * Long extension is fragment of filename after first dot "." separator, including dot.
+     *
+     * For example: "c:\\windows\\file.txt.bak" will give ".txt.bak"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getLongExtension(Path path) {
         String name = path.getFileName().toString();
         int index = name.indexOf(".");
         return (-1==index) ? "" : name.substring(index);
     }
 
+    /**
+     * Extracts short extension part from given path.
+     * Short extension is fragment of filename after last dot "." separator, including dot.
+     *
+     * For example: "c:\\windows\\file.txt.bak" will give ".bak"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getShortExtension(String path) {
         return getShortExtension(Paths.get(path));
     }
 
+    /**
+     * Extracts short extension part from given path.
+     * Short extension is fragment of filename after last dot "." separator, including dot.
+     *
+     * For example: "c:\\windows\\file.txt.bak" will give ".bak"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getShortExtension(Path path) {
         String name = path.getFileName().toString();
         int index = name.lastIndexOf(".");
         return (-1==index) ? "" : name.substring(index);
     }
-    
+
+    /**
+     * Creates new path, with long extesion replaced by provided one.
+     * Long extension is fragment of filename after first dot "." separator, including dot.
+     *
+     * New extension should include dot, if necessary.
+     * New extension could be empty, then old extension will just removed.
+     *
+     * For example, for path: "c:\\windows\\file.txt.bak"
+     *      replace with ".log" will give "c:\\windows\\file.log"
+     *      replace with "log" will give "c:\\windows\\filelog"
+     *      replace with "\\log" will give "c:\\windows\\file\\log"
+     *      replace with "" will give "c:\\windows\\file"
+     *
+     * @param path path
+     * @param extension extension
+     * @return path
+     */
     public static Path changeLongExtension(Path path, String extension) {
         return changeLongExtension(path.toString(), extension);
     }
-    
+
+    /**
+     * Creates new path, with long extesion replaced by provided one.
+     * Long extension is fragment of filename after first dot "." separator, including dot.
+     *
+     * New extension should include dot, if necessary.
+     * New extension could be empty, then old extension will be just removed.
+     *
+     * For example, for path: "c:\\windows\\file.txt.bak"
+     *      replace with ".log" will give "c:\\windows\\file.log"
+     *      replace with "log" will give "c:\\windows\\filelog"
+     *      replace with "\\log" will give "c:\\windows\\file\\log"
+     *      replace with "" will give "c:\\windows\\file"
+     *
+     * @param path path
+     * @param extension extension
+     * @return path
+     */
     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(".");
@@ -79,10 +161,44 @@ public class PathUtils {
         }
     }
 
+    /**
+     * Creates new path, with short extesion replaced by provided one.
+     * Short extension is fragment of filename after last dot "." separator, including dot.
+     *
+     * New extension should include dot, if necessary.
+     * New extension could be empty, then old extension will be just removed.
+     *
+     * For example, for path: "c:\\windows\\file.txt.bak"
+     *      replace with ".log" will give "c:\\windows\\file.txt.log"
+     *      replace with "log" will give "c:\\windows\\file.txtlog"
+     *      replace with "\\log" will give "c:\\windows\\file.txt\\log"
+     *      replace with "" will give "c:\\windows\\file.txt"
+     *
+     * @param path path
+     * @param extension extension
+     * @return path
+     */
     public static Path changeShortExtension(Path path, String extension) {
         return changeShortExtension(path.toString(), extension);
     }
 
+    /**
+     * Creates new path, with short extesion replaced by provided one.
+     * Short extension is fragment of filename after last dot "." separator, including dot.
+     *
+     * New extension should include dot, if necessary.
+     * New extension could be empty, then old extension will be just removed.
+     *
+     * For example, for path: "c:\\windows\\file.txt.bak"
+     *      replace with ".log" will give "c:\\windows\\file.txt.log"
+     *      replace with "log" will give "c:\\windows\\file.txtlog"
+     *      replace with "\\log" will give "c:\\windows\\file.txt\\log"
+     *      replace with "" will give "c:\\windows\\file.txt"
+     *
+     * @param path path
+     * @param extension extension
+     * @return path
+     */
     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(".");
@@ -93,6 +209,16 @@ public class PathUtils {
         }
     }
 
+    /**
+     * Returns true if path ends with provided suffix.
+     *
+     * Behaviour is platform dependent:
+     * if current file system is case sensitive, then this methods is too.
+     *
+     * @param path path
+     * @param extension extension
+     * @return boolean
+     */
     public static boolean hasExtension(String path, String extension) {
         if(CASE_SENSITIVE_FILES) {
             return path.endsWith(extension);
@@ -101,28 +227,76 @@ public class PathUtils {
         }
     }
 
+    /**
+     * Returns true if path ends with provided suffix.
+     *
+     * Behaviour is platform dependent:
+     * if current file system is case sensitive, then this methods is too.
+     *
+     * @param path path
+     * @param extension extension
+     * @return boolean
+     */
     public static boolean hasExtension(Path path, String extension) {
         return hasExtension(path.toString(), extension);
     }
-    
+
+    /**
+     * Extracts filename without long extension.
+     * For example: "c:\\windows\\file.txt.bak" will give "file"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getSimpleName(String path) {
         return getSimpleName(Paths.get(path));
     }
-    
+
+    /**
+     * Extracts filename without long extension.
+     * For example: "c:\\windows\\file.txt.bak" will give "file"
+     *
+     * @param path path
+     * @return string
+     */
     public static String getSimpleName(Path path) {
         String name = path.getFileName().toString();
         int index = name.indexOf(".");
         return index<0 ? name : name.substring(0, index);
     }
 
+    /**
+     * Returns normalized absolute path.
+     * It uses filesystem calls to compute both absolute path and normalization.
+     *
+     * @param path path
+     * @return Path
+     */
     public static Path normalize(Path path) {
         return path.toAbsolutePath().normalize();
     }
 
+    /**
+     * Returns normalized absolute path.
+     * It uses filesystem calls to compute both absolute path and normalization.
+     *
+     * @param path path
+     * @return Path
+     */
     public static String normalize(String path) {
         return normalize(Paths.get(path)).toString();
     }
 
+    /**
+     * Returns CQuery with all files inside directory.
+     * Returns both subdirectories and ordinary files.
+     * It is not recursive.
+     *
+     * The Path objects are obtained as if by resolving the name of the directory entry against dir.
+     *
+     * @param dir dir
+     * @return query
+     */
     public static CQuery<Path> list(Path dir) {
         if(WALKER_JDK7) {
             return listJDK7(dir);
@@ -145,29 +319,85 @@ public class PathUtils {
         return CQuery.from().stream(() -> Files.list(dir));
     }
 
+    /**
+     * Returns CQuery with all files inside directory matching provided wildcard
+     * Returns both subdirectories and ordinary files.
+     * It is not recursive.
+     *
+     * The Path objects are obtained as if by resolving the name of the directory entry against dir.
+     *
+     * @param dir dir
+     * @param wildcard wildcard
+     * @return query
+     */
     public static CQuery<Path> list(Path dir, String wildcard) {
         return list(dir).filter(Wildcard.compile(wildcard).asPathFilter());
     }
 
+    /**
+     * Returns CQuery with all files inside directory.
+     * Returns both subdirectories and ordinary files.
+     * It is recursive.
+     * Recursively visited directories are returned too.
+     *
+     * The Path objects are obtained as if by resolving the name of the directory entry against dir.
+     *
+     * @param dir dir
+     * @return query
+     */
     public static CQuery<Path> walk(Path dir) {
         return list(dir).flatIf(Files::isDirectory, file -> {
             return CQuery.builder().with(file).withQuery(PathUtils.walk(file)).build();
         });
     }
 
+    /**
+     * Returns CQuery with all files inside directory matching provided wildcard
+     * Returns both subdirectories and ordinary files.
+     * It is recursive.
+     * Recursively visited directories are returned too.
+     *
+     * The Path objects are obtained as if by resolving the name of the directory entry against dir.
+     *
+     * @param dir dir
+     * @param wildcard wildcard
+     * @return query
+     */
     public static CQuery<Path> walk(Path dir, String wildcard) {
         return PathUtils.walk(dir).filter(Wildcard.compile(wildcard).asPathFilter());
     }
 
+    /**
+     * Returns current working directory of application
+     *
+     * @return path
+     */
     public static Path cwd() {
         return PathUtils.normalize(Paths.get("."));
     }
 
+    /**
+     * Returns common path part of two specified paths.
+     * It means: path to directory which contains both arguments.
+     * Returns empty path if there is no common root.
+     *
+     * @param a a
+     * @param b b
+     * @return path
+     */
     public static Path common(Path a, Path b) {
         return common0(a,b);
     }
 
 
+    /**
+     * Returns common path part of all specified paths.
+     * It means: path to directory which contains all paths.
+     * Returns empty path if there is no common root.
+     *
+     * @param values values
+     * @return path
+     */
     public static Path common(Collection<Path> values) {
         if(values.isEmpty()) {
             return cwd();

+ 225 - 28
assira.core/src/main/java/net/ranides/assira/io/uri/URIBuilder.java

@@ -21,6 +21,8 @@ import net.ranides.assira.reflection.util.ResolveUtils;
 import net.ranides.assira.text.*;
 
 /**
+ * This is mutable URI.
+ * It could be used both for parsing and for modification of URI compoments.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
@@ -40,11 +42,21 @@ public class URIBuilder {
     
     private String login;
     private String password;
-    
+
+    /**
+     * Creates new empty URIBuilder.
+     * Even scheme part is undefined.
+     */
     public URIBuilder() {
         params = new ParamMap();
     }
-    
+
+    /**
+     * Creates new URIBuilder from provided string.
+     * String should have at least defined scheme part.
+     *
+     * @param uri uri
+     */
     public URIBuilder(String uri) {
         Matcher matcher = RE_URL.matcher(uri);
         if(!matcher.matches()) {
@@ -59,7 +71,12 @@ public class URIBuilder {
         this.params = new ParamMap(matcher.group(7));
         this.fragment = matcher.group(8);
     }
-    
+
+    /**
+     * Creates new URIBuilder from provided URI.
+     *
+     * @param uri uri
+     */
     public URIBuilder(URI uri) {
         scheme = uri.getScheme();
         host = uri.getHost();
@@ -72,113 +89,277 @@ public class URIBuilder {
         login = URIUtils.getLogin(uri).orElse(null);
         password = URIUtils.getPassword(uri).orElse(null);
     }
-    
+
+    /**
+     * Returns LOGIN part of URI, assuming form: "scheme://login:password@host..."
+     *
+     * @return optional
+     */
     public Optional<String> login() {
         return Optional.ofNullable(login);
     }
-    
+
+    /**
+     * Changes LOGIN part of URI, assuming form: "scheme://login:password@host..."
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder login(String value) {
         this.login = value;
         return this;
     }
-    
+
+    /**
+     * Returns PASSWORD part of URI, assuming form: "scheme://login:password@host..."
+     *
+     * @return optional
+     */
     public Optional<String> password() {
         return Optional.ofNullable(password);
     }
-    
+
+    /**
+     * Changes PASSWORD part of URI, assuming form: "scheme://login:password@host..."
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder password(String value) {
         this.password = value;
         return this;
     }
-    
+
+    /**
+     * Returns HOST part of URI, assuming form: "scheme://...@host.com:port/..."
+     *
+     * @return optional
+     */
     public Optional<String> host() {
         return Optional.ofNullable(host);
     }
-    
+
+    /**
+     * Changes HOST part of URI, assuming form: "scheme://...@host.com:port/..."
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder host(String value) {
         this.host = value;
         return this;
     }
-    
+
+    /**
+     * Returns FRAGMENT part of URI, assuming form: "scheme://host/path...#fragment"
+     *
+     * @return optional
+     */
     public Optional<String> fragment() {
         return Optional.ofNullable(fragment);
     }
-    
+
+    /**
+     * Changes FRAGMENT part of URI, assuming form: "scheme://host/path...#fragment"
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder fragment(String value) {
         this.fragment = value;
         return this;
     }
-    
+
+    /**
+     * Returns PATH part of URI, assuming form: "scheme://host/path...#fragment"
+     *
+     * @return optional
+     */
     public Optional<String> path() {
         return StringTraits.isEmpty(path) ? Optional.empty() : Optional.of(path);
     }
-    
+
+    /**
+     * Changes PATH part of URI, assuming form: "scheme://host/path...#fragment"
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder path(String value) {
         path = value;
         return this;
     }
-    
+
+    /**
+     * Changes PATH part of URI, assuming form: "scheme://host/path...#fragment"
+     * Concatenates provided values using "/" as separator.
+     *
+     * @param values values
+     * @return this
+     */
     public URIBuilder path(String... values) {
         path = StringUtils.join(values, "/");
         return this;
     }
 
+    /**
+     * Changes PATH part of URI, assuming form: "scheme://host/path...#fragment"
+     * Appends provided value to already defined path, using "/" as separator.
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder append(String value) {
         path = path + "/" + value;
         return this;
     }
-    
+
+    /**
+     * Changes PATH part of URI, assuming form: "scheme://host/path...#fragment"
+     * Appends provided values to already defined path, using "/" as separator.
+     *
+     * @param values values
+     * @return this
+     */
     public URIBuilder append(String... values) {
         path = new StrBuilder(path.length()).append(path).append("/").open("", "", "/").append(values).toString();
         return this;
     }
-    
+
+    /**
+     * Returns PORT part of URI, assuming form: "scheme://...@host.com:port/..."
+     *
+     * @return optional
+     */
     public Optional<String> port() {
         return Optional.ofNullable(port);
     }
-    
+
+    /**
+     * Changes PORT part of URI, assuming form: "scheme://...@host.com:port/..."
+     * If you want to remove PORT part, you should pass null.
+     *
+     * It is not strictly required, but advised strongly, to use values parsable as integer.
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder port(String value) {
         this.port = value;
         return this;
     }
-    
+
+    /**
+     * Changes PORT part of URI, assuming form: "scheme://...@host.com:port/..."
+     * If you want to remove PORT part, you should pass -1.
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder port(int value) {
         this.port = String.valueOf(value);
         return this;
     }
-    
+
+    /**
+     * Returns value of query parameter with "name".
+     * Because URI allows for multiple parameters with the same value, returns list.
+     *
+     * @param name name
+     * @return list
+     */
     public List<String> param(String name) {
         return params.getAll(name);
     }
-    
+
+    /**
+     * Adds query parameter with specified name and value.
+     *
+     * It does not remove any already defined parameters
+     * Even parameters with the same name won't be removed or replaced.
+     *
+     * @param name name
+     * @param value value
+     * @return this
+     */
     public URIBuilder param(String name, String value) {
         params.put(name, value);
         return this;
     }
-    
+
+    /**
+     * Adds query parameters with specified name and values.
+     * URIs support adding multiple parameters with the same name, thats what this method does.
+     *
+     * It does not remove any already defined parameters
+     * Even parameters with the same name won't be removed or replaced.
+     *
+     * @param name name
+     * @param value value
+     * @return this
+     */
     public URIBuilder param(String name, List<String> value) {
         params.putAll(name, value);
         return this;
     }
-    
+
+    /**
+     * Returns all defined parameters.
+     * Returned MapBuilder can be used for both read and write operations.
+     *
+     * MapBuilder is backed by MultiMap, because URIs support adding multiple parameters with the same name.
+     *
+     * @return MultiMap
+     */
     public MapBuilder<String,String,? extends MultiMap<String,String>> params() {
         return MapBuilder.into(params);
     }
-    
+
+    /**
+     * Adds all query parameters with name and values store inside provided map.
+     *
+     * Please note, that MultiMap is correctly inserted too.
+     *
+     * It does not remove or replace any already defined parameters
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder params(Map<String,String> value) {
         params.putAll(value);
         return this;
     }
-    
+
+    /**
+     * Returns SCHEME part of URI, assuming form: "scheme://..."
+     *
+     * @return string
+     */
     public String scheme() {
         return scheme;
     }
-    
+
+    /**
+     * Changes SCHEME part of URI, assuming form: "scheme://..."
+     *
+     * @param value value
+     * @return this
+     */
     public URIBuilder scheme(String value) {
         this.scheme = value;
         return this;
     }
-    
+
+    /**
+     * Returns new URIBuilder with all ${variable} expressions replaced by properties
+     * loaded from provided context.
+     *
+     * It uses ResolveDialect.HANDLEBARS to detect variable expressions.
+     * It uses {@link ResolveFormat#format(Object)} to compute detected variables.
+     *
+     * @param context context
+     * @return URIBuilder
+     */
     public URIBuilder resolve(Object context) {
         URIBuilder out = new URIBuilder();
         out.scheme = resolveFormat(scheme, context);
@@ -192,7 +373,12 @@ public class URIBuilder {
         
         return out;
     }
-    
+
+    /**
+     * Creates new URI
+     *
+     * @return URI
+     */
     public URI build() {
         try {
             HostAddr addr = new HostAddr(host, port);
@@ -201,7 +387,18 @@ public class URIBuilder {
             throw new IllegalArgumentException(cause);
         }
     }
-    
+
+    /**
+     * Creates new URI
+     *
+     * Replaces all ${variable} expressions by properties loaded from provided context.
+     *
+     * It uses ResolveDialect.HANDLEBARS to detect variable expressions.
+     * It uses {@link ResolveFormat#format(Object)} to compute detected variables.
+     *
+     * @param context context
+     * @return URI
+     */
     public URI build(Object context) {
         return resolve(context).build();
     }

+ 75 - 7
assira.core/src/main/java/net/ranides/assira/io/uri/URIFlags.java

@@ -7,25 +7,63 @@
 package net.ranides.assira.io.uri;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.Set;
 import net.ranides.assira.collection.sets.MaskFactory;
 import net.ranides.assira.collection.sets.MaskSet;
 
 /**
+ * This type defines all operations which can be supported by {@link URIHandle}.
+ *
+ * @see URIHandle#flags()
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public enum URIFlags {
-    
+
+    /**
+     * URI can be used for reading.
+     * It should support method like "istream", "reader" and "content".
+     * Please note, that "istream(offset)" is supported only if handler reports SEEK
+     */
     READABLE    (0x0000_0001),
+    /**
+     * URI can be used for writing.
+     * It should support method like "ostream", "writer".
+     */
     WRITABLE    (0x0000_0002),
+    /**
+     * URI points to file with "executable" attribute.
+     * At this moment there is no support for execution by URI.
+     */
     EXECUTE     (0x0000_0004),
+    /**
+     * URI points to existing resource (local file, FTP file, HTTP resource).
+     */
     EXISTS      (0x0000_0008),
+    /**
+     * URI points to directory
+     */
     DIR         (0x0000_0010),
+    /**
+     * URI points to ordinary file
+     */
     FILE        (0x0000_0020),
+    /**
+     * URI points to text file (most probably with defined charset)
+     */
     TEXT        (0x0000_0040),
+    /**
+     * URI points to binary file (or text file with undefined charset)
+     */
     BINARY      (0x0000_0080),
+    /**
+     * URI resource has well defined size in bytes
+     */
     SIZE        (0x0000_0100),
+    /**
+     * URI resource supports opening input stream at specified offset
+     */
     SEEK        (0x0000_0200),
     ;
     
@@ -36,23 +74,53 @@ public enum URIFlags {
     URIFlags(int mask) {
         this.mask = mask;
     }
-    
-    public boolean matches(URIHandle ustream) throws IOException {
-        return ustream.flags().contains(this);
+
+    /**
+     * Returns true, if provided handle supports feature described by this enum.
+     *
+     * @param handle handle
+     * @return boolean
+     * @throws IOException on error
+     */
+    public boolean matches(URIHandle handle) throws IOException {
+        return handle.flags().contains(this);
     }
 
+    /**
+     * Returns immutable MaskSet with no features at all
+     *
+     * @return mask
+     */
     public static MaskSet<URIFlags> empty() {
         return MAP.empty();
     }
-    
+
+    /**
+     * Returns mutable set with specified features.
+     *
+     * @param flags flags
+     * @return mask
+     */
     public static MaskSet<URIFlags> collect(URIFlags... flags) {
         return MAP.collect(flags);
     }
-    
+
+    /**
+     * Returns immutable set with specified features.
+     *
+     * @param flags flags
+     * @return mask
+     */
     public static MaskSet<URIFlags> constant(URIFlags... flags) {
         return MAP.constant(flags);
     }
-    
+
+    /**
+     * Returns new mutable set with the same features as in provided set.
+     *
+     * @param flags flags
+     * @return mask
+     */
     public static MaskSet<URIFlags> clone(Set<URIFlags> flags) {
         return MAP.clone(flags);
     }

+ 317 - 27
assira.core/src/main/java/net/ranides/assira/io/uri/URIHandle.java

@@ -17,87 +17,377 @@ import java.net.URI;
 import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.time.Instant;
+import java.util.Map;
 import java.util.Set;
 import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.text.IOStrings;
 import net.ranides.assira.text.StringUtils;
 
 /**
+ * This is abstraction over URL, File and Path objects.
+ * URIHandle can point to any resource using any protocol (local file, HTTP, FTP).
+ *
+ * URIHandles are not created directly, but by {@link URIResolver} factory.
+ *
+ * Static methods in this interface use {@link URIResolver#DEFAULT}.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public interface URIHandle extends Closeable {
 
+    /**
+     * Uses default resolver to create new URIHandle pointing to specified location.
+     *
+     * @param location location
+     * @return URIHandle
+     * @throws IOException
+     *  if location has invalid format
+     *  or location has unsupported scheme
+     *  or URIHandler throws exception
+     */
     static URIHandle resolve(String location) throws IOException {
         return URIResolver.DEFAULT.resolve(location);
     }
-    
+
+    /**
+     * Uses default resolver to create new URIHandle pointing to specified location.
+     *
+     * @param location location
+     * @return URIHandle
+     * @throws IOException
+     *  if location has invalid format
+     *  or location has unsupported scheme
+     *  or URIHandler throws exception
+     */
     static URIHandle resolve(File location) throws IOException {
         return URIResolver.DEFAULT.resolve(location);
     }
-    
+
+    /**
+     * Uses default resolver to create new URIHandle pointing to specified location.
+     *
+     * @param location location
+     * @return URIHandle
+     * @throws IOException
+     *  if location has invalid format
+     *  or location has unsupported scheme
+     *  or URIHandler throws exception
+     */
     static URIHandle resolve(URI location) throws IOException {
         return URIResolver.DEFAULT.resolve(location);
     }
-    
+
+    /**
+     * Returns URIResolver factory which creates this handle
+     *
+     * @return URIResolver
+     */
     URIResolver resolver();
-    
-    URI uri() throws IOException;
-    
-    String scheme() throws IOException;
-    
+
+    /**
+     * Returns URI used to access to resource
+     *
+     * @return URI
+     */
+    URI uri();
+
+    /**
+     * Returns scheme used to access resource
+     *
+     * @return String
+     */
+    String scheme();
+
+    /**
+     * Returns file which points to resource.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @return File
+     * @throws IOException on error
+     */
     File file() throws IOException;
-    
+
+    /**
+     * Returns path which points to resource.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @return Path
+     * @throws IOException on error
+     */
     Path path() throws IOException;
-    
+
+    /**
+     * Returns charset defined for resource.
+     * Many resource types return UTF8 by default.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return Charset
+     * @throws IOException on error
+     */
     Charset charset() throws IOException;
-    
+
+    /**
+     * Returns attributes of this resource.
+     * Many resource types return at least EXISTS and READ for correct URIs.
+     *
+     * This method may make I/O calls.
+     *
+     * @return URIFlags
+     * @throws IOException on error
+     */
     Set<URIFlags> flags() throws IOException;
-    
+
+    /**
+     * Returns true if this handle points to existing resource.
+     *
+     * This method may make I/O calls.
+     *
+     * @return boolean
+     * @throws IOException on error
+     */
     boolean exists() throws IOException;
-    
+
+    /**
+     * Returns requested timestamp of resource.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param ut ut
+     * @return Instant
+     * @throws IOException on error
+     */
     Instant time(URITime ut) throws IOException;
 
+    // @todo #86
+    // Map<URITime, Instant> time() throws IOException;
+
+    /**
+     * Returns size of this resource, in bytes.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return long
+     * @throws IOException on error
+     */
     long size() throws IOException;
-    
+
+    /**
+     * Returns handle to parent resource (most probably parent directory).
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @return handle
+     * @throws IOException on error
+     */
     URIHandle parent() throws IOException;
-    
+
+    /**
+     * Assumes that resource is directory and returns CQuery with all resources inside it.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return CQuery
+     * @throws IOException on error
+     */
     CQuery<URIHandle> scan() throws IOException;
-    
+
+    /**
+     * Assumes that resource is ordinary file and returns byte-stream opened for reading.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return stream
+     * @throws IOException on error
+     */
     InputStream istream() throws IOException;
-    
+
+    /**
+     * Assumes that resource is ordinary file and returns byte-stream opened for reading.
+     * Starts reading from specified offset.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param offset offset
+     * @return stream
+     * @throws IOException on error
+     */
     InputStream istream(long offset) throws IOException;
-    
+
+    /**
+     * Assumes that resource is ordinary file and returns byte-stream opened for reading.
+     * Starts reading from specified offset and terminates after "size" bytes.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param offset offset
+     * @param size size
+     * @return stream
+     * @throws IOException on error
+     */
     InputStream istream(long offset, long size) throws IOException;
-    
+
+    /**
+     * Assumes that resource is ordinary file and returns byte-stream opened for writing.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return stream
+     * @throws IOException on error
+     */
     OutputStream ostream() throws IOException;
-    
+
+    /**
+     * Assumes that resource is text file and returns char-stream opened for reading.
+     * Uses {@link #charset()} method for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return reader
+     * @throws IOException on error
+     */
     Reader reader() throws IOException;
 
+    /**
+     * Assumes that resource is text file and returns char-stream opened for reading.
+     * Uses provided charset for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param cs cs
+     * @return reader
+     * @throws IOException on error
+     */
     Reader reader(Charset cs) throws IOException;
-    
+
+    /**
+     * Assumes that resource is text file and returns char-stream opened for writing.
+     * Uses {@link #charset()} method for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return writer
+     * @throws IOException on error
+     */
     Writer writer() throws IOException;
-    
+
+    /**
+     * Assumes that resource is text file and returns char-stream opened for writing.
+     * Uses provided charset for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param cs cs
+     * @return writer
+     * @throws IOException on error
+     */
     Writer writer(Charset cs) throws IOException;
 
+    /**
+     * Assumes that resource is text file and returns its content as String.
+     * Uses {@link #charset()} method for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @return string
+     * @throws IOException on error
+     */
     default String content() throws IOException {
         return IOStrings.read(reader());
     }
 
+    /**
+     * Assumes that resource is text file and returns its content as String.
+     * Uses provided charset for character encoding.
+     *
+     * This method is optional: may throw IOException if not supported.
+     * This method may make I/O calls.
+     *
+     * @param cs cs
+     * @return string
+     * @throws IOException on error
+     */
     default String content(Charset cs) throws IOException {
         return IOStrings.read(reader(cs));
     }
 
+    /**
+     * Filesystem I/O operation: removes this resource.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @throws IOException on error
+     */
     void delete() throws IOException;
-    
+
+    /**
+     * Filesystem I/O operation: creates resource specified by URI inside this handle.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @throws IOException on error
+     */
     void create() throws IOException;
-    
+
+    /**
+     * Filesystem I/O operation: changes location of this resource.
+     *
+     * Please note, that "target" must use public format supported by URIHandle,
+     * not internal path (for example local path on FTP server).
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @param target target
+     * @throws IOException on error
+     */
     void move(String target) throws IOException;
-    
+
+    /**
+     * Filesystem I/O operation: changes location of this resource.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @param target target
+     * @throws IOException on error
+     */
     void move(URIHandle target) throws IOException;
-    
+
+    /**
+     * Filesystem I/O operation: copy this resource into new location.
+     *
+     * Please note, that "target" must use public format supported by URIHandle,
+     * not internal path (for example local path on FTP server).
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @param target target
+     * @throws IOException on error
+     */
     void copy(String target) throws IOException;
-    
+
+    /**
+     * Filesystem I/O operation: copy this resource into new location.
+     *
+     * This method is optional: may throw IOException if not supported.
+     *
+     * @param target target
+     * @throws IOException on error
+     */
     void copy(URIHandle target) throws IOException;
     
 }

+ 24 - 2
assira.core/src/main/java/net/ranides/assira/io/uri/URIHandler.java

@@ -11,13 +11,35 @@ import java.net.URI;
 import java.util.Collection;
 
 /**
+ * URIHandler interface should be implemented by classes which want to add suport for
+ * specific URI schemes into URIResolver.
+ *
+ * URIHandler subclasses can register itself inside "META-INF/services"
+ *
+ * URIHandler subclasses can be registered manually by calling "register" method on resolver.
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public interface URIHandler {
-    
+
+    /**
+     * Creates new handle for specified URI.
+     *
+     * It is preferred to avoid I/O calls, but not obligatory.
+     *
+     * @param uri uri
+     * @return handle
+     * @throws IOException on error
+     */
     URIHandle resolve(URI uri) throws IOException;
-    
+
+    /**
+     * Returns immutable collection of all scheme prefixes supported by this handler.
+     *
+     * Scheme prefixes shouldn't inlcude ":" character.
+     *
+     * @return schemes
+     */
     Collection<String> schemes();
     
 }

+ 46 - 5
assira.core/src/main/java/net/ranides/assira/io/uri/URITime.java

@@ -16,21 +16,62 @@ import java.time.Instant;
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
 public enum URITime {
-    
-    CREATED, 
+
+    /**
+     * Time of creation
+     */
+    CREATED,
+    /**
+     * Time of last modification of content
+     */
     MODIFIED,
+    /**
+     * Time of last access
+     */
     ACCESSED,
+    /**
+     * Time of last modification of meta-data
+     */
     META_MODIFIED,
     ;
-    
+
+    /**
+     * Returns timestamp for provided resource.
+     *
+     * This method makes a call to {@link URIHandle#time(URITime)}.
+     *
+     * @param ustream ustream
+     * @return Instant
+     * @throws IOException on error
+     */
     public Instant resolve(URIHandle ustream) throws IOException {
         return ustream.time(this);
     }
-    
+
+    /**
+     * Returns timestamp for provided resource.
+     *
+     * This method uses default resolver to create handle.
+     * This method makes a call to {@link URIHandle#time(URITime)}.
+     *
+     * @param file file
+     * @return Instant
+     * @throws IOException on error
+     */
     public Instant resolve(File file) throws IOException {
         return resolve(URIHandle.resolve(file));
     }
-    
+
+    /**
+     * Returns timestamp for provided resource.
+     *
+     * This method uses default resolver to create handle.
+     * This method makes a call to {@link URIHandle#time(URITime)}.
+     *
+     * @param uri uri
+     * @return Instant
+     * @throws IOException on error
+     */
     public Instant resolve(URI uri) throws IOException {
         return resolve(URIHandle.resolve(uri));
     }

+ 2 - 1
assira.core/src/main/java/net/ranides/assira/io/uri/impl/CFileHandler.java

@@ -152,9 +152,10 @@ public class CFileHandler implements URIHandler {
             try {
                 BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
 
+                flags.add(URIFlags.EXISTS);
+
                 if (attr.isDirectory()) {
                     flags.add(URIFlags.DIR);
-                    flags.add(URIFlags.EXISTS);
                 }
                 if (attr.isRegularFile()) {
                     flags.add(URIFlags.FILE);

+ 1 - 1
assira.core/src/main/java/net/ranides/assira/io/uri/impl/CHandle.java

@@ -35,7 +35,7 @@ public abstract class CHandle implements URIHandle {
     private static final String NSE = "Not supported by URIHandler: ";
 
     @Override
-    public String scheme() throws IOException {
+    public String scheme() {
         return uri().getScheme();
     }
 

+ 3 - 15
assira.core/src/test/java/net/ranides/assira/io/uri/impl/CClassPathHandlerTest.java

@@ -79,27 +79,15 @@ public class CClassPathHandlerTest {
     }
 
     private void printme(URIHandle handler) {
-        try {
-            System.out.println(" - " + handler.uri());
-        } catch (IOException e) {
-            throw ExceptionUtils.rethrow(e);
-        }
+        System.out.println(" - " + handler.uri());
     }
 
     private String filename(URIHandle f) {
-        try {
-            return StringUtils.after(f.uri().toString(), "/target");
-        } catch (IOException cause) {
-            throw ExceptionUtils.rethrow(cause);
-        }
+        return StringUtils.after(f.uri().toString(), "/target");
     }
 
     private String jarname(URIHandle f) {
-        try {
-            return f.uri().toString().split("!/")[1];
-        } catch (IOException cause) {
-            throw ExceptionUtils.rethrow(cause);
-        }
+        return f.uri().toString().split("!/")[1];
     }
 
 }