diff --git a/cache/Cache.scala b/cache/Cache.scala index c638e94f0..0cf5ccd12 100644 --- a/cache/Cache.scala +++ b/cache/Cache.scala @@ -3,28 +3,25 @@ */ package sbt -import sbinary.{CollectionTypes, Format, JavaFormats} +import sbinary.{CollectionTypes, DefaultProtocol, Format, Input, JavaFormats, Output} import java.io.File +import java.net.{URI, URL} import Types.:+: +import DefaultProtocol.{asProduct2, asSingleton, BooleanFormat, ByteFormat, IntFormat, wrap} +import scala.xml.NodeSeq trait Cache[I,O] { def apply(file: File)(i: I): Either[O, O => Unit] } -trait SBinaryFormats extends CollectionTypes with JavaFormats with NotNull +trait SBinaryFormats extends CollectionTypes with JavaFormats { - //TODO: add basic types from SBinary minus FileFormat + implicit def urlFormat: Format[URL] = DefaultProtocol.UrlFormat + implicit def uriFormat: Format[URI] = DefaultProtocol.UriFormat } -object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImplicits +object Cache extends CacheImplicits { def cache[I,O](implicit c: Cache[I,O]): Cache[I,O] = c - def outputCache[O](implicit c: OutputCache[O]): OutputCache[O] = c - def inputCache[O](implicit c: InputCache[O]): InputCache[O] = c - - def wrapInputCache[I,DI](implicit convert: I => DI, base: InputCache[DI]): InputCache[I] = - new WrappedInputCache(convert, base) - def wrapOutputCache[O,DO](implicit convert: O => DO, reverse: DO => O, base: OutputCache[DO]): OutputCache[O] = - new WrappedOutputCache[O,DO](convert, reverse, base) def cached[I,O](file: File)(f: I => O)(implicit cache: Cache[I,O]): I => O = in => @@ -36,25 +33,219 @@ object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImpl store(out) out } -} -trait BasicCacheImplicits extends NotNull -{ - implicit def basicInputCache[I](implicit format: Format[I], equiv: Equiv[I]): InputCache[I] = - new BasicInputCache(format, equiv) - implicit def basicOutputCache[O](implicit format: Format[O]): OutputCache[O] = - new BasicOutputCache(format) - implicit def ioCache[I,O](implicit input: InputCache[I], output: OutputCache[O]): Cache[I,O] = - new SeparatedCache(input, output) - implicit def defaultEquiv[T]: Equiv[T] = new Equiv[T] { def equiv(a: T, b: T) = a == b } + def debug[I](label: String, c: InputCache[I]): InputCache[I] = + new InputCache[I] + { + type Internal = c.Internal + def convert(i: I) = c.convert(i) + def read(from: Input) = + { + val v = c.read(from) + println(label + ".read: " + v) + v + } + def write(to: Output, v: Internal) + { + println(label + ".write: " + v) + c.write(to, v) + } + def equiv: Equiv[Internal] = new Equiv[Internal] { + def equiv(a: Internal, b: Internal)= + { + val equ = c.equiv.equiv(a,b) + println(label + ".equiv(" + a + ", " + b +"): " + equ) + equ + } + } + } } +trait CacheImplicits extends BasicCacheImplicits with SBinaryFormats with HListCacheImplicits with UnionImplicits +trait BasicCacheImplicits +{ + implicit def basicCache[I, O](implicit in: InputCache[I], outFormat: Format[O]): Cache[I,O] = + new BasicCache()(in, outFormat) + def basicInput[I](implicit eq: Equiv[I], fmt: Format[I]): InputCache[I] = InputCache.basicInputCache(fmt, eq) + + def defaultEquiv[T]: Equiv[T] = new Equiv[T] { def equiv(a: T, b: T) = a == b } + + implicit def optInputCache[T](implicit t: InputCache[T]): InputCache[Option[T]] = + new InputCache[Option[T]] + { + type Internal = Option[t.Internal] + def convert(v: Option[T]): Internal = v.map(x => t.convert(x)) + def read(from: Input) = + { + val isDefined = BooleanFormat.reads(from) + if(isDefined) Some(t.read(from)) else None + } + def write(to: Output, j: Internal): Unit = + { + BooleanFormat.writes(to, j.isDefined) + j foreach { x => t.write(to, x) } + } + def equiv = optEquiv(t.equiv) + } + + def wrapEquiv[S,T](f: S => T)(implicit eqT: Equiv[T]): Equiv[S] = + new Equiv[S] { + def equiv(a: S, b: S) = + eqT.equiv( f(a), f(b) ) + } + + implicit def optEquiv[T](implicit t: Equiv[T]): Equiv[Option[T]] = + new Equiv[Option[T]] { + def equiv(a: Option[T], b: Option[T]) = + (a,b) match + { + case (None, None) => true + case (Some(va), Some(vb)) => t.equiv(va, vb) + case _ => false + } + } + implicit def urlEquiv(implicit uriEq: Equiv[URI]): Equiv[URL] = wrapEquiv[URL, URI](_.toURI)(uriEq) + implicit def uriEquiv: Equiv[URI] = defaultEquiv + implicit def stringSetEquiv: Equiv[Set[String]] = defaultEquiv + implicit def stringMapEquiv: Equiv[Map[String, String]] = defaultEquiv + + + implicit def xmlInputCache(implicit strEq: InputCache[String]): InputCache[NodeSeq] = wrapIn[NodeSeq, String](_.toString, strEq) + + implicit def seqCache[T](implicit t: InputCache[T]): InputCache[Seq[T]] = + new InputCache[Seq[T]] + { + type Internal = Seq[t.Internal] + def convert(v: Seq[T]) = v.map(x => t.convert(x)) + def read(from: Input) = + { + val size = IntFormat.reads(from) + def next(left: Int, acc: List[t.Internal]): Internal = + if(left <= 0) acc.reverse else next(left - 1, t.read(from) :: acc) + next(size, Nil) + } + def write(to: Output, vs: Internal) + { + val size = vs.length + IntFormat.writes(to, size) + for(v <- vs) t.write(to, v) + } + def equiv: Equiv[Internal] = seqEquiv(t.equiv) + } + + implicit def arrEquiv[T](implicit t: Equiv[T]): Equiv[Array[T]] = + wrapEquiv( (x: Array[T]) => x :Seq[T] )(seqEquiv[T](t)) + + implicit def seqEquiv[T](implicit t: Equiv[T]): Equiv[Seq[T]] = + new Equiv[Seq[T]] + { + def equiv(a: Seq[T], b: Seq[T]) = + a.length == b.length && + ((a,b).zipped forall t.equiv) + } + implicit def seqFormat[T](implicit t: Format[T]): Format[Seq[T]] = + wrap[Seq[T], List[T]](_.toList, _.toSeq)(DefaultProtocol.listFormat) + + def wrapIn[I,J](implicit f: I => J, jCache: InputCache[J]): InputCache[I] = + new InputCache[I] + { + type Internal = jCache.Internal + def convert(i: I) = jCache.convert(f(i)) + def read(from: Input) = jCache.read(from) + def write(to: Output, j: Internal) = jCache.write(to, j) + def equiv = jCache.equiv + } + + def singleton[T](t: T): InputCache[T] = + basicInput(trueEquiv, asSingleton(t)) + + def trueEquiv[T] = new Equiv[T] { def equiv(a: T, b: T) = true } +} + trait HListCacheImplicits { - implicit def hConsInputCache[H,T<:HList](implicit headCache: InputCache[H], tailCache: InputCache[T]): InputCache[H :+: T] = - new HConsInputCache(headCache, tailCache) - implicit lazy val hNilInputCache: InputCache[HNil] = new HNilInputCache - - implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[H :+: T] = - new HConsOutputCache(headCache, tailCache) - implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache + implicit def hConsCache[H, T <: HList](implicit head: InputCache[H], tail: InputCache[T]): InputCache[H :+: T] = + new InputCache[H :+: T] + { + type Internal = (head.Internal, tail.Internal) + def convert(in: H :+: T) = (head.convert(in.head), tail.convert(in.tail)) + def read(from: Input) = + { + val h = head.read(from) + val t = tail.read(from) + (h, t) + } + def write(to: Output, j: Internal) + { + head.write(to, j._1) + tail.write(to, j._2) + } + def equiv = new Equiv[Internal] + { + def equiv(a: Internal, b: Internal) = + head.equiv.equiv(a._1, b._1) && + tail.equiv.equiv(a._2, b._2) + } + } + + implicit def hNilCache: InputCache[HNil] = Cache.singleton(HNil : HNil) } +trait UnionImplicits +{ + def unionInputCache[UB, HL <: HList](implicit uc: UnionCache[HL, UB]): InputCache[UB] = + new InputCache[UB] + { + type Internal = Found[_] + def convert(in: UB) = uc.find(in) + def read(in: Input) = + { + val index = ByteFormat.reads(in) + val (cache, clazz) = uc.at(index) + val value = cache.read(in) + new Found[cache.Internal](cache, clazz, value, index) + } + def write(to: Output, i: Internal) + { + def write0[I](f: Found[I]) + { + ByteFormat.writes(to, f.index.toByte) + f.cache.write(to, f.value) + } + write0(i) + } + def equiv: Equiv[Internal] = new Equiv[Internal] + { + def equiv(a: Internal, b: Internal) = + { + if(a.clazz == b.clazz) + force(a.cache.equiv, a.value, b.value) + else + false + } + def force[T <: UB, UB](e: Equiv[T], a: UB, b: UB) = e.equiv(a.asInstanceOf[T], b.asInstanceOf[T]) + } + } + + implicit def unionCons[H <: UB, UB, T <: HList](implicit head: InputCache[H], mf: Manifest[H], t: UnionCache[T, UB]): UnionCache[H :+: T, UB] = + new UnionCache[H :+: T, UB] + { + val size = 1 + t.size + def c = mf.erasure + def find(value: UB): Found[_] = + if(c.isInstance(value)) new Found[head.Internal](head, c, head.convert(value.asInstanceOf[H]), size - 1) else t.find(value) + def at(i: Int): (InputCache[_ <: UB], Class[_]) = if(size == i + 1) (head, c) else t.at(i) + } + + implicit def unionNil[UB]: UnionCache[HNil, UB] = new UnionCache[HNil, UB] { + def size = 0 + def find(value: UB) = error("No valid sum type for " + value) + def at(i: Int) = error("Invalid union index " + i) + } + + final class Found[I](val cache: InputCache[_] { type Internal = I }, val clazz: Class[_], val value: I, val index: Int) + sealed trait UnionCache[HL <: HList, UB] + { + def size: Int + def at(i: Int): (InputCache[_ <: UB], Class[_]) + def find(forValue: UB): Found[_] + } +} \ No newline at end of file diff --git a/cache/FileInfo.scala b/cache/FileInfo.scala index 425a8598d..5731a535e 100644 --- a/cache/FileInfo.scala +++ b/cache/FileInfo.scala @@ -30,15 +30,21 @@ private final case class FileHashModified(file: File, hash: List[Byte], lastModi object FileInfo { - sealed trait Style extends NotNull + implicit def existsInputCache: InputCache[PlainFileInfo] = exists.infoInputCache + implicit def modifiedInputCache: InputCache[ModifiedFileInfo] = lastModified.infoInputCache + implicit def hashInputCache: InputCache[HashFileInfo] = hash.infoInputCache + implicit def fullInputCache: InputCache[HashModifiedFileInfo] = full.infoInputCache + + sealed trait Style { type F <: FileInfo implicit def apply(file: File): F implicit def unapply(info: F): File = info.file implicit val format: Format[F] import Cache._ - implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F] - implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F] + implicit def fileInfoEquiv: Equiv[F] = defaultEquiv + implicit def infoInputCache: InputCache[F] = basicInput + implicit def fileInputCache: InputCache[File] = wrapIn[File,F] } object full extends Style { @@ -71,10 +77,10 @@ object FileInfo } } -final case class FilesInfo[F <: FileInfo] private(files: Set[F]) extends NotNull +final case class FilesInfo[F <: FileInfo] private(files: Set[F]) object FilesInfo { - sealed abstract class Style extends NotNull + sealed abstract class Style { val fileStyle: FileInfo.Style type F = fileStyle.F @@ -85,8 +91,9 @@ object FilesInfo val manifest: Manifest[Format[FilesInfo[F]]] def empty: FilesInfo[F] = new FilesInfo(Set.empty) import Cache._ - implicit def infosInputCache: InputCache[Set[File]] = wrapInputCache[Set[File],FilesInfo[F]] - implicit def infosOutputCache: OutputCache[Set[File]] = wrapOutputCache[Set[File],FilesInfo[F]] + implicit def infosInputCache: InputCache[FilesInfo[F]] = basicInput + implicit def filesInputCache: InputCache[Set[File]] = wrapIn[Set[File],FilesInfo[F]] + implicit def filesInfoEquiv: Equiv[FilesInfo[F]] = defaultEquiv } private final class BasicStyle[FI <: FileInfo](val fileStyle: FileInfo.Style { type F = FI }) (implicit val manifest: Manifest[Format[FilesInfo[FI]]]) extends Style @@ -95,8 +102,8 @@ object FilesInfo implicit def apply(files: Set[File]): FilesInfo[F] = FilesInfo( files.map(_.getAbsoluteFile).map(fileStyle.apply) ) implicit val formats: Format[FilesInfo[F]] = wrap(_.files, (fs: Set[F]) => new FilesInfo(fs)) } - lazy val full: Style = new BasicStyle(FileInfo.full) - lazy val hash: Style = new BasicStyle(FileInfo.hash) - lazy val lastModified: Style = new BasicStyle(FileInfo.lastModified) - lazy val exists: Style = new BasicStyle(FileInfo.exists) + lazy val full: Style { type F = HashModifiedFileInfo } = new BasicStyle(FileInfo.full) + lazy val hash: Style { type F = HashFileInfo } = new BasicStyle(FileInfo.hash) + lazy val lastModified: Style { type F = ModifiedFileInfo } = new BasicStyle(FileInfo.lastModified) + lazy val exists: Style { type F = PlainFileInfo } = new BasicStyle(FileInfo.exists) } \ No newline at end of file diff --git a/cache/HListCache.scala b/cache/HListCache.scala deleted file mode 100644 index 2bb3def3b..000000000 --- a/cache/HListCache.scala +++ /dev/null @@ -1,47 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009 Mark Harrah - */ -package sbt - -import java.io.{InputStream,OutputStream} - -import Types._ -class HNilInputCache extends NoInputCache[HNil] -class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: InputCache[T]) extends InputCache[H :+: T] -{ - def uptodate(in: H :+: T)(cacheStream: InputStream) = - { - val headResult = headCache.uptodate(in.head)(cacheStream) - val tailResult = tailCache.uptodate(in.tail)(cacheStream) - new CacheResult - { - val uptodate = headResult.uptodate && tailResult.uptodate - def update(outputStream: OutputStream) = - { - headResult.update(outputStream) - tailResult.update(outputStream) - } - } - } - def force(in: H :+: T)(cacheStream: OutputStream) = - { - headCache.force(in.head)(cacheStream) - tailCache.force(in.tail)(cacheStream) - } -} - -class HNilOutputCache extends NoOutputCache[HNil](HNil) -class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCache: OutputCache[T]) extends OutputCache[H :+: T] -{ - def loadCached(cacheStream: InputStream) = - { - val head = headCache.loadCached(cacheStream) - val tail = tailCache.loadCached(cacheStream) - HCons(head, tail) - } - def update(out: H :+: T)(cacheStream: OutputStream) - { - headCache.update(out.head)(cacheStream) - tailCache.update(out.tail)(cacheStream) - } -} \ No newline at end of file diff --git a/cache/NoCache.scala b/cache/NoCache.scala deleted file mode 100644 index bdb9c4f1b..000000000 --- a/cache/NoCache.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009 Mark Harrah - */ -package sbt - -import java.io.{InputStream,OutputStream} - -class NoInputCache[T] extends InputCache[T] -{ - def uptodate(in: T)(cacheStream: InputStream) = - new CacheResult - { - def uptodate = true - def update(outputStream: OutputStream) {} - } - def force(in: T)(outputStream: OutputStream) {} -} -class NoOutputCache[O](create: => O) extends OutputCache[O] -{ - def loadCached(cacheStream: InputStream) = create - def update(out: O)(cacheStream: OutputStream) {} -} \ No newline at end of file diff --git a/cache/SeparatedCache.scala b/cache/SeparatedCache.scala index 6509e05bf..59bbbe379 100644 --- a/cache/SeparatedCache.scala +++ b/cache/SeparatedCache.scala @@ -3,86 +3,56 @@ */ package sbt -import sbinary.Format -import sbinary.JavaIO._ +import Types.:+: +import sbinary.{DefaultProtocol, Format, Input, JavaIO, Output} +import DefaultProtocol.ByteFormat +import JavaIO._ import java.io.{File, InputStream, OutputStream} -trait CacheResult +trait InputCache[I] { - def uptodate: Boolean - def update(stream: OutputStream): Unit + type Internal + def convert(i: I): Internal + def read(from: Input): Internal + def write(to: Output, j: Internal): Unit + def equiv: Equiv[Internal] } -class ForceResult[I](inCache: InputCache[I])(in: I) extends CacheResult +object InputCache { - def uptodate = false - def update(stream: OutputStream) = inCache.force(in)(stream) + implicit def basicInputCache[I](implicit fmt: Format[I], eqv: Equiv[I]): InputCache[I] = + new InputCache[I] + { + type Internal = I + def convert(i: I) = i + def read(from: Input): I = fmt.reads(from) + def write(to: Output, i: I) = fmt.writes(to, i) + def equiv = eqv + } } -trait InputCache[I] extends NotNull -{ - def uptodate(in: I)(cacheStream: InputStream): CacheResult - def force(in: I)(cacheStream: OutputStream): Unit -} -trait OutputCache[O] extends NotNull -{ - def loadCached(cacheStream: InputStream): O - def update(out: O)(cacheStream: OutputStream): Unit -} -class SeparatedCache[I,O](input: InputCache[I], output: OutputCache[O]) extends Cache[I,O] + +class BasicCache[I,O](implicit input: InputCache[I], outFormat: Format[O]) extends Cache[I,O] { def apply(file: File)(in: I) = - try { applyImpl(file, in) } - catch { case _: Exception => Right(update(file)(in)) } - protected def applyImpl(file: File, in: I) = + { + val j = input.convert(in) + try { applyImpl(file, j) } + catch { case e: Exception => Right(update(file)(j)) } + } + protected def applyImpl(file: File, in: input.Internal) = { Using.fileInputStream(file) { stream => - val cache = input.uptodate(in)(stream) - lazy val doUpdate = (result: O) => - { - Using.fileOutputStream(false)(file) { stream => - cache.update(stream) - output.update(result)(stream) - } - } - if(cache.uptodate) - try { Left(output.loadCached(stream)) } - catch { case _: Exception => Right(doUpdate) } + val previousIn = input.read(stream) + if(input.equiv.equiv(in, previousIn)) + Left(outFormat.reads(stream)) else - Right(doUpdate) + Right(update(file)(in)) } } - protected def update(file: File)(in: I)(out: O) + protected def update(file: File)(in: input.Internal) = (out: O) => { Using.fileOutputStream(false)(file) { stream => - input.force(in)(stream) - output.update(out)(stream) + input.write(stream, in) + outFormat.writes(stream, out) } } -} -class BasicOutputCache[O](val format: Format[O]) extends OutputCache[O] -{ - def loadCached(cacheStream: InputStream): O = format.reads(cacheStream) - def update(out: O)(cacheStream: OutputStream): Unit = format.writes(cacheStream, out) -} -class BasicInputCache[I](val format: Format[I], val equiv: Equiv[I]) extends InputCache[I] -{ - def uptodate(in: I)(cacheStream: InputStream) = - { - val loaded = format.reads(cacheStream) - new CacheResult - { - val uptodate = equiv.equiv(in, loaded) - def update(outputStream: OutputStream) = force(in)(outputStream) - } - } - def force(in: I)(outputStream: OutputStream) = format.writes(outputStream, in) -} -class WrappedInputCache[I,DI](val convert: I => DI, val base: InputCache[DI]) extends InputCache[I] -{ - def uptodate(in: I)(cacheStream: InputStream) = base.uptodate(convert(in))(cacheStream) - def force(in: I)(outputStream: OutputStream) = base.force(convert(in))(outputStream) -} -class WrappedOutputCache[O,DO](val convert: O => DO, val reverse: DO => O, val base: OutputCache[DO]) extends OutputCache[O] -{ - def loadCached(cacheStream: InputStream): O = reverse(base.loadCached(cacheStream)) - def update(out: O)(cacheStream: OutputStream): Unit = base.update(convert(out))(cacheStream) } \ No newline at end of file diff --git a/cache/tracking/Tracked.scala b/cache/tracking/Tracked.scala index 798adbb11..6fe399821 100644 --- a/cache/tracking/Tracked.scala +++ b/cache/tracking/Tracked.scala @@ -5,7 +5,7 @@ package sbt import java.io.{File,IOException} import CacheIO.{fromFile, toFile} -import sbinary.Format +import sbinary.{Format, JavaIO} import scala.reflect.Manifest import IO.{delete, read, write} @@ -18,8 +18,8 @@ object Tracked * In both cases, the timestamp is not updated if the function throws an exception.*/ def tstamp(cacheFile: File, useStartTime: Boolean = true): Timestamp = new Timestamp(cacheFile, useStartTime) /** Creates a tracker that only evaluates a function when the input has changed.*/ - def changed[O](cacheFile: File)(implicit input: InputCache[O]): Changed[O] = - new Changed[O](cacheFile)(input) + def changed[O](cacheFile: File)(implicit format: Format[O], equiv: Equiv[O]): Changed[O] = + new Changed[O](cacheFile) /** Creates a tracker that provides the difference between a set of input files for successive invocations.*/ def diffInputs(cache: File, style: FilesInfo.Style): Difference = @@ -52,22 +52,29 @@ class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 } } -class Changed[O](val cacheFile: File)(implicit input: InputCache[O]) extends Tracked +class Changed[O](val cacheFile: File)(implicit equiv: Equiv[O], format: Format[O]) extends Tracked { def clean = delete(cacheFile) def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => { - val cache = - try { Using.fileInputStream(cacheFile)(input.uptodate(value)) } - catch { case _: IOException => new ForceResult(input)(value) } - if(cache.uptodate) + if(uptodate(value)) ifUnchanged(value) else { - Using.fileOutputStream(false)(cacheFile)(cache.update) + update(value) ifChanged(value) } } + import JavaIO._ + def update(value: O): Unit = Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value)) + def uptodate(value: O): Boolean = + try { + Using.fileInputStream(cacheFile) { + stream => equiv.equiv(value, format.reads(stream)) + } + } catch { + case _: IOException => false + } } object Difference {