| /* | |
| * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. | |
| * 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 jdk.internal.access.JavaLangAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.util.ByteArray; | |
| import static jdk.internal.util.ModifiedUtf.putChar; | |
| import static jdk.internal.util.ModifiedUtf.utfLen; | |
| /** | |
| * A data output stream lets an application write primitive Java data | |
| * types to an output stream in a portable way. An application can | |
| * then use a data input stream to read the data back in. A data output | |
| * stream wraps another output stream and delegates writing bytes to the | |
| * write methods of that output stream. Writing data consisting of more than | |
| * a single byte may cause several writes to the underlying output stream. | |
| * <p> | |
| * A DataOutputStream is not safe for use by multiple concurrent | |
| * threads. If a DataOutputStream is to be used by more than one | |
| * thread then access to the data output stream should be controlled | |
| * by appropriate synchronization. | |
| * | |
| * @see java.io.DataInputStream | |
| * @since 1.0 | |
| */ | |
| public class DataOutputStream extends FilterOutputStream implements DataOutput { | |
| private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); | |
| /** | |
| * The number of bytes written to the data output stream so far. | |
| * If this counter overflows, it will be wrapped to Integer.MAX_VALUE. | |
| */ | |
| protected int written; | |
| /** | |
| * bytearr is initialized on demand by writeUTF | |
| */ | |
| private byte[] bytearr = null; | |
| private final byte[] writeBuffer = new byte[8]; | |
| /** | |
| * Creates a new data output stream to write data to the specified | |
| * underlying output stream. The counter {@code written} is | |
| * set to zero. | |
| * | |
| * @param out the underlying output stream, to be saved for later | |
| * use. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public DataOutputStream(OutputStream out) { | |
| super(out); | |
| } | |
| /** | |
| * Increases the written counter by the specified value | |
| * until it reaches Integer.MAX_VALUE. | |
| */ | |
| private void incCount(int value) { | |
| int temp = written + value; | |
| if (temp < 0) { | |
| temp = Integer.MAX_VALUE; | |
| } | |
| written = temp; | |
| } | |
| /** | |
| * Writes the specified byte (the low eight bits of the argument | |
| * {@code b}) to the underlying output stream. If no exception | |
| * is thrown, the counter {@code written} is incremented by | |
| * {@code 1}. | |
| * <p> | |
| * Implements the {@code write} method of {@code OutputStream}. | |
| * | |
| * @param b the {@code byte} to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public synchronized void write(int b) throws IOException { | |
| out.write(b); | |
| incCount(1); | |
| } | |
| /** | |
| * Writes {@code len} bytes from the specified byte array | |
| * starting at offset {@code off} to the underlying output stream. | |
| * If no exception is thrown, the counter {@code written} is | |
| * incremented by {@code len}. | |
| * | |
| * @param b the data. | |
| * @param off the start offset in the data. | |
| * @param len the number of bytes to write. | |
| * @throws IOException if an I/O error occurs. | |
| * @throws IndexOutOfBoundsException {@inheritDoc} | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public synchronized void write(byte[] b, int off, int len) | |
| throws IOException | |
| { | |
| out.write(b, off, len); | |
| incCount(len); | |
| } | |
| /** | |
| * Flushes this data output stream. This forces any buffered output | |
| * bytes to be written out to the stream. | |
| * <p> | |
| * The {@code flush} method of {@code DataOutputStream} | |
| * calls the {@code flush} method of its underlying output stream. | |
| * | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| * @see java.io.OutputStream#flush() | |
| */ | |
| public void flush() throws IOException { | |
| out.flush(); | |
| } | |
| /** | |
| * Writes a {@code boolean} to the underlying output stream as | |
| * a 1-byte value. The value {@code true} is written out as the | |
| * value {@code (byte)1}; the value {@code false} is | |
| * written out as the value {@code (byte)0}. If no exception is | |
| * thrown, the counter {@code written} is incremented by | |
| * {@code 1}. | |
| * | |
| * @param v a {@code boolean} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeBoolean(boolean v) throws IOException { | |
| out.write(v ? 1 : 0); | |
| incCount(1); | |
| } | |
| /** | |
| * Writes out a {@code byte} to the underlying output stream as | |
| * a 1-byte value. If no exception is thrown, the counter | |
| * {@code written} is incremented by {@code 1}. | |
| * | |
| * @param v a {@code byte} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeByte(int v) throws IOException { | |
| out.write(v); | |
| incCount(1); | |
| } | |
| /** | |
| * Writes a {@code short} to the underlying output stream as two | |
| * bytes, high byte first. If no exception is thrown, the counter | |
| * {@code written} is incremented by {@code 2}. | |
| * | |
| * @param v a {@code short} to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeShort(int v) throws IOException { | |
| ByteArray.setUnsignedShort(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 2); | |
| incCount(2); | |
| } | |
| /** | |
| * Writes a {@code char} to the underlying output stream as a | |
| * 2-byte value, high byte first. If no exception is thrown, the | |
| * counter {@code written} is incremented by {@code 2}. | |
| * | |
| * @param v a {@code char} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeChar(int v) throws IOException { | |
| ByteArray.setUnsignedShort(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 2); | |
| incCount(2); | |
| } | |
| /** | |
| * Writes an {@code int} to the underlying output stream as four | |
| * bytes, high byte first. If no exception is thrown, the counter | |
| * {@code written} is incremented by {@code 4}. | |
| * | |
| * @param v an {@code int} to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeInt(int v) throws IOException { | |
| ByteArray.setInt(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 4); | |
| incCount(4); | |
| } | |
| /** | |
| * Writes a {@code long} to the underlying output stream as eight | |
| * bytes, high byte first. In no exception is thrown, the counter | |
| * {@code written} is incremented by {@code 8}. | |
| * | |
| * @param v a {@code long} to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeLong(long v) throws IOException { | |
| ByteArray.setLong(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 8); | |
| incCount(8); | |
| } | |
| /** | |
| * Converts the float argument to an {@code int} using the | |
| * {@code floatToIntBits} method in class {@code Float}, | |
| * and then writes that {@code int} value to the underlying | |
| * output stream as a 4-byte quantity, high byte first. If no | |
| * exception is thrown, the counter {@code written} is | |
| * incremented by {@code 4}. | |
| * | |
| * @param v a {@code float} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| * @see java.lang.Float#floatToIntBits(float) | |
| */ | |
| public final void writeFloat(float v) throws IOException { | |
| ByteArray.setFloat(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 4); | |
| incCount(4); | |
| } | |
| /** | |
| * Converts the double argument to a {@code long} using the | |
| * {@code doubleToLongBits} method in class {@code Double}, | |
| * and then writes that {@code long} value to the underlying | |
| * output stream as an 8-byte quantity, high byte first. If no | |
| * exception is thrown, the counter {@code written} is | |
| * incremented by {@code 8}. | |
| * | |
| * @param v a {@code double} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| * @see java.lang.Double#doubleToLongBits(double) | |
| */ | |
| public final void writeDouble(double v) throws IOException { | |
| ByteArray.setDouble(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 8); | |
| incCount(8); | |
| } | |
| /** | |
| * Writes out the string to the underlying output stream as a | |
| * sequence of bytes. Each character in the string is written out, in | |
| * sequence, by discarding its high eight bits. If no exception is | |
| * thrown, the counter {@code written} is incremented by the | |
| * length of {@code s}. | |
| * | |
| * @param s a string of bytes to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeBytes(String s) throws IOException { | |
| int len = s.length(); | |
| for (int i = 0 ; i < len ; i++) { | |
| out.write((byte)s.charAt(i)); | |
| } | |
| incCount(len); | |
| } | |
| /** | |
| * Writes a string to the underlying output stream as a sequence of | |
| * characters. Each character is written to the data output stream as | |
| * if by the {@code writeChar} method. If no exception is | |
| * thrown, the counter {@code written} is incremented by twice | |
| * the length of {@code s}. | |
| * | |
| * @param s a {@code String} value to be written. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.DataOutputStream#writeChar(int) | |
| * @see java.io.FilterOutputStream#out | |
| */ | |
| public final void writeChars(String s) throws IOException { | |
| int len = s.length(); | |
| for (int i = 0 ; i < len ; i++) { | |
| int v = s.charAt(i); | |
| ByteArray.setUnsignedShort(writeBuffer, 0, v); | |
| out.write(writeBuffer, 0, 2); | |
| } | |
| incCount(len * 2); | |
| } | |
| /** | |
| * Writes a string to the underlying output stream using | |
| * <a href="DataInput.html#modified-utf-8">modified UTF-8</a> | |
| * encoding in a machine-independent manner. | |
| * <p> | |
| * First, two bytes are written to the output stream as if by the | |
| * {@code writeShort} method giving the number of bytes to | |
| * follow. This value is the number of bytes actually written out, | |
| * not the length of the string. Following the length, each character | |
| * of the string is output, in sequence, using the modified UTF-8 encoding | |
| * for the character. If no exception is thrown, the counter | |
| * {@code written} is incremented by the total number of | |
| * bytes written to the output stream. This will be at least two | |
| * plus the length of {@code str}, and at most two plus | |
| * thrice the length of {@code str}. | |
| * | |
| * @param str a string to be written. | |
| * @throws UTFDataFormatException if the modified UTF-8 encoding of | |
| * {@code str} would exceed 65535 bytes in length | |
| * @throws IOException if some other I/O error occurs. | |
| * @see #writeChars(String) | |
| */ | |
| public final void writeUTF(String str) throws IOException { | |
| writeUTF(str, this); | |
| } | |
| /** | |
| * Writes a string to the specified DataOutput using | |
| * <a href="DataInput.html#modified-utf-8">modified UTF-8</a> | |
| * encoding in a machine-independent manner. | |
| * <p> | |
| * First, two bytes are written to out as if by the {@code writeShort} | |
| * method giving the number of bytes to follow. This value is the number of | |
| * bytes actually written out, not the length of the string. Following the | |
| * length, each character of the string is output, in sequence, using the | |
| * modified UTF-8 encoding for the character. If no exception is thrown, the | |
| * counter {@code written} is incremented by the total number of | |
| * bytes written to the output stream. This will be at least two | |
| * plus the length of {@code str}, and at most two plus | |
| * thrice the length of {@code str}. | |
| * | |
| * @param str a string to be written. | |
| * @param out destination to write to | |
| * @return The number of bytes written out. | |
| * @throws UTFDataFormatException if the modified UTF-8 encoding of | |
| * {@code str} would exceed 65535 bytes in length | |
| * @throws IOException if some other I/O error occurs. | |
| */ | |
| @SuppressWarnings("deprecation") | |
| static int writeUTF(String str, DataOutput out) throws IOException { | |
| final int strlen = str.length(); | |
| int countNonZeroAscii = JLA.countNonZeroAscii(str); | |
| int utflen = utfLen(str, countNonZeroAscii); | |
| if (utflen > 65535 || /* overflow */ utflen < strlen) | |
| throw new UTFDataFormatException(tooLongMsg(str, utflen)); | |
| final byte[] bytearr; | |
| if (out instanceof DataOutputStream dos) { | |
| if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2))) | |
| dos.bytearr = new byte[(utflen*2) + 2]; | |
| bytearr = dos.bytearr; | |
| } else { | |
| bytearr = new byte[utflen + 2]; | |
| } | |
| int count = 0; | |
| ByteArray.setUnsignedShort(bytearr, count, utflen); | |
| count += 2; | |
| str.getBytes(0, countNonZeroAscii, bytearr, count); | |
| count += countNonZeroAscii; | |
| for (int i = countNonZeroAscii; i < strlen;) { | |
| count = putChar(bytearr, count, str.charAt(i++)); | |
| } | |
| out.write(bytearr, 0, utflen + 2); | |
| return utflen + 2; | |
| } | |
| private static String tooLongMsg(String s, int bits32) { | |
| int slen = s.length(); | |
| String head = s.substring(0, 8); | |
| String tail = s.substring(slen - 8, slen); | |
| // handle int overflow with max 3x expansion | |
| long actualLength = (long)slen + Integer.toUnsignedLong(bits32 - slen); | |
| return "encoded string (" + head + "..." + tail + ") too long: " | |
| + actualLength + " bytes"; | |
| } | |
| /** | |
| * Returns the current value of the counter {@code written}, | |
| * the number of bytes written to this data output stream so far. | |
| * If the counter overflows, it will be wrapped to Integer.MAX_VALUE. | |
| * | |
| * @return the value of the {@code written} field. | |
| * @see java.io.DataOutputStream#written | |
| */ | |
| public final int size() { | |
| return written; | |
| } | |
| } |