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.lang.reflect.*; 021 022import org.apache.juneau.marshall.*; 023import org.apache.juneau.marshall.parser.*; 024 025/** 026 * O(1)-memory wrappers that adapt a fine-grained {@link TokenReader} / {@link TokenWriter} to 027 * the array-element record-stream contract. Memory does not scale with the number of array 028 * elements — reading binds one element at a time; writing emits one element at a time. 029 * 030 * <p> 031 * For the reader: the wrapper opens an outer array via the cursor's {@link TokenReader#next()}, 032 * yields elements via {@link TokenReader#read(Class)} until {@link TokenReader#canRead()} returns 033 * false (end of array), then advances past the closing {@code ]} on close. 034 * 035 * <p> 036 * For the writer: the wrapper opens an outer array via {@link TokenWriter#startArray()}, 037 * forwards each {@link RecordWriter#write(Object) write(...)} call to 038 * {@link TokenWriter#object(Object)}, and emits the closing {@code ]} via 039 * {@link TokenWriter#endArray()} on close. 040 * 041 * <p> 042 * Used by FULL-capability formats whose token cursor genuinely streams (json, json5, cbor, etc.). 043 * For PARTIAL formats whose token surface doesn't exist, see 044 * {@link RecordAdapter#arrayReader(ParserSession, Object)} / {@link RecordAdapter#arrayWriter}, which 045 * are buffered. 046 */ 047@SuppressWarnings({ 048 "resource" // Wrapped TokenReader/TokenWriter are Closeables owned by the caller; Eclipse JDT @Owning/resource-leak warning is by design. 049}) 050public final class StreamingArrayRecord { 051 052 private StreamingArrayRecord() {} 053 054 /** 055 * Wraps a {@link TokenReader} as a streaming array-element {@link RecordReader}. 056 * 057 * <p> 058 * The wrapper assumes the cursor is positioned at the start of an array on the wire. It 059 * consumes the opening {@link TokenType#START_ARRAY} on construction and the closing 060 * {@link TokenType#END_ARRAY} on {@link RecordReader#close() close()}. 061 * 062 * @param cursor The token reader to wrap. Must not be <jk>null</jk>. Owned by the returned 063 * RecordReader; closing the RecordReader closes the cursor. 064 * @return A streaming array-element {@link RecordReader}. 065 * @throws IOException If a problem occurred reading the underlying input. 066 * @throws ParseException If the input is not a JSON-style array. 067 */ 068 public static RecordReader reader(TokenReader cursor) throws IOException, ParseException { 069 var t = cursor.next(); 070 if (t != TokenType.START_ARRAY) 071 throw new ParseException("Expected START_ARRAY at the root of arrayRecordReader input but got %s", t); 072 return new RecordReader() { 073 @Override public boolean canRead() throws IOException, ParseException { 074 return cursor.canRead(); 075 } 076 @Override public <T> T read(Class<T> type) throws IOException, ParseException { 077 return cursor.read(type); 078 } 079 @Override public <T> T read(ClassMeta<T> type) throws IOException, ParseException { 080 return cursor.read(type); 081 } 082 @Override public <T> T read(Type type, Type... args) throws IOException, ParseException { 083 return cursor.read(type, args); 084 } 085 @Override public boolean isStreaming() { 086 return true; 087 } 088 @Override public void close() throws IOException { 089 cursor.close(); 090 } 091 }; 092 } 093 094 /** 095 * Wraps a {@link TokenWriter} as a streaming array-element {@link RecordWriter}. 096 * 097 * <p> 098 * The wrapper emits the opening array on construction. Each 099 * {@link RecordWriter#write(Object)} call appends one array element via 100 * {@link TokenWriter#object(Object)}. {@link RecordWriter#close() close()} emits the 101 * closing array bracket and closes the underlying cursor. 102 * 103 * @param cursor The token writer to wrap. Must not be <jk>null</jk>. Owned by the returned 104 * RecordWriter; closing the RecordWriter closes the cursor. 105 * @return A streaming array-element {@link RecordWriter}. 106 * @throws IOException If a problem occurred writing to the underlying output. 107 */ 108 public static RecordWriter writer(TokenWriter cursor) throws IOException { 109 cursor.startArray(); 110 return new RecordWriter() { 111 private boolean closed; 112 113 @Override public RecordWriter write(Object value) throws IOException { 114 if (closed) 115 throw new IllegalStateException("Array stream is closed."); 116 cursor.object(value); 117 return this; 118 } 119 @Override public boolean isStreaming() { 120 return true; 121 } 122 @Override public void flush() throws IOException { 123 cursor.flush(); 124 } 125 @Override public void close() throws IOException { 126 if (closed) 127 return; 128 closed = true; 129 cursor.endArray(); 130 cursor.close(); 131 } 132 }; 133 } 134}