/scalate-util/src/main/scala/org/fusesource/scalate/util/Resource.scala

http://github.com/scalate/scalate · Scala · 275 lines · 111 code · 55 blank · 109 comment · 8 complexity · 64d78d08a1a6b1decd2d15c4af5cae30 MD5 · raw file

  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.util
  19. import io.Source
  20. import java.io._
  21. import java.net.{ URISyntaxException, URL }
  22. /**
  23. * Represents a string, file or URI based resource
  24. */
  25. trait Resource {
  26. /**
  27. * Returns the URI of the resource
  28. */
  29. def uri: String
  30. /**
  31. * Returns the text content of the resource
  32. */
  33. def text: String = IOUtil.loadText(inputStream)
  34. /**
  35. * Returns the reader of the content of the resource
  36. */
  37. def reader: Reader = new InputStreamReader(inputStream)
  38. /**
  39. * Returns the input stream of the content of the resource
  40. */
  41. def inputStream: InputStream
  42. /**
  43. * Returns the last modified time of the resource
  44. */
  45. def lastModified: Long
  46. def toFile: Option[File] = None
  47. }
  48. /**
  49. * Not all resources are writeable so this optional trait is for those
  50. */
  51. trait WriteableResource extends Resource {
  52. /**
  53. * Writes text to the resource replacing its previous content
  54. */
  55. def text_=(value: String): Unit = IOUtil.writeText(outputStream, value)
  56. /**
  57. * Returns the output stream of the resource
  58. */
  59. def outputStream: OutputStream
  60. /**
  61. * Returns the writer to the content of the resource
  62. */
  63. def writer: Writer = new OutputStreamWriter(outputStream)
  64. }
  65. abstract class TextResource extends Resource {
  66. override def reader = new StringReader(text)
  67. def inputStream = new ByteArrayInputStream(text.getBytes)
  68. // just return current time as we have no way to know
  69. def lastModified: Long = System.currentTimeMillis
  70. }
  71. case class StringResource(uri: String, override val text: String) extends TextResource
  72. case class UriResource(override val uri: String, resourceLoader: ResourceLoader) extends DelegateResource {
  73. protected def delegate = resourceLoader.resourceOrFail(uri)
  74. }
  75. /**
  76. * Can act as a RichFile type interface too, adding a number of extra helper methods to make Files more rich
  77. */
  78. case class FileResource(file: File, uri: String) extends WriteableResource {
  79. override def text = IOUtil.loadTextFile(file)
  80. override def reader = new FileReader(file)
  81. def inputStream = new FileInputStream(file)
  82. def outputStream = new FileOutputStream(file)
  83. def lastModified = file.lastModified
  84. /**
  85. * Create a child file
  86. */
  87. def /(name: String) = FileResource(new File(file, name), uri + "/" + name)
  88. implicit def asFile: File = file
  89. override def toFile = Some(file)
  90. def name = file.getName
  91. /**
  92. * Returns the extension of the file
  93. */
  94. def extension = Files.extension(name)
  95. /**
  96. * Returns the name of the file without its extension
  97. */
  98. def nameDropExtension = Files.dropExtension(name)
  99. /**
  100. * Recursively finds the first file in this directory that matches the given
  101. * predicate or matches against this file for non-directories
  102. */
  103. def recursiveFind(f: File => Boolean): Option[File] = Files.recursiveFind(file)(f)
  104. /**
  105. * Returns an Iterable over the immediate children of this directory
  106. * or an empty Iterable
  107. */
  108. def children: Iterable[File] = Files.children(file)
  109. /**
  110. * Returns an Iterable over any descending files
  111. */
  112. def descendants: Iterable[File] = Files.descendants(file)
  113. /**
  114. * Returns an Iterable over this file and any descending files
  115. */
  116. def andDescendants: Iterable[File] = Files.andDescendants(file)
  117. /**
  118. * Returns the relative URI of this file from the given root directory
  119. */
  120. def relativeUri(root: File) = Files.relativeUri(root, file)
  121. }
  122. object URLResource extends Log
  123. case class URLResource(url: URL) extends WriteableResource {
  124. import URLResource._
  125. def uri = url.toExternalForm
  126. lazy val connection = url.openConnection
  127. def inputStream = url.openStream
  128. def outputStream = connection.getOutputStream
  129. def lastModified = {
  130. val con = url.openConnection
  131. con.getLastModified
  132. }
  133. override def toFile: Option[File] = {
  134. var f: File = null
  135. if (url.getProtocol == "file") {
  136. try {
  137. try {
  138. f = new File(url.toURI)
  139. } catch {
  140. case e: URISyntaxException => f = new File(url.getPath)
  141. }
  142. } catch {
  143. case e: ThreadDeath => throw e
  144. case e: VirtualMachineError => throw e
  145. case e: Exception => debug(e, "While converting " + url + " to a File I caught: " + e)
  146. }
  147. }
  148. if (f != null && f.exists && f.isFile) {
  149. Some(f)
  150. } else {
  151. None
  152. }
  153. }
  154. }
  155. case class SourceResource(uri: String, source: Source) extends TextResource {
  156. override def text = {
  157. val builder = new StringBuilder
  158. val s: Source = source.pos match {
  159. case 0 => source
  160. case _ => {
  161. source.close()
  162. source.reset()
  163. }
  164. }
  165. for (c <- s) {
  166. builder.append(c)
  167. }
  168. builder.toString
  169. }
  170. }
  171. abstract class DelegateResource extends Resource {
  172. override def uri = delegate.uri
  173. override def text = delegate.text
  174. override def reader = delegate.reader
  175. override def inputStream = delegate.inputStream
  176. def lastModified = delegate.lastModified
  177. protected def delegate: Resource
  178. }
  179. /**
  180. * Helper methods to create a [[org.fusesource.scalate.support.Resource]] from various sources
  181. */
  182. object Resource {
  183. /**
  184. * Creates a [[org.fusesource.scalate.support.Resource]] from the actual String contents using the given
  185. * URI.
  186. *
  187. * The URI is used to determine the package name to put the template in along with
  188. * the template kind (using the extension of the URI)
  189. */
  190. def fromText(uri: String, templateText: String) = StringResource(uri, templateText)
  191. /**
  192. * Creates a [[org.fusesource.scalate.support.Resource]] from a local URI such as in a web application using the
  193. * class loader to resolve URIs to actual resources
  194. */
  195. def fromUri(uri: String, resourceLoader: ResourceLoader) = UriResource(uri, resourceLoader)
  196. /**
  197. * Creates a [[org.fusesource.scalate.support.Resource]] from a file
  198. */
  199. def fromFile(file: File): FileResource = fromFile(file, file.getPath)
  200. def fromFile(file: File, uri: String): FileResource = FileResource(file, uri)
  201. /**
  202. * Creates a [[org.fusesource.scalate.support.Resource]] from a file name
  203. */
  204. def fromFile(fileName: String): FileResource = fromFile(new File(fileName))
  205. /**
  206. * Creates a [[org.fusesource.scalate.support.Resource]] from a URL
  207. */
  208. def fromURL(url: URL): URLResource = URLResource(url)
  209. /**
  210. * Creates a [[org.fusesource.scalate.support.Resource]] from a URL
  211. */
  212. def fromURL(url: String): URLResource = fromURL(new URL(url))
  213. /**
  214. * Creates a [[org.fusesource.scalate.support.Resource]] from the [[scala.io.Source]] and the given URI.
  215. *
  216. * The URI is used to determine the package name to put the template in along with
  217. * the template kind (using the extension of the URI)
  218. */
  219. def fromSource(uri: String, source: Source) = SourceResource(uri, source)
  220. }