keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / util.scala
CommitLineData
8eabb4ff
MW
1/* -*-scala-*-
2 *
3 * Miscellaneous utilities
4 *
5 * (c) 2018 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11 *
12 * TrIPE is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
16 *
17 * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26package uk.org.distorted; package object tripe {
27
28/*----- Imports -----------------------------------------------------------*/
29
0157de02
MW
30import scala.language.{existentials, implicitConversions};
31
32import scala.collection.mutable.{HashSet, WeakHashMap};
8eabb4ff 33import scala.concurrent.duration.{Deadline, Duration};
c8292b34 34import scala.util.control.{Breaks, ControlThrowable};
8eabb4ff 35
c8292b34 36import java.io.{BufferedReader, Closeable, File, InputStream, Reader};
04a5abae 37import java.net.{HttpURLConnection, URL, URLConnection};
8eabb4ff 38import java.nio.{ByteBuffer, CharBuffer};
04a5abae
MW
39import java.nio.channels.{SelectionKey, Selector};
40import java.nio.channels.spi.{AbstractSelector, AbstractSelectableChannel};
8eabb4ff 41import java.nio.charset.Charset;
a5ec891a 42import java.text.SimpleDateFormat;
04a5abae 43import java.util.{Set => JSet};
8eabb4ff
MW
44import java.util.concurrent.locks.{Lock, ReentrantLock};
45
46/*----- Miscellaneous useful things ---------------------------------------*/
47
48val rng = new java.security.SecureRandom;
49
50def unreachable(msg: String): Nothing = throw new AssertionError(msg);
c8292b34
MW
51def unreachable(): Nothing = unreachable("unreachable");
52final val ok = ();
0157de02
MW
53class Brand(val what: String) {
54 override def toString(): String = s"<${getClass.getName} $what>";
55}
8eabb4ff
MW
56
57/*----- Various pieces of implicit magic ----------------------------------*/
58
59class InvalidCStringException(msg: String) extends Exception(msg);
8eabb4ff 60
25c35469 61object Implicits {
8eabb4ff
MW
62
63 /* --- Syntactic sugar for locks --- */
64
65 implicit class LockOps(lk: Lock) {
66 /* LK withLock { BODY }
67 * LK.withLock(INTERRUPT) { BODY }
25c35469
MW
68 * LK.withLock(DUR, [INTERRUPT]) { BODY } orElse { ALT }
69 * LK.withLock(DL, [INTERRUPT]) { BODY } orElse { ALT }
8eabb4ff
MW
70 *
71 * Acquire a lock while executing a BODY. If a duration or deadline is
72 * given then wait so long for the lock, and then give up and run ALT
73 * instead.
74 */
75
76 def withLock[T](dur: Duration, interrupt: Boolean)
77 (body: => T): PendingLock[T] =
78 new PendingLock(lk, if (dur > Duration.Zero) dur else Duration.Zero,
79 interrupt, body);
80 def withLock[T](dur: Duration)(body: => T): PendingLock[T] =
81 withLock(dur, true)(body);
82 def withLock[T](dl: Deadline, interrupt: Boolean)
83 (body: => T): PendingLock[T] =
84 new PendingLock(lk, dl.timeLeft, interrupt, body);
85 def withLock[T](dl: Deadline)(body: => T): PendingLock[T] =
86 withLock(dl, true)(body);
87 def withLock[T](interrupt: Boolean)(body: => T): T = {
88 if (interrupt) lk.lockInterruptibly();
89 else lk.lock();
90 try { body; } finally lk.unlock();
91 }
92 def withLock[T](body: => T): T = withLock(true)(body);
93 }
94
25c35469 95 class PendingLock[T] private[Implicits]
8eabb4ff
MW
96 (val lk: Lock, val dur: Duration,
97 val interrupt: Boolean, body: => T) {
25c35469 98 /* An auxiliary class for LockOps; provides the `orElse' qualifier. */
8eabb4ff 99
25c35469 100 def orElse(alt: => T): T = {
8eabb4ff
MW
101 val locked = (dur, interrupt) match {
102 case (Duration.Inf, true) => lk.lockInterruptibly(); true
103 case (Duration.Inf, false) => lk.lock(); true
104 case (Duration.Zero, false) => lk.tryLock()
105 case (_, true) => lk.tryLock(dur.length, dur.unit)
106 case _ => unreachable("timed wait is always interruptible");
107 }
108 if (!locked) alt;
109 else try { body; } finally lk.unlock();
110 }
111 }
0157de02
MW
112
113 /* Implicit conversions to `Boolean'. I miss the way C integers and
114 * pointers convert to boolean, so let's do that here.
115 *
116 * Numeric zero, null, and empty containers are all false; other objects
117 * are true.
118 */
119 implicit def truish(n: Byte): Boolean = n != 0;
120 implicit def truish(n: Char): Boolean = n != 0;
121 implicit def truish(n: Short): Boolean = n != 0;
122 implicit def truish(n: Int): Boolean = n != 0;
123 implicit def truish(n: Long): Boolean = n != 0;
124 implicit def truish(n: Float): Boolean = n != 0;
125 implicit def truish(n: Double): Boolean = n != 0;
126 implicit def truish(x: AnyRef): Boolean = x != null;
127 implicit def truish(s: String): Boolean = s != null && s != "";
128 implicit def truish(o: Option[_]): Boolean = o != None;
129 implicit def truish(i: Iterator[_]): Boolean = i != null && i.hasNext;
130 implicit def truish(c: Traversable[_]): Boolean =
131 c != null && c.nonEmpty;
132
133 /* Some additional bitwise operators.
134 *
135 * For now, just the `bic' operator `&~', because typing `& ~' is
136 * inconsistent with my current style.
137 */
138 class BitwiseIntImplicits(x: Int) {
139 def &~(y: Byte): Int = x & ~y;
140 def &~(y: Char): Int = x & ~y;
141 def &~(y: Short): Int = x & ~y;
142 def &~(y: Int): Int = x & ~y;
143 def &~(y: Long): Long = x & ~y;
144 }
145 class BitwiseLongImplicits(x: Long) {
146 def &~(y: Byte): Long = x & ~y;
147 def &~(y: Char): Long = x & ~y;
148 def &~(y: Short): Long = x & ~y;
149 def &~(y: Int): Long = x & ~y;
150 def &~(y: Long): Long = x & ~y;
151 }
152 implicit def bitwiseImplicits(x: Byte) = new BitwiseIntImplicits(x);
153 implicit def bitwiseImplicits(x: Char) = new BitwiseIntImplicits(x);
154 implicit def bitwiseImplicits(x: Short) = new BitwiseIntImplicits(x);
155 implicit def bitwiseImplicits(x: Int) = new BitwiseIntImplicits(x);
156 implicit def bitwiseImplicits(x: Long) = new BitwiseLongImplicits(x);
8eabb4ff
MW
157}
158
0157de02
MW
159import Implicits.truish;
160
8eabb4ff
MW
161/*----- Cleanup assistant -------------------------------------------------*/
162
163class Cleaner {
164 /* A helper class for avoiding deep nests of `try'/`finally'.
165 *
166 * Make a `Cleaner' instance CL at the start of your operation. Apply it
167 * to blocks of code -- as CL { ACTION } -- as you proceed, to accumulate
168 * cleanup actions. Finally, call CL.cleanup() to invoke the accumulated
169 * actions, in reverse order.
170 */
171
172 var cleanups: List[() => Unit] = Nil;
173 def apply(cleanup: => Unit) { cleanups +:= { () => cleanup; } }
174 def cleanup() { cleanups foreach { _() } }
175}
176
177def withCleaner[T](body: Cleaner => T): T = {
178 /* An easier way to use the `Cleaner' class. Just
179 *
180 * withCleaner { CL => BODY }
181 *
182 * The BODY can attach cleanup actions to the cleaner CL by saying
183 * CL { ACTION } as usual. When the BODY exits, normally or otherwise, the
184 * cleanup actions are invoked in reverse order.
185 */
186
187 val cleaner = new Cleaner;
188 try { body(cleaner) }
189 finally { cleaner.cleanup(); }
190}
191
192def closing[T, U <: Closeable](thing: U)(body: U => T): T =
193 try { body(thing) }
194 finally { thing.close(); }
195
c8292b34
MW
196/*----- Control structures ------------------------------------------------*/
197
198private case class ExitBlock[T](brand: Brand, result: T)
199 extends ControlThrowable;
200
201def block[T](body: (T => Nothing) => T): T = {
202 /* block { exit[T] => ...; exit(x); ... }
203 *
204 * Execute the body until it calls the `exit' function or finishes.
205 * Annoyingly, Scala isn't clever enough to infer the return type, so
206 * you'll have to write it explicitly.
207 */
208
0157de02 209 val mybrand = new Brand("block-exit");
c8292b34
MW
210 try { body { result => throw new ExitBlock(mybrand, result) } }
211 catch {
212 case ExitBlock(brand, result) if brand eq mybrand =>
213 result.asInstanceOf[T]
214 }
215}
216
217def blockUnit(body: (=> Nothing) => Unit) {
218 /* blockUnit { exit => ...; exit; ... }
219 *
220 * Like `block'; it just saves you having to write `exit[Unit] => ...;
221 * exit(ok); ...'.
222 */
223
0157de02 224 val mybrand = new Brand("block-exit");
c8292b34
MW
225 try { body { throw new ExitBlock(mybrand, null) }; }
226 catch { case ExitBlock(brand, result) if brand eq mybrand => ok; }
227}
228
229def loop[T](body: (T => Nothing) => Unit): T = {
230 /* loop { exit[T] => ...; exit(x); ... }
231 *
232 * Repeatedly execute the body until it calls the `exit' function.
233 * Annoyingly, Scala isn't clever enough to infer the return type, so
234 * you'll have to write it explicitly.
235 */
236
237 block { exit => while (true) body(exit); unreachable }
238}
239
240def loopUnit(body: (=> Nothing) => Unit): Unit = {
241 /* loopUnit { exit => ...; exit; ... }
242 *
243 * Like `loop'; it just saves you having to write `exit[Unit] => ...;
244 * exit(()); ...'.
245 */
246
247 blockUnit { exit => while (true) body(exit); }
248}
249
250val BREAKS = new Breaks;
251import BREAKS.{breakable, break};
252
04a5abae
MW
253/*----- Interruptably doing things ----------------------------------------*/
254
255private class InterruptCatcher[T](body: => T, onWakeup: => Unit)
256 extends AbstractSelector(null) {
257 /* Hook onto the VM's thread interruption machinery.
258 *
259 * The `run' method is the only really interesting one. It will run the
260 * BODY, returning its result; if the thread is interrupted during this
261 * time, ONWAKEUP is invoked for effect. The expectation is that ONWAKEUP
262 * will somehow cause BODY to stop early.
263 *
264 * Credit for this hack goes to Nicholas Wilson: see
265 * <https://github.com/NWilson/javaInterruptHook>.
266 */
267
268 private def nope: Nothing =
269 { throw new UnsupportedOperationException("can't do that"); }
270 protected def implCloseSelector() { }
271 protected def register(chan: AbstractSelectableChannel,
272 ops: Int, att: Any): SelectionKey = nope;
273 def keys(): JSet[SelectionKey] = nope;
274 def selectedKeys(): JSet[SelectionKey] = nope;
275 def select(): Int = nope;
276 def select(millis: Long): Int = nope;
277 def selectNow(): Int = nope;
278
279 def run(): T = try {
280 begin();
281 val ret = body;
282 if (Thread.interrupted()) throw new InterruptedException;
283 ret
284 } finally {
285 end();
286 }
287 def wakeup(): Selector = { onWakeup; this }
288}
289
290class PendingInterruptable[T] private[tripe](body: => T) {
291 /* This class exists to provide the `onInterrupt THUNK' syntax. */
292
293 def onInterrupt(thunk: => Unit): T =
294 new InterruptCatcher(body, thunk).run();
295}
296def interruptably[T](body: => T) = {
297 /* interruptably { BODY } onInterrupt { THUNK }
298 *
299 * Execute BODY and return its result. If the thread receives an
300 * interrupt -- or is already in an interrupted state -- execute THUNK for
301 * effect; it is expected to cause BODY to return expeditiously, and when
302 * the BODY completes, an `InterruptedException' is thrown.
303 */
304
305 new PendingInterruptable(body);
306}
307
8eabb4ff
MW
308/*----- A gadget for fetching URLs ----------------------------------------*/
309
310class URLFetchException(msg: String) extends Exception(msg);
311
312trait URLFetchCallbacks {
313 def preflight(conn: URLConnection) { }
c8292b34 314 def write(buf: Array[Byte], n: Int, len: Long): Unit;
8eabb4ff
MW
315 def done(win: Boolean) { }
316}
317
318def fetchURL(url: URL, cb: URLFetchCallbacks) {
319 /* Fetch the URL, feeding the data through the callbacks CB. */
320
321 withCleaner { clean =>
04a5abae 322 var win: Boolean = false; clean { cb.done(win); }
8eabb4ff 323
04a5abae
MW
324 /* Set up the connection. This isn't going to block, I think, and we
325 * need to use it in the interrupt handler.
326 */
8eabb4ff 327 val c = url.openConnection();
8eabb4ff 328
04a5abae
MW
329 /* Java's default URL handlers don't respond to interrupts, so we have to
330 * take over this duty.
8eabb4ff 331 */
04a5abae
MW
332 interruptably {
333 /* Run the caller's preflight check. This must be done here, since it
334 * might well block while it discovers things like the content length.
335 */
336 cb.preflight(c);
337
338 /* Start fetching data. */
339 val in = c.getInputStream; clean { in.close(); }
340 val explen = c.getContentLength;
341
342 /* Read a buffer at a time, and give it to the callback. Maintain a
343 * running total.
344 */
345 var len: Long = 0;
346 blockUnit { exit =>
347 for ((buf, n) <- blocks(in)) {
348 cb.write(buf, n, len);
349 len += n;
350 if (explen != -1 && len > explen) exit;
351 }
c8292b34 352 }
8eabb4ff 353
04a5abae
MW
354 /* I can't find it documented anywhere that the existing machinery
355 * checks the received stream against the advertised content length.
356 * It doesn't hurt to check again, anyway.
357 */
358 if (explen != -1 && explen != len) {
359 throw new URLFetchException(
360 s"received $len /= $explen bytes from `$url'");
361 }
8eabb4ff 362
04a5abae
MW
363 /* Glorious success is ours. */
364 win = true;
365 } onInterrupt {
366 /* Oh. How do we do this? */
367
368 c match {
369 case c: HttpURLConnection =>
370 /* It's an HTTP connection (what happened to the case here?).
371 * HTTPS connections match too because they're a subclass. Getting
372 * the input stream will block, but there's an easier way.
373 */
374 c.disconnect();
375
376 case _ =>
377 /* It's something else. Let's hope that getting the input stream
378 * doesn't block.
379 */
380 c.getInputStream.close();
381 }
382 }
8eabb4ff
MW
383 }
384}
385
8eabb4ff
MW
386/*----- Threading things --------------------------------------------------*/
387
04a5abae
MW
388def thread(name: String, run: Boolean = true, daemon: Boolean = true)
389 (f: => Unit): Thread = {
8eabb4ff
MW
390 /* Make a thread with a given name, and maybe start running it. */
391
392 val t = new Thread(new Runnable { def run() { f; } }, name);
393 if (daemon) t.setDaemon(true);
394 if (run) t.start();
395 t
396}
397
04a5abae
MW
398class ValueThread[T](name: String, group: ThreadGroup = null,
399 stacksz: Long = 0)(body: => T)
400 extends Thread(group, null, name, stacksz) {
401 private[this] var exc: Throwable = _;
402 private[this] var ret: T = _;
403
404 override def run() {
405 try { ret = body; }
406 catch { case e: Throwable => exc = e; }
407 }
408 def get: T =
409 if (isAlive) throw new IllegalArgumentException("still running");
410 else if (exc != null) throw exc;
411 else ret;
412}
413def valueThread[T](name: String, run: Boolean = true)
414 (body: => T): ValueThread[T] = {
415 val t = new ValueThread(name)(body);
416 if (run) t.start();
417 t
418}
419
8eabb4ff
MW
420/*----- Quoting and parsing tokens ----------------------------------------*/
421
422def quoteTokens(v: Seq[String]): String = {
423 /* Return a string representing the token sequence V.
424 *
425 * The tokens are quoted as necessary.
426 */
427
428 val b = new StringBuilder;
429 var sep = false;
430 for (s <- v) {
431
432 /* If this isn't the first word, then write a separating space. */
433 if (!sep) sep = true;
434 else b += ' ';
435
436 /* Decide how to handle this token. */
437 if (s.length > 0 &&
438 (s forall { ch => (ch != ''' && ch != '"' && ch != '\\' &&
439 !ch.isWhitespace) })) {
440 /* If this word is nonempty and contains no problematic characters,
441 * we can write it literally.
442 */
443
444 b ++= s;
445 } else {
446 /* Otherwise, we shall have to do this the hard way. We could be
447 * cleverer about this, but it's not worth the effort.
448 */
449
450 b += '"';
451 s foreach { ch =>
452 if (ch == '"' || ch == '\\') b += '\\';
453 b += ch;
454 }
455 b += '"';
456 }
457 }
458 b.result
459}
460
461class InvalidQuotingException(msg: String) extends Exception(msg);
462
463def nextToken(s: String, pos: Int = 0): Option[(String, Int)] = {
464 /* Parse the next token from a string S.
465 *
466 * If there is a token in S starting at or after index POS, then return
467 * it, and the index for the following token; otherwise return `None'.
468 */
469
470 val b = new StringBuilder;
471 val n = s.length;
472 var i = pos;
473 var q = 0;
474
475 /* Skip whitespace while we find the next token. */
476 while (i < n && s(i).isWhitespace) i += 1;
477
478 /* Maybe there just isn't anything to find. */
479 if (i >= n) return None;
480
481 /* There is something there. Unpick the quoting and escaping. */
0157de02 482 while (i < n && (q || !s(i).isWhitespace)) {
8eabb4ff
MW
483 s(i) match {
484 case '\\' =>
485 if (i + 1 >= n) throw new InvalidQuotingException("trailing `\\'");
486 b += s(i + 1); i += 2;
487 case ch@('"' | ''') =>
0157de02 488 if (!q) q = ch;
8eabb4ff
MW
489 else if (q == ch) q = 0;
490 else b += ch;
491 i += 1;
492 case ch =>
493 b += ch;
494 i += 1;
495 }
496 }
497
498 /* Check that the quoting was valid. */
0157de02 499 if (q) throw new InvalidQuotingException(s"unmatched `$q'");
8eabb4ff
MW
500
501 /* Skip whitespace before the next token. */
502 while (i < n && s(i).isWhitespace) i += 1;
503
504 /* We're done. */
505 Some((b.result, i))
506}
507
508def splitTokens(s: String, pos: Int = 0): Seq[String] = {
509 /* Return all of the tokens in string S into tokens, starting at POS. */
510
511 val b = List.newBuilder[String];
512 var i = pos;
513
c8292b34
MW
514 loopUnit { exit => nextToken(s, i) match {
515 case Some((w, j)) => b += w; i = j;
516 case None => exit;
517 } }
8eabb4ff
MW
518 b.result
519}
520
0157de02
MW
521/*----- Hooks -------------------------------------------------------------*/
522
523/* This is a really simple publisher/subscriber system. The only slight
524 * tweak -- and the reason I'm not just using the Scala machinery -- is that
525 * being attached to a hook doesn't prevent the client from being garbage
526 * collected.
527 */
528
529trait BaseHookClient[E] {
530 /* The minimal requirements for a hook client. Honestly you should be
531 * using `HookClient' instead.
532 */
533
534 type H = Hook[E]; // the type of hook we attach to
535 def hook(hk: H, evt: E); // called with events from the hook
536}
537
538trait HookClient[E] extends BaseHookClient[E] {
539 /* The properly cooked hook client. This keeps track of which hooks we're
540 * attached to so we can release them all easily.
541 */
542
543 private val hooks = new HashSet[H];
544 protected def attachHook(hk: H) { hk.addHookClient(this); hooks += hk; }
545 protected def detachHook(hk: H) { hk.rmHookClient(this); hooks -= hk; }
546 protected def detachAllHooks()
547 { for (hk <- hooks) hk.rmHookClient(this); hooks.clear(); }
548}
549
550trait Hook[E] {
551 type C = BaseHookClient[E];
552 private val clients = new WeakHashMap[C, Unit];
553 def addHookClient(c: C) { clients(c) = (); }
554 def rmHookClient(c: C) { clients -= c; }
555 protected def callHook(evt: E)
556 { for (c <- clients.keys) c.hook(this, evt); }
557}
558
559/*----- Fluid variables ---------------------------------------------------*/
560
561object BaseFluid {
562 /* The multi-fluid `let' form is defined here so that it can access the
563 * `capture' method of individual fluids, but users should use the
564 * package-level veneer.
565 */
566
567 private[tripe] def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)
568 (body: => U): U = {
569 /* See the package-level `let' for details. */
570 val binds = for ((f, _) <- fxs) yield f.capture;
571 try { for ((f, x) <- fxs) f.v = x; body }
572 finally { for (b <- binds) b.restore(); }
573 }
574}
575def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)(body: => U): U = {
576 /* let(F -> X, ...) { BODY }
577 *
578 * Evaluate BODY in a dynamic context where each fluid F is bound to the
579 * corresponding value X.
580 */
581
582 BaseFluid.let(fxs: _*)(body);
583}
584
585trait BaseFluid[T] {
586 /* The basic fluid protocol. */
587
588 override def toString(): String =
589 f"${getClass.getName}%s@${hashCode}%x($v%s)";
590
591 protected trait Binding {
592 /* A captured binding which can be restored later. Implementing this is
593 * a subclass responsibility.
594 */
595
596 def restore();
597 /* Restore the fluid's state to the state captured here. */
598 }
599
600 /* Fetch and modify the current binding. */
601 def v: T;
602 def v_=(x: T);
603
604 protected def capture: Binding;
605 /* Capture and the current state of the fluid. */
606
607 def let[U](x: T)(body: => U): U = {
608 /* let(X) { BODY }
609 *
610 * Evaluate BODY in a dynamic context where the fluid is bound to the
611 * value X.
612 */
613
614 val b = capture;
615 try { v = x; body } finally { b.restore(); }
616 }
617}
618
619class SharedFluid[T](init: T) extends BaseFluid[T] {
620 /* A simple global fluid. It's probably a mistake to try to access a
621 * `SharedFluid' from multiple threads without serious synchronization.
622 */
623
624 var v: T = init;
625 private class Binding(old: T) extends super.Binding
626 { def restore() { v = old; } }
627 protected def capture: super.Binding = new Binding(v);
628}
629
630class ThreadFluid[T](init: T) extends BaseFluid[T] {
631 /* A thread-aware fluid. The top-level binding is truly global, shared by
632 * all threads, but `let'-bindings are thread-local.
633 */
634
635 private[this] var global: T = init;
636 private[this] var bound: ThreadLocal[Option[T]] = new ThreadLocal;
637 bound.set(None);
638
639 def v: T = bound.get match { case None => global; case Some(x) => x; };
640 def v_=(x: T) { bound.get match {
641 case None => global = x;
642 case _ => bound.set(Some(x));
643 } }
644
645 private class Binding(old: Option[T]) extends super.Binding
646 { def restore() { bound.set(old); } }
647 protected def capture: super.Binding = new Binding(bound.get);
648}
649
c8292b34
MW
650/*----- Other random things -----------------------------------------------*/
651
8eabb4ff 652trait LookaheadIterator[T] extends BufferedIterator[T] {
c8292b34
MW
653 /* An iterator in terms of a single `maybe there's another item' function.
654 *
655 * It seems like every time I write an iterator in Scala, the only way to
656 * find out whether there's a next item, for `hasNext', is to actually try
657 * to fetch it. So here's an iterator in terms of a function which goes
658 * off and maybe returns a next thing. It turns out to be easy to satisfy
659 * the additional requirements for `BufferedIterator', so why not?
660 */
661
662 /* Subclass responsibility. */
8eabb4ff 663 protected def fetch(): Option[T];
c8292b34
MW
664
665 /* The machinery. `st' is `None' if there's no current item, null if we've
666 * actually hit the end, or `Some(x)' if the current item is x.
667 */
668 private[this] var st: Option[T] = None;
8eabb4ff 669 private[this] def peek() {
c8292b34 670 /* Arrange to have a current item. */
8eabb4ff
MW
671 if (st == None) fetch() match {
672 case None => st = null;
673 case x@Some(_) => st = x;
674 }
675 }
c8292b34
MW
676
677 /* The `BufferedIterator' protocol. */
8eabb4ff 678 override def hasNext: Boolean = { peek(); st != null }
c8292b34 679 override def head: T =
8eabb4ff 680 { peek(); if (st == null) throw new NoSuchElementException; st.get }
c8292b34 681 override def next(): T = { val it = head; st = None; it }
8eabb4ff
MW
682}
683
c8292b34
MW
684def bufferedReader(r: Reader): BufferedReader = r match {
685 case br: BufferedReader => br
686 case _ => new BufferedReader(r)
687}
8eabb4ff 688
c8292b34
MW
689def lines(r: BufferedReader): BufferedIterator[String] =
690 new LookaheadIterator[String] {
691 /* Iterates over the lines of text in a `Reader' object. */
692 override protected def fetch() = Option(r.readLine());
693 }
694def lines(r: Reader): BufferedIterator[String] = lines(bufferedReader(r));
695
696def blocks(in: InputStream, blksz: Int):
697 BufferedIterator[(Array[Byte], Int)] =
698 /* Iterates over (possibly irregularly sized) blocks in a stream. */
699 new LookaheadIterator[(Array[Byte], Int)] {
700 val buf = new Array[Byte](blksz)
701 override protected def fetch() = {
702 val n = in.read(buf);
703 if (n < 0) None
704 else Some((buf, n))
705 }
706 }
707def blocks(in: InputStream):
04a5abae 708 BufferedIterator[(Array[Byte], Int)] = blocks(in, 65536);
c8292b34
MW
709
710def blocks(in: BufferedReader, blksz: Int):
711 BufferedIterator[(Array[Char], Int)] =
712 /* Iterates over (possibly irregularly sized) blocks in a reader. */
713 new LookaheadIterator[(Array[Char], Int)] {
714 val buf = new Array[Char](blksz)
715 override protected def fetch() = {
716 val n = in.read(buf);
717 if (n < 0) None
718 else Some((buf, n))
719 }
8eabb4ff 720 }
c8292b34 721def blocks(in: BufferedReader):
04a5abae 722 BufferedIterator[(Array[Char], Int)] = blocks(in, 65536);
c8292b34
MW
723def blocks(r: Reader, blksz: Int): BufferedIterator[(Array[Char], Int)] =
724 blocks(bufferedReader(r), blksz);
725def blocks(r: Reader): BufferedIterator[(Array[Char], Int)] =
726 blocks(bufferedReader(r));
727
728def oxford(conj: String, things: Seq[String]): String = things match {
729 case Seq() => "<nothing>"
730 case Seq(a) => a
731 case Seq(a, b) => s"$a $conj $b"
732 case Seq(a, tail@_*) =>
733 val sb = new StringBuilder;
734 sb ++= a; sb ++= ", ";
735 def iter(rest: Seq[String]) {
736 rest match {
737 case Seq() => unreachable;
738 case Seq(a) => sb ++= conj; sb += ' '; sb ++= a;
739 case Seq(a, tail@_*) => sb ++= a; sb ++= ", "; iter(tail);
740 }
741 }
742 iter(tail);
743 sb.result
8eabb4ff
MW
744}
745
a5ec891a
MW
746val datefmt = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
747
748def formatDuration(t: Int): String =
04a5abae
MW
749 if (t < -1) "???"
750 else {
751 val (s, t1) = (t%60, t/60);
752 val (m, h) = (t1%60, t1/60);
753 if (h > 0) f"$h%d:$m%02d:$s%02d"
754 else f"$m%02d:$s%02d"
755 }
756
8eabb4ff
MW
757/*----- That's all, folks -------------------------------------------------*/
758
759}