
.10.dp.source-code.io.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core_2.10 Show documentation
Show all versions of core_2.10 Show documentation
Core drx utilities that have no other dependencies other than the core scala lib
The newest version!
/*
Copyright 2010 Aaron J. Radke
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cc.drx
import java.io.{InputStream => JIS}
import java.io.{OutputStream => JOS}
import java.util.zip.GZIPOutputStream
import java.util.zip.GZIPInputStream
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
@deprecated("use Output or Input or StreamGenerator instead","dhu")
object IO{
//--Output constructors/wrappers/generators
@deprecated("use Output.apply instead","dhu")
def apply(os:JOS):Output = Output(os)
@deprecated("use Output.bytes instead","dhu")
def bytes:Output.ByteStreamOutput = Output.bytes
//
//--Input constructors/wrappers/generators
@deprecated("use Input.apply instead","dhu")
def apply(bytes:Array[Byte]):Input = new Input(new java.io.ByteArrayInputStream(bytes))
@deprecated("use Input.apply instead","dhu")
def apply(is:JIS):Input = new Input(is)
@deprecated("use Input.apply instead","dhu")
def apply(string:String):Input = Input(string, StandardCharsets.UTF_8)
@deprecated("use Input.apply instead","dhu")
def apply(string:String,charset:Charset):Input = Input(string getBytes charset)
// def apply(url:URL):Input = apply(url.toJava.openStream)
@deprecated("use Input.apply instead","dhu")
def apply(url:java.net.URL):Input = Input(url.openStream)
//TODO this maybe need to be removed in preference to URL
@deprecated("use Input.url instead","dhu")
def url(uri:String):Input = Input(new java.net.URL(uri))
@deprecated("use Input.resource instead","dhu")
def resource(path:String):Input = Input(getClass.getResource(if(path startsWith "/") path else "/"+path))
@deprecated("use Input.resource instead","dhu")
def resource(file:File):Input = Input.resource(file).get
}
//--Input and output generators
//-----------------------
trait IOGenerator[I,O]{
val exts:List[String]
def canGenerate(file:File):Boolean = canWrite && exts.exists{ext => file.name.endsWith("."+ext)}
def canRead(file:File):Boolean = canGenerate(file)
def canWrite(file:File):Boolean = canGenerate(file)
def canWrite:Boolean = true //default assume the stream can be written as well as read
def apply(in:Input):I = apply(in.is)
def apply(out:Output):O = apply(out.os)
def apply(jis:JIS):I// = is(jis)
def apply(jos:JOS):O// = os(jos)
}
trait StreamGenerator extends IOGenerator[Input,Output]{
def apply(bytes:Array[Byte]):Input = apply(new java.io.ByteArrayInputStream(bytes))
def apply(string:String,charset:Charset=StandardCharsets.UTF_8):Input = apply(string getBytes charset)
override def apply(in:Input):Input = in //Input(in.is)
override def apply(out:Output):Output = out //Output(out.os)
}
object StreamGenerator{
case object Normal extends StreamGenerator{
val exts:List[String] = List.empty
def apply(jis:JIS) = Input(jis)
def apply(jos:JOS) = Output(jos)
}
case object GZ extends StreamGenerator{
val exts:List[String] = List("gz")
def apply(jis:JIS) = Input(new GZIPInputStream(jis))
def apply(jos:JOS) = Output(new GZIPOutputStream(jos))
}
}
//--Output stream constructors
object Output{
def apply(os:JOS):Output = new Output(os)
def bytes:Output.ByteStreamOutput = new ByteStreamOutput(new java.io.ByteArrayOutputStream)
def nullOutput:Output = new Output( new JOS{ override def write(b:Int):Unit = () })
def toByteArray(write: Output => Unit):Array[Byte] = {val out = Output.bytes; write(out); out.toByteArray}
class ByteStreamOutput(bos:java.io.ByteArrayOutputStream) extends Output(bos){
def toByteArray = bos.toByteArray
}
}
//--Output stream wrappers and methods
class Output(val os:JOS){
//TODO wrap autoclose with proper try finally blocks that really close all the resources like Shabin suggestions with Scope at ScalaWorld2016
var autoClose = true
def as(sg:StreamGenerator):Output = {val out = sg(os); out.autoClose = autoClose; out} //pass through autoClose option
def keepOpen = {autoClose = false; this} //turn off autoClose
def apply[A](applyFunc:JOS => A):Try[A] = {
val res = Try{applyFunc(os)}
if(autoClose) os.close //TODO need to clean up if there was a failure to close and use pat in others
res
}
def write(bytes:Array[Byte]):Unit = {
os.write(bytes)
if(autoClose) os.close
}
def write(writeFunc:java.io.DataOutputStream => Unit):Unit = {
val dos = new java.io.DataOutputStream(os)
writeFunc(dos)
if(autoClose) dos.close
}
def print(printFunc:java.io.PrintWriter => Unit):Unit = {
val pw = new java.io.PrintWriter(os)
printFunc(pw)
if(autoClose) pw.close
}
def print(string:String):Unit = print(f => f.print(string))
def println(string:String):Unit = print(f => f.println(string))
def print(strings:Iterable[String]):Unit = print{f => for(s <- strings) f.println(s)}
def close = os.close
}
object Input{
lazy val std = new Input(System.in)
//--Input constructors/wrappers/generators
def apply(is:JIS):Input = new Input(is)
def apply(bytes:Array[Byte]):Input = new Input(new java.io.ByteArrayInputStream(bytes))
def apply(string:String):Input = Input(string, StandardCharsets.UTF_8)
def apply(string:String,charset:Charset):Input = Input(string getBytes charset)
// def apply(url:URL):Input = apply(url.toJava.openStream)
def apply(url:java.net.URL):Input = Input(url.openStream)
//TODO this maybe need to be removed in preference to URL
def url(uri:String):Input = Input(new java.net.URL(uri))
// @deprecated("use resource(path).get instead to emphasize unsafe pluck","@dia")
// def resource(path:String):Input = resource(path).get
// @deprecated("use resource(file).get instead to emphasize unsafe pluck","@dia")
//
def apply(bytes:Iterator[Byte]):Input = new Input(/*new java.io.BufferedInputStream*/(new JIS{
// private var buf:Array[Byte] = bytes.take(1.k).toArray
// private var i:Int = 0
def read():Int = if(bytes.hasNext) bytes.next().toInt else -1
//TODO add more JIS functionality for higher performance
override def available:Int = if(bytes.hasNext) 1 else 0 //the default implementation=0 so could break some functions
//TODO add more JIS functionality for higher performance
//FIXME to work with AIS (audio)
}))
def apply(bytes:Iterable[Byte]):Input = apply(bytes.iterator)
private def resourceURL(file:File):Option[java.net.URL] = {
val path = file.unixPath
val modPath = if(path startsWith "/") path else "/"+path
Option(getClass.getResource(modPath)) //wrap the null return as an None
}
def resource(file:File):Option[Input] = resourceURL(file) map Input.apply
def isResource(file:File):Boolean = resourceURL(file).isDefined
// @deprecated("used resource(file.path).get instead", "@dia") //deprecation warnings were not caught and the type checker complained instead
// def resource(path:String):Input = Input(resourceURL(File(path)).get)
def empty:Input = Input("")
// def apply(file:File) = TODO add a smart searhc for local file then resources...
//--implicits
import scala.language.implicitConversions
implicit def Input2InputStream(input:Input):JIS = input.is
//--Misc data funtions
// def bytes(xs:Int*):Array[Byte] = xs map(_.toByte).toArray
//
//def buffer(xs:Byte*):java.nio.ByteBuffer
def xdd(bs:Array[Byte], cols:Int=16, bytesPerGroup:Int=2):Iterator[String] = {
val bytesPerLine = bytesPerGroup*cols
bs.grouped(bytesPerLine).zipWithIndex.map{case (l,i) =>
val index = "%08X" format i*bytesPerLine
val hex = l grouped bytesPerGroup map {g => g map {"%02X" format _} mkString ""} mkString " "
val utf = l grouped 2 map {g => U16(g(0), g(1)).toInt.toChar} mkString ""
val padding = " "*((bytesPerGroup*2+1)*cols - hex.size)
index + " " + hex + padding + " " + Ansi.strip(utf).replaceAll("[\r\n]",".")
}
}
class LineIterator(is:JIS, debug:Option[Int]=None, autoClose:Boolean=true) extends Iterator[String]{
private val src = Input(is).bufferedSource(scala.io.Source.DefaultBufSize*8)
private val it0 = src.getLines
private val it = if(debug.isDefined) it0 take debug.get else it0
private var isOpen = true
def hasNext:Boolean = if(isOpen && it.hasNext) true else {if(isOpen && autoClose) close; false}
def next:String = it.next
def close = { isOpen = false; src.close}
}
}
//---Input stream wrappers and methods
class Input(val is:JIS){
//TODO wrap autoclose with proper try finally blocks that really close all the resources like Shabin suggestions with Scope at ScalaWorld2016
private var autoClose:Boolean = true
def as(sg:StreamGenerator):Input = {val in = sg(is); in.autoClose = autoClose; in} //pass through autoClose option
def keepOpen:Input = {val in = Input(is); in.autoClose = false; in} //turn off autoClose
def apply[A](applyFunc:JIS => A):Try[A] = {
val res = Try{applyFunc(is)}
if(autoClose) is.close //TODO need to clean up if there was a failure to close and use pat in others
res
}
def monitor(message:String="Progress"):Input =
Input(new javax.swing.ProgressMonitorInputStream(null, message, is))
def close = is.close
def read[A](readFunc:java.io.DataInputStream => A):A = {
val dis = new java.io.DataInputStream(is)
val res = readFunc(dis)
if(autoClose) dis.close
res
}
//FIXME remove this broken somewhat broken concept
@deprecated("remove this broken somewhat broken concept, since the buffer size may not be full","dj")
def readBytes(readFunc:Array[Byte] => Unit):Unit = {
var bytesRead = 0
val buffSize = 8192*2
val buffer = new Array[Byte](buffSize)
//TODO add isAvailable to pause?
while({bytesRead = is.read(buffer); bytesRead != -1}) readFunc(
if(bytesRead < buffSize) buffer take bytesRead else buffer
)
if(autoClose) is.close
}
@deprecated("use byteIt, since bytes seems like an immutable list","dk")
def bytes:Iterator[Byte] = byteIt(8192*2)
/**default large buffer iterator chunks*/
def byteIt:Iterator[Byte] = byteIt(8192*2)
def byteIt(bufferSize:Int):Iterator[Byte] =
if(bufferSize <= 1) byteIt(1) else new Iterator[Byte]{
//--constants
private val N = 8192*2 //buffer size
//--mutables
private val bs = new Array[Byte](bufferSize) //byte buffer
private var n = 0 //bytes read
private var i = bufferSize //buffer index
private var foundEnd:Boolean = false //end of file cache
private def eof:Boolean = {
if(!foundEnd && n == -1){
if(autoClose) is.close //autoClose if the first time we found an eof
foundEnd = true
}
foundEnd
}
private def eob:Boolean = i >= n-1 //end of buffer
def hasNext:Boolean = {
if(eof) false
else if(eob) {
n = is read bs //read the buffer
i = -1 //reset buffer index
// n != -1
!eof //check if eof
}
else true //inside buffer
}
def next():Byte = {
// Log(bufferSize,n,i,foundEnd,eof,eob)
if(foundEnd) ??? //no such element
else {
i += 1
bs(i)
}
}
}
/**readline like but with a callback on each charcter input; returning the full string*/
def readCharsWhile(callback:Char => Boolean,charset:Charset=StandardCharsets.UTF_8):Unit = {
val reader = new java.io.BufferedReader(new java.io.InputStreamReader(is,charset))
var _char:Int = 0
var _continue = true
while(_continue && {_char = is.read; /*print("[" + _char + "]");*/ _char != -1}) { //TODO add an available to prevent blocking on read
_continue = callback(_char.toChar)
}
}
/**readline like but with a callback on each charcter input; returning the full string*/
def promptLine:String= {
var _string:String = ""
readCharsWhile{ c =>
print(c) //show to std out
if(c == '\n' || c == '\r') false else {_string += c ; true}
}
print("\n")
_string
}
//TODO plan to deprecate since the copy direction is not explicilty clear
def copy(that:Output):Unit = copyTo(that)
def copyTo(that:Output):Unit = {
//IO.copy(this.is, that.os, autoClose)
var bytesRead = 0
val buffSize = 8192*2
val buffer = new Array[Byte](buffSize)
while({bytesRead = this.is.read(buffer); bytesRead != -1}) that.os.write(buffer,0,bytesRead)
that.os.flush
if(this.autoClose) this.is.close
if(that.autoClose) that.os.close
}
def lines:Input.LineIterator = new Input.LineIterator(is, None, autoClose)
def lines(debug:Option[Int]):Input.LineIterator = new Input.LineIterator(is, debug, autoClose)
def bufferedSource(bufferSize:Int=scala.io.Source.DefaultBufSize)(implicit codec:scala.io.Codec):scala.io.BufferedSource =
scala.io.Source.createBufferedSource(
is,
bufferSize,
reset = () => bufferedSource(bufferSize)(codec),
close = () => is.close()
) (codec)
private def toByteArrayOutputStream = {
val bos = new java.io.ByteArrayOutputStream
this copy Output(bos)
bos
}
override def toString = toString()
def toString(charset:Charset=StandardCharsets.UTF_8) = new String(toByteArray,charset)
def toByteArray:Array[Byte] = toByteArrayOutputStream.toByteArray
def toByteBuffer:java.nio.ByteBuffer = java.nio.ByteBuffer.wrap(toByteArray)
def base64:String = Base64.encode(toByteArray)
def cat:String = if(is != null){
val buf = new java.io.BufferedReader(new java.io.InputStreamReader(is))
@tailrec def read(str:StringBuilder):StringBuilder= {
val x = buf.readLine
if(x != null && x.nonEmpty) read(str ++= x) else str
}
read(new StringBuilder).toString
}
else ""
import java.security.{MessageDigest,DigestInputStream}
def digest(implicit md:MessageDigest):String = { //TODO should return a byteArray and byteArray has a toHex
val digestBytes = try{
val dis = new DigestInputStream(is,md)
val buffer = new Array[Byte](8192)
while(dis.read(buffer) >= 0){}
dis.close
md.digest
} finally {is.close}
digestBytes.map("%02x" format _).mkString
}
def sha1 = digest(MessageDigest getInstance "SHA-1")
def sha256 = digest(MessageDigest getInstance "SHA-256")
def md5 = digest(MessageDigest getInstance "MD5")
}
//-- PathEntry is either a FileEntry or a DirEntry
//TODO put all of these sub class inside some super trait like PathEntry or Input or File.Entry
// TODO implment moving over zip without commons compress by using these PathEntry
// so moving over zip files can work without the commons-compress dependency by using the built in java deflat/unzip
sealed trait PathEntry{ //TODO this basic trait could be a superset of file
val file:File
val date:Date
val size:Bytes
}
object PathEntry{
def apply(file:File):PathEntry =
if(file.isDir) DirEntry(file, file.modified)
else FileEntry(file)
protected[drx] def normalizePath(path:String) = path.replaceAllLiterally("\\","/")
}
object FileEntry{
def apply(file:File):FileEntry = new FileEntry(file, ()=>file.in, file.size, file.modified)
def apply(file:File, content:String):FileEntry = new FileEntry(file, ()=>Input(content), Bytes(content.size), Date.now)
def apply(file:File, bytes:Array[Byte]):FileEntry = new FileEntry(file, ()=>Input(bytes), Bytes(bytes.size), Date.now)
}
class FileEntry(val file:File, val makeStream:()=>Input, val size:Bytes, val date:Date) extends PathEntry{
override def toString = s"FileEntry($file, , size=$size, $date)"
lazy val in:Input = makeStream()
def as(sg:StreamGenerator):FileEntry = {
val bos = Output.bytes
in copy bos.as(sg)
val bytes = bos.toByteArray
new FileEntry(file, ()=>Input(bytes), Bytes(bytes.size), date)
}
def fullPath:String = PathEntry.normalizePath(file.path)
}
case class DirEntry(file:File, date:Date) extends PathEntry{
val size = Bytes(0L) //size:Long=ArchiveEntry.SIZE_UNKNOWN = -1
}
class DeepFileEntry(val parent:FileEntry, e:FileEntry) extends FileEntry(e.file,e.makeStream,e.size,e.date){
/** parents are the parents in a walk deep*/
def parents:List[FileEntry] = parent match{
case p:DeepFileEntry => p :: p.parents
case p:FileEntry => p :: Nil
}
/** file path as as a string (in a deep walk) */
override def fullPath:String = PathEntry.normalizePath((this :: parents).reverse map {_.file.path} mkString "/")
def treePath:String = (this :: parents).reverse.zipWithIndex map {case (e,i) => " "*i + e.file.path} mkString "\n"
override def toString = s"DeepFileEntry($fullPath, , size=$size, $date)"
}