content: /*
   * Copyright (C) 2020 ActiveJ LLC.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package io.activej.bytebuf.util;
  
  import io.activej.bytebuf.ByteBuf;
  import io.activej.bytebuf.ByteBufPool;
  import io.activej.common.MemSize;
  
  import java.io.IOException;
  import java.io.Writer;
  import java.nio.ByteBuffer;
  import java.nio.CharBuffer;
  import java.nio.charset.Charset;
  import java.nio.charset.StandardCharsets;
  
  /**
   * An implementation of character {@link Writer}, that appends characters
   * it received to an allocated {@link ByteBuf}.
   * The byte buf can grow (via reallocation) so it is limited only by available RAM.
   * <p>
   * This is a useful proxy between ActiveJ {@link ByteBuf ByteBufs} and API's that use standard Java {@link Writer writers}.
   */
  @SuppressWarnings("NullableProblems")
  public final class ByteBufWriter extends Writer {
  	public static final MemSize INITIAL_BUF_SIZE = MemSize.kilobytes(2);
  
  	private final Charset charset;
  	private ByteBuf byteBuf;
  
  	private char[] surrogateBuffer;
  
  	public ByteBufWriter() {
  		this(INITIAL_BUF_SIZE, StandardCharsets.UTF_8);
  	}
  
  	public ByteBufWriter(MemSize initialSize) {
  		this(initialSize, StandardCharsets.UTF_8);
  	}
  
  	public ByteBufWriter(Charset charset) {
  		this(INITIAL_BUF_SIZE, charset);
  	}
  
  	public ByteBufWriter(MemSize initialSize, Charset charset) {
  		byteBuf = ByteBufPool.allocate(initialSize);
  		this.charset = charset;
  	}
  
  	@Override
  	public void write(char[] cbuf, int off, int len) {
  		ByteBuffer buffer = charset.encode(CharBuffer.wrap(cbuf, off, len));
  		this.byteBuf = ByteBufPool.append(byteBuf, buffer.array(), buffer.position(), buffer.limit());
  	}
  
  	// Override all writer methods without IOException since we never throw it
  
  	@Override
  	public void write(int c) {
  		// sneaky little buffering for proper encoding of surrogate character pairs
  		//noinspection SynchronizeOnNonFinalField - lock is from java.io.Writer
  		synchronized (lock) {
  			if (surrogateBuffer == null) {
  				surrogateBuffer = new char[2];
  			}
  			char ch = (char) c;
  			if (Character.isLowSurrogate(ch)) {
  				surrogateBuffer[1] = ch;
  				write(surrogateBuffer, 0, 2);
  			} else {
  				surrogateBuffer[0] = ch;
  				if (!Character.isHighSurrogate(ch)) {
  					write(surrogateBuffer, 0, 1);
  				}
  			}
  		}
  	}
  
  	@Override
  	public void write(char[] cbuf) {
  		try {
  			super.write(cbuf);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public void write(String str) {
  		try {
  			super.write(str);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public void write(String str, int off, int len) {
  		try {
  			super.write(str, off, len);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public Writer append(CharSequence csq) {
  		try {
  			return super.append(csq);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public Writer append(CharSequence csq, int start, int end) {
  		try {
  			return super.append(csq, start, end);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public Writer append(char c) {
  		try {
  			return super.append(c);
  		} catch (IOException ignored) {
  			throw new AssertionError("unreachable");
  		}
  	}
  
  	@Override
  	public void flush() {
  	}
  
  	@Override
  	public void close() {
  	}
  
  	public ByteBuf getBuf() {
  		return byteBuf;
  	}
  }
  