aboutsummaryrefslogtreecommitdiff
path: root/libjava/classpath/java/io/Reader.java
blob: 11a12f81262b8f04ec6ab73281ed31a564c0fb3b (plain)
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/* Reader.java -- base class of classes that read input as a stream of chars
   Copyright (C) 1998, 1999, 2000, 2003, 2004, 2005  Free Software Foundation

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package java.io;

import java.nio.CharBuffer;

/* Written using "Java Class Libraries", 2nd edition, plus online
 * API docs for JDK 1.2 beta from http://www.javasoft.com.
 * Status:  Believed complete and correct.
 */

/**
 * This abstract class forms the base of the hierarchy of classes that read
 * input as a stream of characters.  It provides a common set of methods for
 * reading characters from streams.  Subclasses implement and extend these
 * methods to read characters from a particular input source such as a file
 * or network connection.
 *
 * @author Per Bothner (bothner@cygnus.com)
 * @date April 21, 1998.
 * @author Aaron M. Renn (arenn@urbanophile.com)
 */
public abstract class Reader implements Closeable, Readable
{
  /**
   * This is the <code>Object</code> used for synchronizing critical code
   * sections.  Subclasses should use this variable instead of a
   * synchronized method or an explicit synchronization on <code>this</code>
   */
  protected Object lock;

  /**
    * Unitializes a <code>Reader</code> that will use the object
    * itself for synchronization of critical code sections.
    */
  protected Reader()
  {
    this.lock = this;
  }

  /**
    * Initializes a <code>Reader</code> that will use the specified
    * <code>Object</code> for synchronization of critical code sections.
    *
    * @param lock The <code>Object</code> to use for synchronization
    */
  protected Reader(Object lock)
  {
    this.lock = lock;
  }

  /**
   * Read chars from a stream and stores them into a caller
   * supplied buffer.  It starts storing the data at index <code>offset</code>
   * into the buffer and attempts to read <code>len</code> chars.  This method
   * can return before reading the number of chars requested.  The actual
   * number of chars read is returned as an int.  A -1 is returned to indicate
   * the end of the stream.
   * <p>
   * This method will block until some data can be read.
   * <p>
   * This method operates by calling the single char <code>read()</code> method
   * in a loop until the desired number of chars are read.  The read loop
   * stops short if the end of the stream is encountered or if an IOException
   * is encountered on any read operation except the first.  If the first
   * attempt to read a chars fails, the IOException is allowed to propagate
   * upward.  And subsequent IOException is caught and treated identically
   * to an end of stream condition.  Subclasses can (and should if possible)
   * override this method to provide a more efficient implementation.
   *
   * @param buf The array into which the chars read should be stored
   * @param offset The offset into the array to start storing chars
   * @param count The requested number of chars to read
   *
   * @return The actual number of chars read, or -1 if end of stream.
   *
   * @exception IOException If an error occurs.
   */
  public abstract int read(char buf[], int offset, int count)
    throws IOException;

  /**
   * Reads chars from a stream and stores them into a caller
   * supplied buffer.  This method attempts to completely fill the buffer,
   * but can return before doing so.  The actual number of chars read is
   * returned as an int.  A -1 is returned to indicate the end of the stream.
   * <p>
   * This method will block until some data can be read.
   * <p>
   * This method operates by calling an overloaded read method like so:
   * <code>read(buf, 0, buf.length)</code>
   *
   * @param buf The buffer into which the chars read will be stored.
   *
   * @return The number of chars read or -1 if end of stream.
   *
   * @exception IOException If an error occurs.
   */
  public int read(char buf[]) throws IOException
  {
    return read(buf, 0, buf.length);
  }

  /**
   * Reads an char from the input stream and returns it
   * as an int in the range of 0-65535.  This method also will return -1 if
   * the end of the stream has been reached.
   * <p>
   * This method will block until the char can be read.
   *
   * @return The char read or -1 if end of stream
   *
   * @exception IOException If an error occurs
   */
  public int read() throws IOException
  {
    char[] buf = new char[1];
    int count = read(buf, 0, 1);
    return count > 0 ? buf[0] : -1;
  }

  /** @since 1.5 */
  public int read(CharBuffer buffer) throws IOException
  {
    // We want to call put(), so we don't manipulate the CharBuffer
    // directly.
    int rem = buffer.remaining();
    char[] buf = new char[rem];
    int result = read(buf, 0, rem);
    if (result != -1)
      buffer.put(buf, 0, result);
    return result;
  }

  /**
   * Closes the stream.  Any futher attempts to read from the
   * stream may generate an <code>IOException</code>.
   *
   * @exception IOException If an error occurs
   */
  public abstract void close() throws IOException;

  /**
   * Returns a boolean that indicates whether the mark/reset
   * methods are supported in this class.  Those methods can be used to
   * remember a specific point in the stream and reset the stream to that
   * point.
   * <p>
   * This method always returns <code>false</code> in this class, but
   * subclasses can override this method to return <code>true</code> if they
   * support mark/reset functionality.
   *
   * @return <code>true</code> if mark/reset functionality is supported,
   *         <code>false</code> otherwise
   *
   */
  public boolean markSupported()
  {
    return false;
  }

  /**
    * Marks a position in the input to which the stream can be
    * "reset" by calling the <code>reset()</code> method.  The parameter
    * <code>readlimit</code> is the number of chars that can be read from the
    * stream after setting the mark before the mark becomes invalid.  For
    * example, if <code>mark()</code> is called with a read limit of 10, then
    * when 11 chars of data are read from the stream before the
    * <code>reset()</code> method is called, then the mark is invalid and the
    * stream object instance is not required to remember the mark.
    *
    * @param readLimit The number of chars that can be read before the mark
    *        becomes invalid
    *
    * @exception IOException If an error occurs such as mark not being
    *            supported for this class
    */
  public void mark(int readLimit) throws IOException
  {
    throw new IOException("mark not supported");
  }

  /**
    * Resets a stream to the point where the <code>mark()</code>
    * method was called.  Any chars that were read after the mark point was
    * set will be re-read during subsequent reads.
    * <p>
    * This method always throws an IOException in this class, but subclasses
    * can override this method if they provide mark/reset functionality.
    *
    * @exception IOException Always thrown for this class
    */
  public void reset() throws IOException
  {
    throw new IOException("reset not supported");
  }

  /**
    * Determines whether or not this stream is ready to be
    * read.  If it returns <code>false</code> the stream may block if a
    * read is attempted, but it is not guaranteed to do so.
    * <p>
    * This method always returns <code>false</code> in this class
    *
    * @return <code>true</code> if the stream is ready to be read,
    * <code>false</code> otherwise.
    *
    * @exception IOException If an error occurs
    */
  public boolean ready() throws IOException
  {
    return false;
  }

  /**
    * Skips the specified number of chars in the stream.  It
    * returns the actual number of chars skipped, which may be less than the
    * requested amount.
    * <p>
    * This method reads and discards chars into a 256 char array until the
    * specified number of chars were skipped or until either the end of stream
    * is reached or a read attempt returns a short count.  Subclasses can
    * override this method to provide a more efficient implementation where
    * one exists.
    *
    * @param count The requested number of chars to skip
    *
    * @return The actual number of chars skipped.
    *
    * @exception IOException If an error occurs
    */
  public long skip(long count) throws IOException
  {
    if (count <= 0)
      return 0;
    int bsize = count > 1024 ? 1024 : (int) count;
    char[] buffer = new char[bsize];
    long todo = count;
    synchronized (lock)
    {
      while (todo > 0)
        {
          int skipped = read(buffer, 0, bsize > todo ? (int) todo : bsize);
          if (skipped <= 0)
            break;
          todo -= skipped;
        }
    }
    return count - todo;
  }
}
ar the entry first heap[elements] = null; elements--; if (elements + DEFAULT_SIZE / 2 <= (heap.length / 4)) { TimerTask new_heap[] = new TimerTask[heap.length / 2]; System.arraycopy(heap, 0, new_heap, 0, elements + 1); heap = new_heap; } } /** * Adds a task to the queue and puts it at the correct place * in the heap. */ public synchronized void enqueue(TimerTask task) { // Check if it is legal to add another element if (heap == null) { throw new IllegalStateException ("cannot enqueue when stop() has been called on queue"); } heap[0] = task; // sentinel add(task); // put the new task at the end // Now push the task up in the heap until it has reached its place int child = elements; int parent = child / 2; while (heap[parent].scheduled > task.scheduled) { heap[child] = heap[parent]; child = parent; parent = child / 2; } // This is the correct place for the new task heap[child] = task; heap[0] = null; // clear sentinel // Maybe sched() is waiting for a new element this.notify(); } /** * Returns the top element of the queue. * Can return null when no task is in the queue. */ private TimerTask top() { if (elements == 0) { return null; } else { return heap[1]; } } /** * Returns the top task in the Queue. * Removes the element from the heap and reorders the heap first. * Can return null when there is nothing in the queue. */ public synchronized TimerTask serve() { // The task to return TimerTask task = null; while (task == null) { // Get the next task task = top(); // return null when asked to stop // or if asked to return null when the queue is empty if ((heap == null) || (task == null && nullOnEmpty)) { return null; } // Do we have a task? if (task != null) { // The time to wait until the task should be served long time = task.scheduled - System.currentTimeMillis(); if (time > 0) { // This task should not yet be served // So wait until this task is ready // or something else happens to the queue task = null; // set to null to make sure we call top() try { this.wait(time); } catch (InterruptedException _) { } } } else { // wait until a task is added // or something else happens to the queue try { this.wait(); } catch (InterruptedException _) { } } } // reconstruct the heap TimerTask lastTask = heap[elements]; remove(); // drop lastTask at the beginning and move it down the heap int parent = 1; int child = 2; heap[1] = lastTask; while (child <= elements) { if (child < elements) { if (heap[child].scheduled > heap[child + 1].scheduled) { child++; } } if (lastTask.scheduled <= heap[child].scheduled) break; // found the correct place (the parent) - done heap[parent] = heap[child]; parent = child; child = parent * 2; } // this is the correct new place for the lastTask heap[parent] = lastTask; // return the task return task; } /** * When nullOnEmpty is true the serve() method will return null when * there are no tasks in the queue, otherwise it will wait until * a new element is added to the queue. It is used to indicate to * the scheduler that no new tasks will ever be added to the queue. */ public synchronized void setNullOnEmpty(boolean nullOnEmpty) { this.nullOnEmpty = nullOnEmpty; this.notify(); } /** * When this method is called the current and all future calls to * serve() will return null. It is used to indicate to the Scheduler * that it should stop executing since no more tasks will come. */ public synchronized void stop() { this.heap = null; this.elements = 0; this.notify(); } /** * Remove all canceled tasks from the queue. */ public synchronized int purge() { int removed = 0; // Null out any elements that are canceled. Skip element 0 as // it is the sentinel. for (int i = elements; i > 0; --i) { if (heap[i].scheduled < 0) { ++removed; // Remove an element by pushing the appropriate child // into place, and then iterating to the bottom of the // tree. int index = i; while (heap[index] != null) { int child = 2 * index; if (child >= heap.length) { // Off end; we're done. heap[index] = null; break; } if (child + 1 >= heap.length || heap[child + 1] == null) { // Nothing -- we're done. } else if (heap[child] == null || (heap[child].scheduled > heap[child + 1].scheduled)) ++child; heap[index] = heap[child]; index = child; } } } // Make a new heap if we shrank enough. int newLen = heap.length; while (elements - removed + DEFAULT_SIZE / 2 <= newLen / 4) newLen /= 2; if (newLen != heap.length) { TimerTask[] newHeap = new TimerTask[newLen]; System.arraycopy(heap, 0, newHeap, 0, elements + 1); heap = newHeap; } return removed; } } // TaskQueue /** * The scheduler that executes all the tasks on a particular TaskQueue, * reschedules any repeating tasks and that waits when no task has to be * executed immediately. Stops running when canceled or when the parent * Timer has been finalized and no more tasks have to be executed. */ private static final class Scheduler implements Runnable { // The priority queue containing all the TimerTasks. private TaskQueue queue; /** * Creates a new Scheduler that will schedule the tasks on the * given TaskQueue. */ public Scheduler(TaskQueue queue) { this.queue = queue; } public void run() { TimerTask task; while ((task = queue.serve()) != null) { // If this task has not been canceled if (task.scheduled >= 0) { // Mark execution time task.lastExecutionTime = task.scheduled; // Repeatable task? if (task.period < 0) { // Last time this task is executed task.scheduled = -1; } // Run the task try { task.run(); } catch (ThreadDeath death) { // If an exception escapes, the Timer becomes invalid. queue.stop(); throw death; } catch (Throwable t) { // If an exception escapes, the Timer becomes invalid. queue.stop(); } } // Calculate next time and possibly re-enqueue. if (task.scheduled >= 0) { if (task.fixed) { task.scheduled += task.period; } else { task.scheduled = task.period + System.currentTimeMillis(); } try { queue.enqueue(task); } catch (IllegalStateException ise) { // Ignore. Apparently the Timer queue has been stopped. } } } } } // Scheduler // Number of Timers created. // Used for creating nice Thread names. private static int nr; // The queue that all the tasks are put in. // Given to the scheduler private TaskQueue queue; // The Scheduler that does all the real work private Scheduler scheduler; // Used to run the scheduler. // Also used to checked if the Thread is still running by calling // thread.isAlive(). Sometimes a Thread is suddenly killed by the system // (if it belonged to an Applet). private Thread thread; // When cancelled we don't accept any more TimerTasks. private boolean canceled; /** * Creates a new Timer with a non daemon Thread as Scheduler, with normal * priority and a default name. */ public Timer() { this(false); } /** * Creates a new Timer with a daemon Thread as scheduler if daemon is true, * with normal priority and a default name. */ public Timer(boolean daemon) { this(daemon, Thread.NORM_PRIORITY); } /** * Create a new Timer whose Thread has the indicated name. It will have * normal priority and will not be a daemon thread. * @param name the name of the Thread * @since 1.5 */ public Timer(String name) { this(false, Thread.NORM_PRIORITY, name); } /** * Create a new Timer whose Thread has the indicated name. It will have * normal priority. The boolean argument controls whether or not it * will be a daemon thread. * @param name the name of the Thread * @param daemon true if the Thread should be a daemon thread * @since 1.5 */ public Timer(String name, boolean daemon) { this(daemon, Thread.NORM_PRIORITY, name); } /** * Creates a new Timer with a daemon Thread as scheduler if daemon is true, * with the priority given and a default name. */ private Timer(boolean daemon, int priority) { this(daemon, priority, "Timer-" + (++nr)); } /** * Creates a new Timer with a daemon Thread as scheduler if daemon is true, * with the priority and name given.E */ private Timer(boolean daemon, int priority, String name) { canceled = false; queue = new TaskQueue(); scheduler = new Scheduler(queue); thread = new Thread(scheduler, name); thread.setDaemon(daemon); thread.setPriority(priority); thread.start(); } /** * Cancels the execution of the scheduler. If a task is executing it will * normally finish execution, but no other tasks will be executed and no * more tasks can be scheduled. */ public void cancel() { canceled = true; queue.stop(); } /** * Schedules the task at Time time, repeating every period * milliseconds if period is positive and at a fixed rate if fixed is true. * * @exception IllegalArgumentException if time is negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ private void schedule(TimerTask task, long time, long period, boolean fixed) { if (time < 0) throw new IllegalArgumentException("negative time"); if (task.scheduled == 0 && task.lastExecutionTime == -1) { task.scheduled = time; task.period = period; task.fixed = fixed; } else { throw new IllegalStateException ("task was already scheduled or canceled"); } if (!this.canceled && this.thread != null) { queue.enqueue(task); } else { throw new IllegalStateException ("timer was canceled or scheduler thread has died"); } } private static void positiveDelay(long delay) { if (delay < 0) { throw new IllegalArgumentException("delay is negative"); } } private static void positivePeriod(long period) { if (period < 0) { throw new IllegalArgumentException("period is negative"); } } /** * Schedules the task at the specified data for one time execution. * * @exception IllegalArgumentException if date.getTime() is negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void schedule(TimerTask task, Date date) { long time = date.getTime(); schedule(task, time, -1, false); } /** * Schedules the task at the specified date and reschedules the task every * period milliseconds after the last execution of the task finishes until * this timer or the task is canceled. * * @exception IllegalArgumentException if period or date.getTime() is * negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void schedule(TimerTask task, Date date, long period) { positivePeriod(period); long time = date.getTime(); schedule(task, time, period, false); } /** * Schedules the task after the specified delay milliseconds for one time * execution. * * @exception IllegalArgumentException if delay or * System.currentTimeMillis + delay is negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void schedule(TimerTask task, long delay) { positiveDelay(delay); long time = System.currentTimeMillis() + delay; schedule(task, time, -1, false); } /** * Schedules the task after the delay milliseconds and reschedules the * task every period milliseconds after the last execution of the task * finishes until this timer or the task is canceled. * * @exception IllegalArgumentException if delay or period is negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void schedule(TimerTask task, long delay, long period) { positiveDelay(delay); positivePeriod(period); long time = System.currentTimeMillis() + delay; schedule(task, time, period, false); } /** * Schedules the task at the specified date and reschedules the task at a * fixed rate every period milliseconds until this timer or the task is * canceled. * * @exception IllegalArgumentException if period or date.getTime() is * negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void scheduleAtFixedRate(TimerTask task, Date date, long period) { positivePeriod(period); long time = date.getTime(); schedule(task, time, period, true); } /** * Schedules the task after the delay milliseconds and reschedules the task * at a fixed rate every period milliseconds until this timer or the task * is canceled. * * @exception IllegalArgumentException if delay or * System.currentTimeMillis + delay is negative * @exception IllegalStateException if the task was already scheduled or * canceled or this Timer is canceled or the scheduler thread has died */ public void scheduleAtFixedRate(TimerTask task, long delay, long period) { positiveDelay(delay); positivePeriod(period); long time = System.currentTimeMillis() + delay; schedule(task, time, period, true); } /** * Tells the scheduler that the Timer task died * so there will be no more new tasks scheduled. */ protected void finalize() throws Throwable { queue.setNullOnEmpty(true); } /** * Removes all cancelled tasks from the queue. * @return the number of tasks removed * @since 1.5 */ public int purge() { return queue.purge(); } }