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.jsonl;
018
019
020import java.io.*;
021import java.util.*;
022
023import org.apache.juneau.marshall.json.*;
024import org.apache.juneau.marshall.serializer.*;
025import org.apache.juneau.marshall.stream.*;
026import static org.apache.juneau.commons.utils.Shorts.*;
027
028/**
029 * Session object that lives for the duration of a single use of {@link JsonlSerializer}.
030 *
031 * <h5 class='section'>Notes:</h5><ul>
032 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
033 * </ul>
034 *
035 * <h5 class='section'>See Also:</h5><ul>
036 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonlSupport">JSONL</a>
037 * </ul>
038 */
039@SuppressWarnings({
040   "java:S110", // Inheritance depth acceptable
041   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
042   "resource" // Resource management handled externally
043})
044public class JsonlSerializerSession extends JsonSerializerSession {
045
046   /**
047    * Builder class.
048    */
049   @SuppressWarnings({
050      "unused" // ctx passed to super(); parent uses it; Builder stores for constructor chain
051   })
052   public static class Builder extends JsonSerializerSession.Builder<Builder> {
053
054      private final JsonlSerializer ctx;
055
056      /**
057       * Constructor
058       *
059       * @param ctx The context creating this session.
060       *    <br>Cannot be <jk>null</jk>.
061       */
062      protected Builder(JsonlSerializer ctx) {
063         super(reqnn("ctx", ctx));
064         this.ctx = ctx;
065      }
066
067      @Override
068      public JsonlSerializerSession build() {
069         return new JsonlSerializerSession(this);
070      }
071   }
072
073   /**
074    * Creates a new builder for this object.
075    *
076    * @param ctx The context creating this session.
077    *    <br>Cannot be <jk>null</jk>.
078    * @return A new builder.
079    */
080   public static Builder create(JsonlSerializer ctx) {
081      return new Builder(reqnn("ctx", ctx));
082   }
083
084   /**
085    * Constructor.
086    *
087    * @param builder The builder for this object.
088    */
089   protected JsonlSerializerSession(Builder builder) {
090      super(builder);
091   }
092
093   /**
094    * Opens a low-level push generator that emits JSONL one structural event at a time, with a
095    * trailing newline after every top-level value, bound to this live session.
096    *
097    * <p>
098    * Same honored/ignored builder properties as {@link JsonSerializerSession#writeTokens(Object)}
099    * except that <c>useWhitespace</c> is forced to <jk>false</jk> &mdash; pretty-printed JSONL is
100    * not standard.
101    *
102    * @param output The output.
103    * @return A new {@link JsonlTokenWriter}.
104    * @throws IOException If the output type is not supported or could not be opened.
105    */
106   @Override /* JsonSerializerSession */
107   public TokenWriter writeTokens(Object output) throws IOException {
108      var walk = new PojoWalker.Options(
109         isKeepNullProperties(),
110         isTrimEmptyMaps(),
111         isTrimEmptyCollections(),
112         isSortMaps(),
113         isSortCollections(),
114         isTrimStrings(),
115         getMarshallingContext());
116      var settings = new JsonTokenWriter.Settings(
117         false /* useWhitespace — JSONL is one-record-per-line, no pretty-print */,
118         getMaxIndent(),
119         getQuoteChar(),
120         isEscapeSolidus(),
121         isTrimStrings(),
122         false /* simpleAttrs — JSONL uses RFC-8259 strict per line */,
123         walk,
124         false /* disableObject */);
125      return JsonlTokenWriter.forOutput(output, settings);
126   }
127
128   /**
129    * Streaming array-element {@link RecordWriter} for JSONL.
130    *
131    * <p>
132    * Unlike the inherited {@link JsonSerializerSession#writeArrayRecords(Object)} &mdash; which wraps
133    * the writes in a bracketed top-level <c>[...]</c> array and is invalid for line-delimited JSONL &mdash;
134    * this aliases the JSONL line record stream: the {@link JsonlTokenWriter} returned by
135    * {@link #writeTokens(Object)} is itself a {@link RecordWriter} whose
136    * {@link RecordWriter#write(Object) write(...)} emits one top-level value per line (no surrounding
137    * brackets).  Memory is O(1) in the number of records.
138    *
139    * @param output The output.
140    * @return A new line-delimited {@link RecordWriter}.
141    * @throws IOException If a problem occurred opening the underlying output.
142    */
143   @Override /* ArrayRecordWritable */
144   public RecordWriter writeArrayRecords(Object output) throws IOException {
145      return writeTokens(output);
146   }
147
148   @Override /* Overridden from JsonSerializerSession */
149   protected void doWrite(SerializerPipe out, Object o) throws IOException, SerializeException {
150      var w = getJsonWriter(out);
151      var cm = getClassMetaForObject(o);
152      var depth = getInitialDepth();
153
154      if (cm != null && (cm.isCollection() || cm.isArray())) {
155         var c = cm.isArray() ? toList(cm.inner(), o) : (Collection<?>) o;
156         var elementType = cm.getElementType();
157         for (Object item : c) {
158            writeAnything(w.i(depth), item, elementType, "root", null);
159            w.w('\n');
160         }
161      } else if (cm != null && cm.isStreamable()) {
162         var elementType = cm.getElementType();
163         forEachStreamableEntry(o, cm, item -> {
164            writeAnything(w.i(depth), item, elementType, "root", null);
165            w.w('\n');
166         });
167      } else {
168         writeAnything(w.i(depth), o, getExpectedRootType(o), "root", null);
169         w.w('\n');
170      }
171   }
172}