/*
 * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
 * Copyright (C) 2014-2020 MicroEJ Corp. - EDC compliance and optimizations.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.io;

import ej.annotation.Nullable;

/**
 * Piped character-output streams.
 *
 * @author Mark Reinhold
 * @since JDK1.1
 */

public class PipedWriter extends Writer {

	/*
	 * REMIND: identification of the read and write sides needs to be more sophisticated. Either using thread groups
	 * (but what about pipes within a thread?) or using finalization (but it may be a long time until the next GC).
	 */
	@Nullable
	private PipedReader sink;

	/*
	 * This flag records the open status of this particular writer. It is independent of the status flags defined in
	 * PipedReader. It is used to do a sanity check on connect.
	 */
	private boolean closed = false;

	/**
	 * Creates a piped writer connected to the specified piped reader. Data characters written to this stream will then
	 * be available as input from <code>snk</code>.
	 *
	 * @param snk
	 *            The piped reader to connect to.
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	public PipedWriter(PipedReader snk) throws IOException {
		connect(snk);
	}

	/**
	 * Creates a piped writer that is not yet connected to a piped reader. It must be connected to a piped reader,
	 * either by the receiver or the sender, before being used.
	 *
	 * @see java.io.PipedReader#connect(java.io.PipedWriter)
	 * @see java.io.PipedWriter#connect(java.io.PipedReader)
	 */
	public PipedWriter() {
	}

	/**
	 * Connects this piped writer to a receiver. If this object is already connected to some other piped reader, an
	 * <code>IOException</code> is thrown.
	 * <p>
	 * If <code>snk</code> is an unconnected piped reader and <code>src</code> is an unconnected piped writer, they may
	 * be connected by either the call: <blockquote>
	 *
	 * <pre>
	 * src.connect(snk)
	 * </pre>
	 *
	 * </blockquote> or the call: <blockquote>
	 *
	 * <pre>
	 * snk.connect(src)
	 * </pre>
	 *
	 * </blockquote> The two calls have the same effect.
	 *
	 * @param snk
	 *            the piped reader to connect to.
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	public synchronized void connect(PipedReader snk) throws IOException {
		if (snk.connected || this.sink != null) { // throws NullPointerException in case snk is null, which it shouldn't
													// be
			throw new IOException("Already connected");
		} else if (snk.closedByReader || this.closed) {
			throw new IOException("Pipe closed");
		}

		this.sink = snk;
		snk.in = -1;
		snk.out = 0;
		snk.connected = true;
	}

	/**
	 * Writes the specified <code>char</code> to the piped output stream. If a thread was reading data characters from
	 * the connected piped input stream, but the thread is no longer alive, then an <code>IOException</code> is thrown.
	 * <p>
	 * Implements the <code>write</code> method of <code>Writer</code>.
	 *
	 * @param c
	 *            the <code>char</code> to be written.
	 * @exception IOException
	 *                if the pipe is <a href=PipedOutputStream.html#BROKEN> <code>broken</code></a>,
	 *                {@link #connect(java.io.PipedReader) unconnected}, closed or an I/O error occurs.
	 */
	@Override
	public void write(int c) throws IOException {
		PipedReader sink = this.sink;
		if (sink == null) {
			throw new IOException("Pipe not connected");
		}
		sink.receive(c);
	}

	/**
	 * Writes <code>len</code> characters from the specified character array starting at offset <code>off</code> to this
	 * piped output stream. This method blocks until all the characters are written to the output stream. If a thread
	 * was reading data characters from the connected piped input stream, but the thread is no longer alive, then an
	 * <code>IOException</code> is thrown.
	 *
	 * @param cbuf
	 *            the data.
	 * @param off
	 *            the start offset in the data.
	 * @param len
	 *            the number of characters to write.
	 * @exception IOException
	 *                if the pipe is <a href=PipedOutputStream.html#BROKEN> <code>broken</code></a>,
	 *                {@link #connect(java.io.PipedReader) unconnected}, closed or an I/O error occurs.
	 */
	@Override
	public void write(char cbuf[], int off, int len) throws IOException {
		PipedReader sink = this.sink;
		if (sink == null) {
			throw new IOException("Pipe not connected");
		} else if ((off | len | (off + len) | (cbuf.length - (off + len))) < 0) {
			throw new IndexOutOfBoundsException();
		}
		sink.receive(cbuf, off, len);
	}

	/**
	 * Flushes this output stream and forces any buffered output characters to be written out. This will notify any
	 * readers that characters are waiting in the pipe.
	 *
	 * @exception IOException
	 *                if the pipe is closed, or an I/O error occurs.
	 */
	@Override
	public synchronized void flush() throws IOException {
		PipedReader sink = this.sink;
		if (sink != null) {
			if (sink.closedByReader || this.closed) {
				throw new IOException("Pipe closed");
			}
			synchronized (sink) {
				sink.notifyAll();
			}
		}
	}

	/**
	 * Closes this piped output stream and releases any system resources associated with this stream. This stream may no
	 * longer be used for writing characters.
	 *
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	@Override
	public void close() throws IOException {
		PipedReader sink = this.sink;
		this.closed = true;
		if (sink != null) {
			sink.receivedLast();
		}
	}
}
