001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.stream; 018 019import java.io.*; 020import java.math.*; 021 022import org.apache.juneau.marshall.serializer.*; 023 024/** 025 * Public low-level push-generator that emits a Juneau-formatted document one structural event at a 026 * time. 027 * 028 * <p> 029 * The caller emits structural events ({@link #startObject()} / {@link #fieldName(String)} / 030 * {@link #number(long)} / ...) and the implementation produces the corresponding bytes for its 031 * format. This is a format-neutral surface; the same writer methods work across every Juneau 032 * format that implements {@link TokenWritable} (the structural-token role). 033 * 034 * <h5 class='section'>Usage:</h5> 035 * <p class='bjava'> 036 * StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(); 037 * <jk>try</jk> (TokenWriter <jv>w</jv> = Json.<jsf>DEFAULT</jsf>.writeTokens(<jv>sb</jv>)) { 038 * <jv>w</jv>.startObject(); 039 * <jv>w</jv>.fieldName(<js>"a"</js>); 040 * <jv>w</jv>.number(1); 041 * <jv>w</jv>.fieldName(<js>"b"</js>); 042 * <jv>w</jv>.startArray(); 043 * <jv>w</jv>.bool(<jk>true</jk>); 044 * <jv>w</jv>.nil(); 045 * <jv>w</jv>.endArray(); 046 * <jv>w</jv>.endObject(); 047 * } 048 * <jc>// sb is now: {"a":1,"b":[true,null]}</jc> 049 * </p> 050 * 051 * <h5 class='section'>Contract:</h5> 052 * <ul> 053 * <li>The writer is <b>purely structural</b>. It bypasses object swaps and {@code @Schema} 054 * annotations — that is the role of the POJO databind path on 055 * {@link Serializer Serializer} and its bridges. 056 * <li>It is the caller's responsibility to emit a well-formed event sequence (every 057 * {@code startObject()} matched by an {@code endObject()}, every field name followed by 058 * exactly one value or container, etc.). Implementations are not required to validate the 059 * sequence, though they may. 060 * <li>Concrete implementations are not required to be thread-safe. 061 * </ul> 062 * 063 * <h5 class='section'>See Also:</h5><ul> 064 * <li>{@link TokenReader} — the pull-side counterpart. 065 * <li>{@link TokenType} — the format-neutral token vocabulary. 066 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a> 067 * </ul> 068 */ 069@SuppressWarnings({ 070 "resource" // TokenWriter wraps the caller's writer; the underlying Closeable is caller-owned, so the resource-leak warning is by design. 071}) 072public interface TokenWriter extends RecordWriter { 073 074 /** 075 * Emits a {@link TokenType#START_OBJECT} event. 076 * 077 * @return This object. 078 * @throws IOException If a problem occurred writing to the underlying stream. 079 * @throws SerializeException If the format cannot represent this event in the current state. 080 */ 081 TokenWriter startObject() throws IOException; 082 083 /** 084 * Emits a {@link TokenType#END_OBJECT} event. 085 * 086 * @return This object. 087 * @throws IOException If a problem occurred writing to the underlying stream. 088 * @throws SerializeException If the format cannot represent this event in the current state. 089 */ 090 TokenWriter endObject() throws IOException; 091 092 /** 093 * Emits a {@link TokenType#START_ARRAY} event. 094 * 095 * @return This object. 096 * @throws IOException If a problem occurred writing to the underlying stream. 097 * @throws SerializeException If the format cannot represent this event in the current state. 098 */ 099 TokenWriter startArray() throws IOException; 100 101 /** 102 * Emits a {@link TokenType#END_ARRAY} event. 103 * 104 * @return This object. 105 * @throws IOException If a problem occurred writing to the underlying stream. 106 * @throws SerializeException If the format cannot represent this event in the current state. 107 */ 108 TokenWriter endArray() throws IOException; 109 110 /** 111 * Emits a {@link TokenType#FIELD_NAME} event for the next field of the enclosing object. 112 * 113 * @param name The field name. Must not be <jk>null</jk>. 114 * @return This object. 115 * @throws IOException If a problem occurred writing to the underlying stream. 116 * @throws SerializeException If the format cannot represent this event in the current state. 117 */ 118 TokenWriter fieldName(String name) throws IOException; 119 120 /** 121 * Emits a {@link TokenType#VALUE_STRING} event. 122 * 123 * @param value The value. May be <jk>null</jk>, in which case a {@link TokenType#VALUE_NULL} 124 * event is emitted instead. 125 * @return This object. 126 * @throws IOException If a problem occurred writing to the underlying stream. 127 * @throws SerializeException If the format cannot represent this event in the current state. 128 */ 129 TokenWriter string(String value) throws IOException; 130 131 /** 132 * Emits a {@link TokenType#VALUE_NUMBER} event for an arbitrary {@link Number}. 133 * 134 * @param value The value. May be <jk>null</jk>, in which case a {@link TokenType#VALUE_NULL} 135 * event is emitted instead. 136 * @return This object. 137 * @throws IOException If a problem occurred writing to the underlying stream. 138 * @throws SerializeException If the format cannot represent this event in the current state. 139 */ 140 TokenWriter number(Number value) throws IOException; 141 142 /** 143 * Emits a {@link TokenType#VALUE_NUMBER} event for a {@code long}. 144 * 145 * @param value The value. 146 * @return This object. 147 * @throws IOException If a problem occurred writing to the underlying stream. 148 * @throws SerializeException If the format cannot represent this event in the current state. 149 */ 150 TokenWriter number(long value) throws IOException; 151 152 /** 153 * Emits a {@link TokenType#VALUE_NUMBER} event for a {@code double}. 154 * 155 * @param value The value. 156 * @return This object. 157 * @throws IOException If a problem occurred writing to the underlying stream. 158 * @throws SerializeException If the format cannot represent this event in the current state. 159 */ 160 TokenWriter number(double value) throws IOException; 161 162 /** 163 * Emits a {@link TokenType#VALUE_NUMBER} event for a {@link BigDecimal}. 164 * 165 * @param value The value. May be <jk>null</jk>, in which case a {@link TokenType#VALUE_NULL} 166 * event is emitted instead. 167 * @return This object. 168 * @throws IOException If a problem occurred writing to the underlying stream. 169 * @throws SerializeException If the format cannot represent this event in the current state. 170 */ 171 TokenWriter number(BigDecimal value) throws IOException; 172 173 /** 174 * Emits a {@link TokenType#VALUE_NUMBER} event for a {@link BigInteger}. 175 * 176 * @param value The value. May be <jk>null</jk>, in which case a {@link TokenType#VALUE_NULL} 177 * event is emitted instead. 178 * @return This object. 179 * @throws IOException If a problem occurred writing to the underlying stream. 180 * @throws SerializeException If the format cannot represent this event in the current state. 181 */ 182 TokenWriter number(BigInteger value) throws IOException; 183 184 /** 185 * Emits a {@link TokenType#VALUE_BOOLEAN} event. 186 * 187 * @param value The value. 188 * @return This object. 189 * @throws IOException If a problem occurred writing to the underlying stream. 190 * @throws SerializeException If the format cannot represent this event in the current state. 191 */ 192 TokenWriter bool(boolean value) throws IOException; 193 194 /** 195 * Emits a {@link TokenType#VALUE_NULL} event. 196 * 197 * @return This object. 198 * @throws IOException If a problem occurred writing to the underlying stream. 199 * @throws SerializeException If the format cannot represent this event in the current state. 200 */ 201 TokenWriter nil() throws IOException; 202 203 /** 204 * Emits an arbitrary POJO through the format's structural events. 205 * 206 * <p> 207 * The value is walked via {@link PojoWalker}: nulls / scalars / collections / arrays / maps / 208 * beans are introspected and re-emitted through this writer's 209 * {@link #startObject()}, {@link #fieldName(String)}, {@link #string(String)}, 210 * {@link #number(long)}, {@link #bool(boolean)}, {@link #nil()}, and {@link #binary(byte[])} 211 * methods. Per-format encoding (quoting, indentation, newlines, etc.) is whatever this 212 * writer is configured for. 213 * 214 * <p> 215 * <b>Cursor positioning:</b> the writer must be in a state where a value emit is legal — 216 * at root, after a {@link #fieldName(String)} call, or inside an array. Calling 217 * {@code object(...)} from inside an object body without a preceding field name throws 218 * {@link IllegalStateException}, same as calling {@link #string(String)} would. 219 * 220 * <p> 221 * <b>Default behavior:</b> the default implementation throws 222 * {@link UnsupportedOperationException}. Format-specific token writers override this by 223 * delegating to {@link PojoWalker#walk(TokenWriter, Object)}. 224 * 225 * <p> 226 * <b>Swaps and {@code @Schema}:</b> this method is purely structural. Object swaps and 227 * schema annotations are NOT applied at the token layer — use the POJO databind path on 228 * {@link Serializer#write(Object)} for those. 229 * 230 * @param value The value to emit. May be <jk>null</jk>. 231 * @return This object. 232 * @throws IOException If the underlying stream fails. 233 */ 234 default TokenWriter object(Object value) throws IOException { 235 throw new UnsupportedOperationException( 236 "object(Object) is not implemented for " + getClass().getName()); 237 } 238 239 /** 240 * Implements {@link RecordWriter#write(Object)} by delegating to {@link #object(Object)}. 241 * 242 * @param value The value. May be <jk>null</jk>. 243 * @return This object. 244 * @throws IOException If a problem occurred writing to the underlying stream. 245 */ 246 @Override 247 default TokenWriter write(Object value) throws IOException { 248 object(value); 249 return this; 250 } 251 252 /** 253 * Emits a {@link TokenType#VALUE_BINARY} event. 254 * 255 * <p> 256 * Text formats normally encode the value as base64-encoded {@link TokenType#VALUE_STRING}; the 257 * exact encoding is format-defined. 258 * 259 * @param value The value. May be <jk>null</jk>, in which case a {@link TokenType#VALUE_NULL} 260 * event is emitted instead. 261 * @return This object. 262 * @throws IOException If a problem occurred writing to the underlying stream. 263 * @throws SerializeException If the format cannot represent this event in the current state. 264 */ 265 TokenWriter binary(byte[] value) throws IOException; 266 267 /** 268 * Structural token cursors are always O(1)-memory streamers. 269 * 270 * @return Always <jk>true</jk>. 271 */ 272 @Override 273 default boolean isStreaming() { return true; } 274 275 /** 276 * Flushes any buffered output to the underlying stream. 277 * 278 * @throws IOException If a problem occurred flushing the underlying stream. 279 */ 280 @Override 281 void flush() throws IOException; 282 283 /** 284 * Closes the writer, flushing any buffered output first. 285 * 286 * <p> 287 * Implementations should be idempotent — closing a closed writer is a no-op. 288 */ 289 @Override 290 void close() throws IOException; 291 292 // ================================================================================= 293 // Binary-native opt-in emits. These default to UnsupportedOperationException so 294 // formats override only the subset they support (CBOR overrides writeTag and 295 // writeSimple; MsgPack overrides writeExt; text formats inherit the throwing 296 // defaults). See {@link BinaryNativeKind} for the catalog of native concepts. 297 // ================================================================================= 298 299 /** 300 * Emits a semantic tag prefixing the next value emit. 301 * 302 * <p> 303 * Tags compose: {@code writeTag(1).writeTag(2).number(5)} writes {@code tag(1)(tag(2)(5))}. 304 * The tag header is written immediately and does NOT consume a map-key/value or array-element 305 * slot; that bookkeeping belongs to the wrapped value emit that follows. 306 * 307 * @param tagNumber The unsigned tag number (carried as a {@code long} for the full unsigned 308 * 64-bit range). 309 * @return This object. 310 * @throws IOException If a problem occurred writing to the underlying stream. 311 * @throws UnsupportedOperationException If the format does not support semantic tags. 312 */ 313 default TokenWriter writeTag(long tagNumber) throws IOException { 314 throw new UnsupportedOperationException("writeTag is not supported by this writer."); 315 } 316 317 /** 318 * Emits an opaque simple value. The corresponding read-side token is 319 * {@link TokenType#VALUE_NULL} with {@link TokenReader#getSimpleValue()}. 320 * 321 * @param value The simple value. Format-specific reserved encodings (e.g. CBOR major-7 322 * codepoints 20-23, 25-27, 31 collide with bool/null/undefined/float/break) must not be 323 * used. 324 * @return This object. 325 * @throws IOException If a problem occurred writing to the underlying stream. 326 * @throws UnsupportedOperationException If the format does not support opaque simple values. 327 */ 328 default TokenWriter writeSimple(int value) throws IOException { 329 throw new UnsupportedOperationException("writeSimple is not supported by this writer."); 330 } 331 332 /** 333 * Emits a typed-binary value (signed type byte + binary payload). The corresponding 334 * read-side token is {@link TokenType#VALUE_BINARY} with {@link TokenReader#getExtType()} 335 * and {@link TokenReader#getBinary()}. 336 * 337 * @param type The signed type byte (range {@code -128..127}). 338 * @param payload The payload bytes. Must not be {@code null}. 339 * @return This object. 340 * @throws IOException If a problem occurred writing to the underlying stream. 341 * @throws UnsupportedOperationException If the format does not support typed-binary values. 342 */ 343 default TokenWriter writeExt(int type, byte[] payload) throws IOException { 344 throw new UnsupportedOperationException("writeExt is not supported by this writer."); 345 } 346}