summaryrefslogtreecommitdiff
path: root/src/library/scala/collection/immutable/PagedSeq.scala
blob: 01854b17978aab4956dbe1e1d7b9c97e75269bf8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */



package scala
package collection
package immutable

import java.io.{File, FileReader, Reader}
import scala.reflect.ClassTag

/** The `PagedSeq` object defines a lazy implementations of
 *  a random access sequence.
 *
 * Provides utility methods that return instances of `PagedSeq[Char]`.
 * `fromIterator` and `fromIterable` provide generalised instances of `PagedSeq`
 *  @since 2.7
 */
@deprecated("this object will be moved to the scala-parser-combinators module", "2.11.8")
object PagedSeq {
  final val UndeterminedEnd = Int.MaxValue

  /** Constructs a paged sequence from an iterator */
  def fromIterator[T: ClassTag](source: Iterator[T]): PagedSeq[T] =
    new PagedSeq[T]((data: Array[T], start: Int, len: Int) => {
      var i = 0
      while (i < len && source.hasNext) {
        data(start + i) = source.next()
        i += 1
      }
      if (i == 0) -1 else i
    })

  /** Constructs a paged sequence from an iterable */
  def fromIterable[T: ClassTag](source: Iterable[T]): PagedSeq[T] =
    fromIterator(source.iterator)

  /** Constructs a paged character sequence from a string iterator */
  def fromStrings(source: Iterator[String]): PagedSeq[Char] = {
    var current: String = ""
    def more(data: Array[Char], start: Int, len: Int): Int =
      if (current.length != 0) {
        val cnt = current.length min len
        current.getChars(0, cnt, data, start)
        current = current.substring(cnt)
        if (cnt == len) cnt
        else (more(data, start + cnt, len - cnt) max 0) + cnt
      } else if (source.hasNext) {
        current = source.next()
        more(data, start, len)
      } else -1
    new PagedSeq(more(_: Array[Char], _: Int, _: Int))
  }

  /** Constructs a paged character sequence from a string iterable */
  def fromStrings(source: Iterable[String]): PagedSeq[Char] =
    fromStrings(source.iterator)

  /** Constructs a paged character sequence from a line iterator
   *  Lines do not contain trailing `\n` characters; The method inserts
   *  a line separator `\n` between any two lines in the sequence.
   */
  def fromLines(source: Iterator[String]): PagedSeq[Char] = {
    var isFirst = true
    fromStrings(source map { line =>
      if (isFirst) {
        isFirst = false
        line
      } else "\n"+line
    })
  }

  /** Constructs a paged character sequence from a line iterable
   *  Lines do not contain trailing `\n` characters; The method inserts
   *  a line separator `\n` between any two lines in the sequence.
   */
  def fromLines(source: Iterable[String]): PagedSeq[Char] =
    fromLines(source.iterator)

  /** Constructs a paged character sequence from an input reader
   */
  def fromReader(source: Reader): PagedSeq[Char] =
    new PagedSeq(source.read(_: Array[Char], _: Int, _: Int))

  /** Constructs a paged character sequence from an input file
   */
  def fromFile(source: File): PagedSeq[Char] =
    fromReader(new FileReader(source))

  /** Constructs a paged character sequence from a file with given name
   */
  def fromFile(source: String): PagedSeq[Char] =
    fromFile(new File(source))

  /** Constructs a paged character sequence from a scala.io.Source value
   */
  def fromSource(source: scala.io.Source) =
    fromLines(source.getLines())
}


import PagedSeq._

/** An implementation of lazily computed sequences, where elements are stored
 *  in "pages", i.e. arrays of fixed size.
 *
 *  A paged sequence is constructed from a function that produces more elements when asked.
 *  The producer function - `more`, is similar to the read method in java.io.Reader.
 *  The `more` function takes three parameters: an array of elements, a start index, and an end index.
 *  It should try to fill the array between start and end indices (excluding end index).
 *  It returns the number of elements produced, or -1 if end of logical input stream was reached
 *  before reading any element.
 *
 *  @tparam T     the type of the elements contained in this paged sequence, with an `ClassTag` context bound.
 *
 *  @author Martin Odersky
 *  @since  2.7
 *  @define Coll `PagedSeq`
 *  @define coll paged sequence
 *  @define mayNotTerminateInf
 *  @define willNotTerminateInf
 */
@deprecated("this class will be moved to the scala-parser-combinators module", "2.11.8")
class PagedSeq[T: ClassTag] protected(
  more: (Array[T], Int, Int) => Int,
  first1: Page[T],
  start: Int,
  end: Int)
extends scala.collection.AbstractSeq[T]
   with scala.collection.IndexedSeq[T]
{
  def this(more: (Array[T], Int, Int) => Int) = this(more, new Page[T](0), 0, UndeterminedEnd)

  private var current: Page[T] = first1

  private def latest = first1.latest

  private def addMore() = latest.addMore(more)

  private def page(absindex: Int) = {
    if (absindex < current.start)
      current = first1
    while (absindex >= current.end && current.next != null)
      current = current.next
    while (absindex >= current.end && !current.isLast) {
      current = addMore()
    }
    current
  }

  /** The length of the paged sequence
   *  @note Calling this method will force the entire sequence to be read.
   */
  def length: Int = {
    while (!latest.isLast && latest.end < end) addMore()
    (latest.end min end) - start
  }

  /** The element at position `index`.
   */
  def apply(index: Int) =
    if (isDefinedAt(index)) page(index + start)(index + start)
    else throw new IndexOutOfBoundsException(index.toString)

  /** Predicate method to check if an element is defined
   *  at position `index` of the current sequence.
   *  Unlike `length` this operation does not force reading
   *  a lazy sequence to the end.
   */
  override def isDefinedAt(index: Int) =
    index >= 0 && index < end - start && {
      val absidx = index + start
      absidx >= 0 && absidx < page(absidx).end
    }

   /** The subsequence from index `start` up to `end -1` if `end`
   *   is lesser than the length of the current sequence and up to
   *   length of the sequence otherwise. This is limited up to the length
   *   of the current sequence if `end` is larger than its length.
   */
  override def slice(_start: Int, _end: Int): PagedSeq[T] = {
    page(start)
    val s = start + _start
    val e = if (_end == UndeterminedEnd) _end else start + _end
    var f = first1
    while (f.end <= s && !f.isLast) {
      if (f.next eq null) f = f.addMore(more)
      else f = f.next
    }
    // Warning -- not refining `more` means that slices can freely request and obtain
    // data outside of their slice.  This is part of the design of PagedSeq
    // (to read pages!) but can be surprising.
    new PagedSeq(more, f, s, e)
  }

  /** The subsequence from index `start` up to
   *  the length of the current sequence.
   */
  def slice(start: Int): PagedSeq[T] = slice(start, UndeterminedEnd)

  /** Convert sequence to string */
  override def toString = {
    val buf = new StringBuilder
    for (ch <- PagedSeq.this.iterator) buf append ch
    buf.toString
  }
}


/** Page containing up to PageSize characters of the input sequence.
 */
private class Page[T: ClassTag](val num: Int) {

  private final val PageSize = 4096

  /** The next page in the sequence */
  var next  : Page[T] = null

  /** A later page in the sequence, serves a cache for pointing to last page */
  var later : Page[T] = this

  /** The number of elements read into this page */
  var filled: Int = 0

  /** Set true if the current page is the last in the sequence or if
  *   the `more` function returned -1 signalling end of input. */
  var isLast: Boolean = false

  /** The element array */
  final val data = new Array[T](PageSize)

  /** The index of the first element in this page relative to the whole sequence */
  final def start = num * PageSize

  /** The index of the element following the last element in this page relative
   *  to the whole sequence */
  final def end = start + filled

  /** The last page as currently present in the sequence; This can change as more
   *  elements get appended to the sequence.  */
  final def latest: Page[T] = {
    if (later.next != null) later = later.next.latest
    later
  }

  /** The element at the given sequence index.
   *  That index is relative to the whole sequence, not the page. */
  def apply(index: Int) = {
    if (index < start || index - start >= filled) throw new IndexOutOfBoundsException(index.toString)
    data(index - start)
  }

  /** Produces more elements by calling `more` and adds them on the current page,
   *  or fills a subsequent page if current page is full.
   *  @note If current page is full, it is the last one in the sequence.  */
  final def addMore(more: (Array[T], Int, Int) => Int): Page[T] =
    if (filled == PageSize) {
      next = new Page[T](num + 1)
      next.addMore(more)
    } else {
      val count = more(data, filled, PageSize - filled)
      if (count < 0) isLast = true
      else filled += count
      this
    }
}