[from now] 2014/08/01 16:44:08

diff --git a/src/main/groovy/gaiden/BindingBuilder.groovy b/src/main/groovy/gaiden/BindingBuilder.groovy
index c482f12..40264b3 100644
--- a/src/main/groovy/gaiden/BindingBuilder.groovy
+++ b/src/main/groovy/gaiden/BindingBuilder.groovy
@@ -17,29 +17,36 @@
 package gaiden

 import gaiden.context.PageBuildContext
-import gaiden.util.FileUtils
+import groovy.transform.CompileStatic
+
+import java.nio.file.Path
+import java.nio.file.Paths

 /**
  * Builder for binding to be passed to a template engine.
  *
  * @author Kazuki YAMAMOTO
  */
+@CompileStatic
 class BindingBuilder {

     private static final String EMPTY_STRING = ""

     private String title
-    private String tocOutputFilePath
+    private Path tocOutputFilePath

     private String content
-    private String sourcePath
-    private String outputPath
+    private Path sourcePath
+    private Path outputPath

     private PageBuildContext pageBuildContext

-    BindingBuilder(String title, String tocOutputFilePath) {
+    private PageReferenceFactory pageReferenceFactory
+
+    BindingBuilder(String title, Path tocOutputFilePath, PageReferenceFactory pageReferenceFactory) {
         this.title = title
         this.tocOutputFilePath = tocOutputFilePath
+        this.pageReferenceFactory = pageReferenceFactory
     }

     /**
@@ -69,7 +76,7 @@ BindingBuilder setContent(String content) {
     /**
      * Sets the output path.
      */
-    BindingBuilder setOutputPath(String outputPath) {
+    BindingBuilder setOutputPath(Path outputPath) {
         this.outputPath = outputPath
         this
     }
@@ -77,7 +84,7 @@ BindingBuilder setOutputPath(String outputPath) {
     /**
      * Sets the page path.
      */
-    BindingBuilder setSourcePath(String sourcePath) {
+    BindingBuilder setSourcePath(Path sourcePath) {
         this.sourcePath = sourcePath
         this
     }
@@ -95,7 +102,7 @@ private String getPrevLink() {
             return EMPTY_STRING
         }

-        def previousTocNode = pageBuildContext.toc.findTocNode(new PageReference(sourcePath))?.previous
+        def previousTocNode = pageBuildContext.toc.findTocNode(pageReferenceFactory.get(sourcePath))?.previous
         if (!previousTocNode) {
             return EMPTY_STRING
         }
@@ -104,10 +111,11 @@ private String getPrevLink() {
             return "<< $previousTocNode.title".encodeAsHtml()
         }

-        def relativePath = FileUtils.getRelativePathForFileToFile(outputPath, previousTocNode.pageSource.outputPath)
+
+        def relativePath = outputPath.relativize(previousTocNode.pageSource.outputPath)

         def sb = new StringBuilder()
-        sb << "<a href='${relativePath + previousTocNode.pageReference.fragment}' class='prev'>"
+        sb << "<a href='${relativePath + previousTocNode.pageReference.hash}' class='prev'>"
         sb << "<< $previousTocNode.title".encodeAsHtml()
         sb << "</a>"
         return sb.toString()
@@ -118,7 +126,7 @@ private String getNextLink() {
             return EMPTY_STRING
         }

-        def nextTocNode = pageBuildContext.toc.findTocNode(new PageReference(sourcePath))?.next
+        def nextTocNode = pageBuildContext.toc.findTocNode(pageReferenceFactory.get(sourcePath))?.next
         if (!nextTocNode) {
             return EMPTY_STRING
         }
@@ -127,10 +135,10 @@ private String getNextLink() {
             return "$nextTocNode.title >>".encodeAsHtml()
         }

-        def relativePath = FileUtils.getRelativePathForFileToFile(outputPath, nextTocNode.pageSource.outputPath)
+        def relativePath = outputPath.relativize(nextTocNode.pageSource.outputPath)

         def sb = new StringBuilder()
-        sb << "<a href='${relativePath + nextTocNode.pageReference.fragment}' class='next'>"
+        sb << "<a href='${relativePath + nextTocNode.pageReference.hash}' class='next'>"
         sb << "$nextTocNode.title >>".encodeAsHtml()
         sb << "</a>"
         return sb.toString()
@@ -138,15 +146,15 @@ private String getNextLink() {

     private Closure getResourceMethod() {
         return { String resourceFilePath ->
-            if (!resourceFilePath.startsWith("/")) {
+            def resourceFile = Paths.get(resourceFilePath)
+            if (!resourceFile.absolute) {
                 return resourceFilePath
             }
-            FileUtils.getRelativePathForFileToFile(outputPath, resourceFilePath)
+            outputPath.relativize(resourceFile).toString()
         }
     }

     private String getTocPath() {
-        FileUtils.getRelativePathForFileToFile(outputPath, tocOutputFilePath)
+        outputPath.relativize(tocOutputFilePath).toString()
     }
-
 }
diff --git a/src/main/groovy/gaiden/DocumentWriter.groovy b/src/main/groovy/gaiden/DocumentWriter.groovy
index 098d116..9184fee 100644
--- a/src/main/groovy/gaiden/DocumentWriter.groovy
+++ b/src/main/groovy/gaiden/DocumentWriter.groovy
@@ -16,11 +16,14 @@

 package gaiden

+import gaiden.util.PathUtils
 import groovy.transform.CompileStatic
-import org.apache.commons.io.FileUtils
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Component

+import java.nio.file.Files
+import java.nio.file.Path
+

 /**
  * A document writer writes a {@link Document} to files.
@@ -41,8 +44,8 @@
      * @param document the document to be written
      */
     void write(Document document) {
-        if (!gaidenConfig.outputDirectory.exists()) {
-            assert gaidenConfig.outputDirectory.mkdirs()
+        if (Files.notExists(gaidenConfig.outputDirectory)) {
+            Files.createDirectories(gaidenConfig.outputDirectory)
         }

         writePages(document.pages)
@@ -50,7 +53,7 @@ void write(Document document) {

         copyStaticFiles()

-        println "Built document at ${gaidenConfig.outputDirectory.canonicalPath}"
+        println "Built document at ${gaidenConfig.outputDirectory.toAbsolutePath()}"
     }

     private void writePages(List<Page> pages) {
@@ -66,16 +69,15 @@ private void writeToc(Toc toc) {
         writeToFile(toc.path, toc.content)
     }

-    private void writeToFile(String path, String content) {
-        def file = new File(gaidenConfig.outputDirectory, path as String)
-        if (!file.parentFile.exists()) {
-            assert file.parentFile.mkdirs()
+    private void writeToFile(Path path, String content) {
+        if (Files.notExists(path.parent)) {
+            Files.createDirectories(path.parent)
         }
-        file.write(content, gaidenConfig.outputEncoding)
+
+        path.write(content, gaidenConfig.outputEncoding)
     }

     private void copyStaticFiles() {
-        FileUtils.copyDirectory(gaidenConfig.staticDirectory, gaidenConfig.outputDirectory)
+        PathUtils.copyFiles(gaidenConfig.staticDirectory, gaidenConfig.outputDirectory)
     }
-
 }
diff --git a/src/main/groovy/gaiden/GaidenConfig.groovy b/src/main/groovy/gaiden/GaidenConfig.groovy
index 338440b..7d0d9fe 100644
--- a/src/main/groovy/gaiden/GaidenConfig.groovy
+++ b/src/main/groovy/gaiden/GaidenConfig.groovy
@@ -118,6 +118,10 @@ Path getDefaultTemplateDirectory() {
         projectDirectory.resolve("template")
     }

+    Path getTocOutputFile() {
+        Paths.get(tocOutputFilePath)
+    }
+
     Path getGaidenConfigFile() {
         userDirectory.resolve(CONFIG_PATH)
     }
diff --git a/src/main/groovy/gaiden/GaidenMain.groovy b/src/main/groovy/gaiden/GaidenMain.groovy
index abfcdf4..39165d3 100644
--- a/src/main/groovy/gaiden/GaidenMain.groovy
+++ b/src/main/groovy/gaiden/GaidenMain.groovy
@@ -61,7 +61,7 @@ protected void executeCommand(String... args) {
     }

     @CompileStatic(TypeCheckingMode.SKIP)
-    private String getCommandName(String[] args, OptionAccessor options) {
+    private static String getCommandName(String[] args, OptionAccessor options) {
         if (options.v) {
             return "version"
         } else if (args) {
diff --git a/src/main/groovy/gaiden/Page.groovy b/src/main/groovy/gaiden/Page.groovy
index 7094c73..d5a2f1d 100644
--- a/src/main/groovy/gaiden/Page.groovy
+++ b/src/main/groovy/gaiden/Page.groovy
@@ -18,6 +18,8 @@

 import groovy.transform.CompileStatic

+import java.nio.file.Path
+
 /**
  * A holder for a content and the meta information.
  *
@@ -36,8 +38,7 @@
     /**
      * Returns a relative path from the output directory.
      */
-    String getPath() {
+    Path getPath() {
         source.outputPath
     }
-
 }
diff --git a/src/main/groovy/gaiden/PageBuilder.groovy b/src/main/groovy/gaiden/PageBuilder.groovy
index 2a9ad93..4aba7e7 100644
--- a/src/main/groovy/gaiden/PageBuilder.groovy
+++ b/src/main/groovy/gaiden/PageBuilder.groovy
@@ -41,6 +41,9 @@
     @Autowired
     GaidenConfig gaidenConfig

+    @Autowired
+    PageReferenceFactory pageReferenceFactory
+
     /**
      * Build from a page source to a page.
      *
@@ -58,7 +61,7 @@ Page build(PageBuildContext context, PageSource pageSource) {
     private String buildPage(PageBuildContext context, PageSource pageSource) {
         def content = markdownProcessor.markdownToHtml(context, pageSource)

-        def binding = new BindingBuilder(gaidenConfig.title, gaidenConfig.tocOutputFilePath)
+        def binding = new BindingBuilder(gaidenConfig.title, gaidenConfig.tocOutputFile, pageReferenceFactory)
             .setContent(content)
             .setPageBuildContext(context)
             .setOutputPath(pageSource.outputPath)
diff --git a/src/main/groovy/gaiden/PageReference.groovy b/src/main/groovy/gaiden/PageReference.groovy
index 9be86af..e4f279a 100644
--- a/src/main/groovy/gaiden/PageReference.groovy
+++ b/src/main/groovy/gaiden/PageReference.groovy
@@ -16,9 +16,9 @@

 package gaiden

-import gaiden.util.FileUtils
 import groovy.transform.CompileStatic
-import groovy.transform.TypeCheckingMode
+
+import java.nio.file.Path

 /**
  * A reference of page.
@@ -28,84 +28,87 @@
 @CompileStatic
 class PageReference {

-    /**
-     * The relative path from the pages directory.
-     * <p>
-     * The path doesn't contain a fragment(#).
-     */
-    String path
-
-    /** The fragment of path */
-    String fragment
-
-    @CompileStatic(TypeCheckingMode.SKIP)
-    PageReference(String url) {
-        (path, fragment) = url.split("#") as List
-        fragment = fragment ? "#${fragment}" : ""
-    }
-
-    /**
-     * Returns the abstract relative path from the pages directory.
-     * <p>
-     * The pass doesn't contain a file extension.
-     *
-     * @return the abstract relative path
-     */
-    String getAbstractPath() {
-        FileUtils.removeExtension(path)
-    }
-
-    /**
-     * Returns the extension of filename.
-     *
-     * @return the extension of filename
-     */
-    String getExtension() {
-        FileUtils.getExtension(new File(path).name)
-    }
-
-    /**
-     * Checks the path is URL.
-     *
-     * @return {@code true} if the path is URL
-     */
-    boolean isUrl() {
-        try {
-            new URL(path)
-        } catch (MalformedURLException e) {
-            return false
-        }
-        true
-    }
-
-    /**
-     * Checks the path is an absolute path.
-     *
-     * @return {@code true} if the path is an absolute path
-     */
-    boolean isAbsolutePath() {
-        !path.startsWith("/")
-    }
-
-    /**
-     * Checks the extension of filename is a page source extension.
-     *
-     * @return {@code true} if the extension of filename is a page source extension
-     * @see SourceCollector#PAGE_SOURCE_EXTENSIONS
-     */
-    boolean isPageSourceExtension() {
-        !extension || extension in SourceCollector.PAGE_SOURCE_EXTENSIONS
-    }
+    Path path
+    String hash

-    /**
-     * Converts this page reference into a page reference which has a full path.
-     *
-     * @param rootDirectory the root directory of page sources
-     * @param baseDirectory the directory of base
-     * @return the page reference which has a full path
-     */
-    PageReference toFullPathPageReference(File rootDirectory, File baseDirectory) {
-        assert isAbsolutePath()
-        new PageReference(FileUtils.getRelativePathForDirectoryToFile(rootDirectory, new File(baseDirectory, path)) + fragment)
-    }
+//    /**
+//     * The relative path from the pages directory.
+//     * <p>
+//     * The path doesn't contain a fragment(#).
+//     */
+//    String path
+//
+//    /** The fragment of path */
+//    String fragment
+//
+//    @CompileStatic(TypeCheckingMode.SKIP)
+//    PageReference(String url) {
+//        (path, fragment) = url.split("#") as List
+//        fragment = fragment ? "#${fragment}" : ""
+//    }
+//
+//    /**
+//     * Returns the abstract relative path from the pages directory.
+//     * <p>
+//     * The pass doesn't contain a file extension.
+//     *
+//     * @return the abstract relative path
+//     */
+//    String getAbstractPath() {
+//        FileUtils.removeExtension(path)
+//    }
+//
+//    /**
+//     * Returns the extension of filename.
+//     *
+//     * @return the extension of filename
+//     */
+//    String getExtension() {
+//        FileUtils.getExtension(new File(path).name)
+//    }
+//
+//    /**
+//     * Checks the path is URL.
+//     *
+//     * @return {@code true} if the path is URL
+//     */
+//    boolean isUrl() {
+//        try {
+//            new URL(path)
+//        } catch (MalformedURLException e) {
+//            return false
+//        }
+//        true
+//    }
+//
+//    /**
+//     * Checks the path is an absolute path.
+//     *
+//     * @return {@code true} if the path is an absolute path
+//     */
+//    boolean isAbsolutePath() {
+//        !path.startsWith("/")
+//    }
+//
+//    /**
+//     * Checks the extension of filename is a page source extension.
+//     *
+//     * @return {@code true} if the extension of filename is a page source extension
+//     * @see SourceCollector#PAGE_SOURCE_EXTENSIONS
+//     */
+//    boolean isPageSourceExtension() {
+//        !extension || extension in SourceCollector.PAGE_SOURCE_EXTENSIONS
+//    }
+//
+//    /**
+//     * Converts this page reference into a page reference which has a full path.
+//     *
+//     * @param rootDirectory the root directory of page sources
+//     * @param baseDirectory the directory of base
+//     * @return the page reference which has a full path
+//     */
+//    PageReference toFullPathPageReference(File rootDirectory, File baseDirectory) {
+//        assert isAbsolutePath()
+//        new PageReference(FileUtils.getRelativePathForDirectoryToFile(rootDirectory, new File(baseDirectory, path)) + fragment)
+//    }
 }
diff --git a/src/main/groovy/gaiden/PageReferenceFactory.groovy b/src/main/groovy/gaiden/PageReferenceFactory.groovy
new file mode 100644
index 0000000..75706ed
--- /dev/null
+++ b/src/main/groovy/gaiden/PageReferenceFactory.groovy
@@ -0,0 +1,77 @@
+package gaiden
+
+import groovy.transform.CompileStatic
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Component
+
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+
+@Component
+@CompileStatic
+class PageReferenceFactory {
+
+    @Autowired
+    GaidenConfig gaidenConfig
+
+    PageReference get(Path path, PageSource basePageSource = null) {
+        return new PageReference(
+            path: getPath(path, basePageSource),
+            hash: getHash(path),
+        )
+    }
+
+    private Path getPath(Path original, PageSource basePageSource) {
+        def basePath = getBasePath(original)
+
+        if (basePath.absolute) {
+            return getCanonicalPath(gaidenConfig.pagesDirectory.resolve(basePath))
+        }
+
+        if (!basePageSource) {
+            // TODO warning log
+            return basePath
+        }
+
+        return getCanonicalPath(basePageSource.path.resolve(basePath).toAbsolutePath())
+    }
+
+    private static Path getCanonicalPath(Path path) {
+        if (Files.exists(path)) {
+            return path
+        }
+
+        def found = SourceCollector.PAGE_SOURCE_EXTENSIONS.collect { String extension ->
+
+            Paths.get("${path.toString()}.${extension}")
+        }.find { Path pathWithExtension ->
+            Files.exists(pathWithExtension)
+        }
+        if (found) {
+            return found
+        }
+
+        return path
+    }
+
+    private static String getHash(Path path) {
+        def filename = path.fileName.toString()
+        def hashIndex = filename.lastIndexOf("#")
+
+        if (hashIndex == -1) {
+            return ""
+        }
+        filename.substring(hashIndex)
+    }
+
+    private static Path getBasePath(Path path) {
+        def filename = path.fileName.toString()
+        def hashIndex = filename.lastIndexOf("#")
+
+        if (hashIndex == -1) {
+            return path
+        }
+        path.parent.resolveSibling(filename.substring(0, hashIndex))
+    }
+}
diff --git a/src/main/groovy/gaiden/PageSource.groovy b/src/main/groovy/gaiden/PageSource.groovy
index f5a891c..57db319 100644
--- a/src/main/groovy/gaiden/PageSource.groovy
+++ b/src/main/groovy/gaiden/PageSource.groovy
@@ -16,7 +16,6 @@

 package gaiden

-import gaiden.util.FileUtils
 import groovy.transform.CompileStatic

 import java.nio.file.Path
@@ -30,47 +29,14 @@
 @CompileStatic
 class PageSource {

-    private static final String OUTPUT_EXTENSION = "html"
-
     /** A relative path from the pages directory */
     Path path

-    /** A content of source */
-    String content
-
-    /**
-     * Returns a output page path.
-     *
-     * @return a output page path
-     */
-    String getOutputPath() {
-        FileUtils.replaceExtension(path, OUTPUT_EXTENSION)
-    }
+    /** A output page path */
+    Path outputPath

-    /**
-     * Returns the abstract relative path from the pages directory.
-     * <p>
-     * The path doesn't contain a file extension.
-     *
-     * @return the abstract relative path
-     */
-    String getAbstractPath() {
-        FileUtils.removeExtension(path)
-    }
-
-    /**
-     * Checks the path matches the specified page reference.
-     *
-     * @param pageReference the page reference
-     * @return {@code true} if the path matches the specified page reference
-     */
-    boolean matches(PageReference pageReference) {
-        if (pageReference.extension) {
-            def pageReferencePath = pageReference.path.replaceFirst("^/", "")
-            path == pageReferencePath || outputPath == pageReferencePath
-        } else {
-            def pageReferencePath = pageReference.abstractPath.replaceFirst("^/", "")
-            abstractPath == pageReferencePath
-        }
+    /** A content of source */
+    String getContent() {
+        path.text
     }
 }
diff --git a/src/main/groovy/gaiden/SourceCollector.groovy b/src/main/groovy/gaiden/SourceCollector.groovy
index d01e043..bafa8bc 100644
--- a/src/main/groovy/gaiden/SourceCollector.groovy
+++ b/src/main/groovy/gaiden/SourceCollector.groovy
@@ -16,11 +16,13 @@

 package gaiden

-import gaiden.util.FileUtils
 import groovy.transform.CompileStatic
+import org.apache.commons.io.FilenameUtils
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Component

+import java.nio.file.Path
+
 /**
  * A source collector collects {@link PageSource} and builds {@link DocumentSource}.
  *
@@ -47,23 +49,24 @@ DocumentSource collect() {

     private List<PageSource> collectPageSources() {
         def pageSources = []
-        gaidenConfig.pagesDirectory.eachFileRecurse { File file ->
-            if (isPageSourceFile(file)) {
-                pageSources << createPageSource(file)
+        gaidenConfig.pagesDirectory.eachFileRecurse { Path path ->
+            if (isPageSourceFile(path)) {
+                pageSources << createPageSource(path)
             }
         }
         pageSources
     }

-    private boolean isPageSourceFile(File file) {
-        file.name ==~ /.*\.(?:${PAGE_SOURCE_EXTENSIONS.join('|')})/
+    private static boolean isPageSourceFile(Path path) {
+        FilenameUtils.getExtension(path.toString()) in PAGE_SOURCE_EXTENSIONS
     }

-    private PageSource createPageSource(File file) {
+    private PageSource createPageSource(Path path) {
+        def relativePath = gaidenConfig.pagesDirectory.relativize(path)
+        def outputPath = gaidenConfig.outputDirectory.resolve(relativePath)
         new PageSource(
-            path: FileUtils.getRelativePathForDirectoryToFile(gaidenConfig.pagesDirectory, file),
-            content: file.getText(gaidenConfig.inputEncoding),
+            path: path,
+            outputPath: outputPath,
         )
     }
-
 }
diff --git a/src/main/groovy/gaiden/Toc.groovy b/src/main/groovy/gaiden/Toc.groovy
index 1247df6..c1dd0e3 100644
--- a/src/main/groovy/gaiden/Toc.groovy
+++ b/src/main/groovy/gaiden/Toc.groovy
@@ -18,6 +18,8 @@

 import groovy.transform.CompileStatic

+import java.nio.file.Path
+
 /**
  * A table of contents.
  *
@@ -34,7 +36,7 @@
     List<TocNode> tocNodes

     /** A relative path from the output directory */
-    String path
+    Path path

     /** A output content */
     String content
diff --git a/src/main/groovy/gaiden/TocNode.groovy b/src/main/groovy/gaiden/TocNode.groovy
index d4cb5a1..3edc573 100644
--- a/src/main/groovy/gaiden/TocNode.groovy
+++ b/src/main/groovy/gaiden/TocNode.groovy
@@ -18,6 +18,8 @@

 import groovy.transform.CompileStatic

+import java.nio.file.Path
+
 /**
  * A node of TOC.
  *
@@ -27,7 +29,7 @@
 class TocNode {

     /** The path of the page source */
-    String path
+    Path path
     /** The title of the page */
     String title

@@ -44,12 +46,5 @@
     /** The {@code List} of TOC nodes children */
     List<TocNode> children

-    /**
-     * Returns The page reference of the path.
-     *
-     * @return The page reference
-     */
-    PageReference getPageReference() {
-        new PageReference(path)
-    }
+    PageReference pageReference
 }
diff --git a/src/main/groovy/gaiden/markdown/GaidenLinkRenderer.groovy b/src/main/groovy/gaiden/markdown/GaidenLinkRenderer.groovy
index 374ef07..0919bfd 100644
--- a/src/main/groovy/gaiden/markdown/GaidenLinkRenderer.groovy
+++ b/src/main/groovy/gaiden/markdown/GaidenLinkRenderer.groovy
@@ -16,12 +16,14 @@

 package gaiden.markdown

-import gaiden.PageReference
+import gaiden.PageReferenceFactory
 import gaiden.PageSource
+import gaiden.SourceCollector
 import gaiden.context.PageBuildContext
 import gaiden.message.MessageSource
-import gaiden.util.FileUtils
+import gaiden.util.UrlUtils
 import groovy.transform.CompileStatic
+import org.apache.commons.io.FilenameUtils
 import org.pegdown.LinkRenderer
 import org.pegdown.ast.ExpLinkNode
 import org.pegdown.ast.RefLinkNode
@@ -43,12 +45,14 @@
     private PageSource pageSource
     private Path pagesDirectory
     private MessageSource messageSource
+    private PageReferenceFactory pageReferenceFactory

-    GaidenLinkRenderer(PageBuildContext context, PageSource pageSource, Path pagesDirectory, MessageSource messageSource) {
+    GaidenLinkRenderer(PageBuildContext context, PageSource pageSource, Path pagesDirectory, MessageSource messageSource, PageReferenceFactory pageReferenceFactory) {
         this.context = context
         this.pageSource = pageSource
         this.pagesDirectory = pagesDirectory
         this.messageSource = messageSource
+        this.pageReferenceFactory = pageReferenceFactory
     }

     @Override
@@ -62,30 +66,20 @@
     }

     protected LinkRenderer.Rendering render(String url, String title, String text) {
-        def pageRef = new PageReference(url)
-
-        if (pageRef.isUrl() || !pageRef.isPageSourceExtension()) {
+        if (UrlUtils.isUrl(url) || isPageSourceExtension(url)) {
             return createRendering(url, title, text)
         }

-        def targetPageSource = findPageSource(pageRef)
+        def pageReference = pageReferenceFactory.get(url, pageSource)
+
+        def targetPageSource = context.documentSource.findPageSource(pageReference)
         if (!targetPageSource) {
             System.err.println("WARNING: " + messageSource.getMessage("output.page.reference.not.exists.message", [url, pageSource.path]))
             return createRendering(url, title, text)
         }

-        def relativeUrl = FileUtils.getRelativePathForFileToFile(pageSource.outputPath, targetPageSource.outputPath)
-        return createRendering(relativeUrl + pageRef.fragment, title, text)
-    }
-
-    private PageSource findPageSource(PageReference pageRef) {
-        if (pageRef.isAbsolutePath()) {
-            def pageSourceParentDirectory = new File(pagesDirectory, pageSource.path).parentFile
-            def fullPathPageRef = pageRef.toFullPathPageReference(pagesDirectory, pageSourceParentDirectory)
-            context.documentSource.findPageSource(fullPathPageRef)
-        } else {
-            context.documentSource.findPageSource(pageRef)
-        }
+        def relativePath = pageSource.outputPath.relativize(targetPageSource.outputPath).toString()
+        return createRendering("${relativePath}${pageReference.hash}", title, text)
     }

     private LinkRenderer.Rendering createRendering(String url, String title, String text) {
@@ -93,4 +87,15 @@ private PageSource findPageSource(PageReference pageRef) {
         return title ? rendering.withAttribute("title", encode(title)) : rendering
     }

+    /**
+     * Checks the extension of filename is a page source extension.
+     *
+     * @return {@code true} if the extension of filename is a page source extension
+     * @see SourceCollector#PAGE_SOURCE_EXTENSIONS
+     */
+    private static boolean isPageSourceExtension(String path) {
+        def extension = FilenameUtils.getExtension(path)
+
+        !extension || extension in SourceCollector.PAGE_SOURCE_EXTENSIONS
+    }
 }
diff --git a/src/main/groovy/gaiden/markdown/GaidenMarkdownProcessor.groovy b/src/main/groovy/gaiden/markdown/GaidenMarkdownProcessor.groovy
index 688338d..39ef3c0 100644
--- a/src/main/groovy/gaiden/markdown/GaidenMarkdownProcessor.groovy
+++ b/src/main/groovy/gaiden/markdown/GaidenMarkdownProcessor.groovy
@@ -17,6 +17,7 @@
 package gaiden.markdown

 import gaiden.GaidenConfig
+import gaiden.PageReferenceFactory
 import gaiden.PageSource
 import gaiden.context.PageBuildContext
 import gaiden.message.MessageSource
@@ -43,6 +44,9 @@
     @Autowired
     MessageSource messageSource

+    @Autowired
+    PageReferenceFactory pageReferenceFactory
+
     GaidenMarkdownProcessor() {
         super(Extensions.ALL - Extensions.HARDWRAPS)
     }
@@ -58,9 +62,8 @@
     String markdownToHtml(PageBuildContext context, PageSource pageSource) throws ParsingTimeoutException {
         def astRoot = parseMarkdown(pageSource.content.toCharArray())

-        def linkRenderer = new GaidenLinkRenderer(context, pageSource, gaidenConfig.pagesDirectory, messageSource)
-        def imageRenderer = new ImageRenderer(pageSource, gaidenConfig.staticDirectory, messageSource)
+        def linkRenderer = new GaidenLinkRenderer(context, pageSource, gaidenConfig.pagesDirectory, messageSource, pageReferenceFactory)
+        def imageRenderer = new ImageRenderer(pageSource, gaidenConfig.staticDirectory, gaidenConfig.outputDirectory, messageSource)
         new GaidenToHtmlSerializer(linkRenderer, imageRenderer).toHtml(astRoot)
     }
-
 }
diff --git a/src/main/groovy/gaiden/markdown/ImageRenderer.groovy b/src/main/groovy/gaiden/markdown/ImageRenderer.groovy
index 4a33145..131855a 100644
--- a/src/main/groovy/gaiden/markdown/ImageRenderer.groovy
+++ b/src/main/groovy/gaiden/markdown/ImageRenderer.groovy
@@ -18,9 +18,13 @@

 import gaiden.PageSource
 import gaiden.message.MessageSource
-import gaiden.util.FileUtils
+import gaiden.util.UrlUtils
 import groovy.transform.CompileStatic

+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+
 /**
  * A Renderer for image node.
  *
@@ -31,10 +35,11 @@
 class ImageRenderer {

     private PageSource pageSource
-    private File staticDirectory
+    private Path staticDirectory
+    private Path outputDirectory
     private MessageSource messageSource

-    ImageRenderer(PageSource pageSource, File staticDirectory, MessageSource messageSource) {
+    ImageRenderer(PageSource pageSource, Path staticDirectory, Path outputDirectory, MessageSource messageSource) {
         this.pageSource = pageSource
         this.staticDirectory = staticDirectory
         this.messageSource = messageSource
@@ -60,33 +65,34 @@
      * @return {@link ImageElement}'s instance
      */
     ImageElement render(String imagePath, String alt) {
-        if (imagePath =~ /^\w+:\/\//) {  // URL
+        if (UrlUtils.isUrl(imagePath)) {  // URL
             return new ImageElement(src: imagePath, alt: alt)
         }

-        def relativeImagePath = getRelativePathForImageFileToOutputPageFile(imagePath)
+        def relativeImagePath = getRelativeImagePath(imagePath)
         new ImageElement(src: relativeImagePath, alt: alt)
     }

-    private String getRelativePathForImageFileToOutputPageFile(String imagePath) {
-        def staticOutputPageFile = new File(staticDirectory, pageSource.outputPath)
+    private String getRelativeImagePath(String imagePath) {
+        def imageFile = getImageFile(imagePath)
+        checkFileExists(imageFile, imagePath)
+
+        pageSource.outputPath.relativize(outputDirectory.resolve(staticDirectory.relativize(imageFile))).toString()
+    }

-        if (imagePath.startsWith("/")) {
-            def resourceFile = new File(staticDirectory, imagePath)
-            checkFileExists(resourceFile, imagePath)
-            return FileUtils.getRelativePathForFileToFile(staticOutputPageFile, resourceFile)
+    private Path getImageFile(String imagePath) {
+        def path = Paths.get(imagePath)
+        if (path.absolute) {
+            return staticDirectory.resolve(path)
         } else {
-            def resourceFile = new File(staticOutputPageFile.parentFile, imagePath)
-            checkFileExists(resourceFile, imagePath)
-            return imagePath
+            return pageSource.path.resolve(imagePath)
         }
     }

-    private void checkFileExists(File resourceFile, String imagePath) {
-        if (!resourceFile.canonicalFile.exists()) {
+    private void checkFileExists(Path imageFile, String imagePath) {
+        if (Files.notExists(imageFile)) {
             // It is required to normalize path such as '/path/to/../images/sample.png'.
             System.err.println("WARNING: " + messageSource.getMessage("image.reference.not.exists.message", [imagePath, pageSource.path]))
         }
     }
-
 }
diff --git a/src/main/groovy/gaiden/util/PathUtils.groovy b/src/main/groovy/gaiden/util/PathUtils.groovy
new file mode 100644
index 0000000..0c54209
--- /dev/null
+++ b/src/main/groovy/gaiden/util/PathUtils.groovy
@@ -0,0 +1,47 @@
+package gaiden.util
+
+import groovy.io.FileType
+import groovy.transform.CompileStatic
+import org.apache.commons.io.FilenameUtils
+
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+
+import static java.nio.file.StandardCopyOption.*
+
+/**
+ * {@link Path} utilities.
+ *
+ * @author Kazuki YAMAMOTO
+ */
+@CompileStatic
+class PathUtils {
+
+    /**
+     * Replaces the extension of filename.
+     *
+     * @param filename the filename
+     * @param extension the new extension
+     * @return the replaced filename
+     */
+    static Path replaceExtension(Path path, String extension) {
+        Paths.get("${FilenameUtils.removeExtension(path.toString())}.${extension}")
+    }
+
+    /**
+     * Removes the extension of filename.
+     *
+     * @param filename
+     * @return the removed filename
+     */
+    static Path removeExtension(Path path) {
+        Paths.get(FilenameUtils.removeExtension(path.toString()))
+    }
+
+    static void copyFiles(Path src, Path dest) {
+        src.eachFileRecurse(FileType.FILES) { Path file ->
+            Files.copy(file, dest.resolve(src.relativize(file)), REPLACE_EXISTING)
+        }
+    }
+}
diff --git a/src/main/groovy/gaiden/util/UrlUtils.groovy b/src/main/groovy/gaiden/util/UrlUtils.groovy
new file mode 100644
index 0000000..6be7e8a
--- /dev/null
+++ b/src/main/groovy/gaiden/util/UrlUtils.groovy
@@ -0,0 +1,21 @@
+package gaiden.util
+
+import groovy.transform.CompileStatic
+
+@CompileStatic
+class UrlUtils {
+
+    /**
+     * Checks the path is URL.
+     *
+     * @return {@code true} if the path is URL
+     */
+    static boolean isUrl(String url) {
+        try {
+            new URL(url)
+        } catch (MalformedURLException e) {
+            return false
+        }
+        true
+    }
+}