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.hjson; 018 019import static org.apache.juneau.commons.utils.IoUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.io.*; 023import java.time.*; 024import java.time.temporal.*; 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.commons.bean.*; 029import org.apache.juneau.commons.lang.*; 030import org.apache.juneau.marshall.*; 031import org.apache.juneau.marshall.serializer.*; 032import org.apache.juneau.marshall.stream.*; 033 034/** 035 * Session for serializing objects to Hjson format. 036 */ 037@SuppressWarnings({ 038 "java:S110", // Session hierarchy (HjsonSerializerSession -> WriterSerializerSession -> SerializerSession -> MarshallingSession ...) is inherently deep. 039 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 040 "java:S3776", // writeAnything() dispatches on every supported value type (bean, map, collection, array, date, etc.) in one if/else chain. 041 "java:S6541", // writeAnything() is the single type-dispatch point of the serializer and is long by design. 042 "rawtypes", // Raw Map, Collection and BeanMap are used in writeMap()/writeCollection() and the casts in doWrite()/writeAnything() because the runtime value type is not known. 043 "resource", // The HjsonWriter wraps the pipe's writer and is closed with the SerializerPipe, not in getHjsonWriter(). 044 "unchecked" // Calls to forEachEntry() with the raw Map/Collection arguments in writeMap()/writeCollection() are unchecked. 045}) 046public class HjsonSerializerSession extends WriterSerializerSession implements RecordWritable { 047 048 /** 049 * Builder for Hjson serializer session. 050 */ 051 public static class Builder extends WriterSerializerSession.Builder<Builder> { 052 053 private final HjsonSerializer ctx; 054 055 protected Builder(HjsonSerializer ctx) { 056 super(reqnn("ctx", ctx)); 057 this.ctx = ctx; 058 } 059 060 @Override 061 public HjsonSerializerSession build() { 062 return new HjsonSerializerSession(this); 063 } 064 } 065 066 /** 067 * Creates a session builder. 068 * 069 * @param ctx The serializer context. 070 * <br>Cannot be <jk>null</jk>. 071 * @return The builder. 072 */ 073 public static Builder create(HjsonSerializer ctx) { 074 return new Builder(reqnn("ctx", ctx)); 075 } 076 077 private final HjsonSerializer ctx; 078 079 protected HjsonSerializerSession(Builder builder) { 080 super(builder); 081 ctx = builder.ctx; 082 } 083 084 @Override /* RecordWritable */ 085 public RecordWriter writeRecords(Object output) throws IOException { 086 return RecordAdapter.writer(this, output); 087 } 088 089 @Override /* RecordWritable */ 090 public boolean isRecordStreaming() { 091 return false; 092 } 093 094 protected final HjsonWriter getHjsonWriter(SerializerPipe out) { 095 var output = out.getRawOutput(); 096 if (output instanceof HjsonWriter output2) 097 return output2; 098 var w = new HjsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), 099 ctx.useNewlineSeparators, ctx.useQuotelessStrings, ctx.useQuotelessKeys, ctx.omitRootBraces, 100 ctx.useMultilineStrings, isUseWhitespace(), getUriResolver()); 101 out.setWriter(w); 102 return w; 103 } 104 105 @Override 106 protected void doWrite(SerializerPipe out, Object o) throws IOException, SerializeException { 107 if (o == null) 108 return; 109 var aType = getClassMetaForObject(o); 110 var eType = getExpectedRootType(o); 111 var hw = getHjsonWriter(out).i(getInitialDepth()); 112 // Apply object swap before type check (e.g., surrogate serializers convert D1 → D2). 113 ClassMeta<?> sType = aType; 114 var swap = aType.getSwap(this); 115 if (nn(swap)) { 116 o = swap(swap, o); 117 sType = swap.getSwapClassMeta(this); 118 if (sType.isObject()) 119 sType = getClassMetaForObject(o); 120 } 121 if (sType.isBean() || sType.isMap()) { 122 var omitRoot = ctx.omitRootBraces; 123 if (sType.isBean()) 124 writeBeanMap(hw, toBeanMap(o), getBeanTypeName(this, eType, sType, null), omitRoot); 125 else 126 writeMap(hw, (Map) o, eType, omitRoot); 127 } else { 128 writeAnything(hw, o, eType, "root", null); 129 } 130 } 131 132 private void writeBeanMap(HjsonWriter out, BeanMap<?> m, String typeName, boolean omitBraces) throws SerializeException { 133 var i = indent; 134 out.objectStart(omitBraces, i); 135 indent++; 136 var first = Flag.create(); 137 Predicate<Object> checkNull = x -> isKeepNullProperties() || nn(x); 138 139 if (nn(typeName)) { 140 var pm = m.getMeta().getTypeProperty(); 141 writeKey(out, pm.getName(), i, first); 142 writeString(out, typeName); 143 first.set(); 144 } 145 146 m.forEachValue(checkNull, (pMeta, key, value, thrown) -> { 147 var cMeta = (ClassMeta<?>) pMeta.getBeanInfo(); 148 if (nn(thrown)) 149 onBeanGetterException(pMeta, thrown); 150 if (canIgnoreValue(pMeta, key, value)) 151 return; 152 writeKey(out, key, i, first); 153 writeAnything(out, value, cMeta, key, pMeta); 154 first.set(); 155 }); 156 157 indent--; 158 out.objectEnd(omitBraces, indent); 159 } 160 161 private void writeKey(HjsonWriter out, Object key, int depth, Flag first) throws SerializeException { 162 if (first.isSet()) 163 out.memberSeparator(depth); 164 else 165 out.cr(depth); 166 var keyStr = toString(key); 167 if (out.needsQuoting(keyStr)) 168 out.quotedString(keyStr); 169 else 170 out.quotelessString(keyStr); 171 out.keyValueSeparator(); 172 } 173 174 private void writeMap(HjsonWriter out, Map m, ClassMeta<?> type, boolean omitBraces) throws SerializeException { 175 var i = indent; 176 out.objectStart(omitBraces, i); 177 indent++; 178 var keyType = type.getKeyType(); 179 var valueType = type.getValueType(); 180 var first = Flag.create(); 181 182 forEachEntry(m, x -> { 183 var value = x.getValue(); 184 var key = generalize(x.getKey(), keyType); 185 writeKey(out, key, i, first); 186 writeAnything(out, value, valueType, key == null ? null : toString(key), null); 187 first.set(); 188 }); 189 190 indent--; 191 out.objectEnd(omitBraces, indent); 192 } 193 194 private void writeCollection(HjsonWriter out, Collection c, ClassMeta<?> type) throws SerializeException { 195 var eType = type.getElementType(); 196 out.arrayStart(); 197 indent++; 198 var first = Flag.create(); 199 forEachEntry(c, x -> { 200 if (first.isSet()) 201 out.memberSeparator(indent); 202 else 203 out.cr(indent); 204 writeAnything(out, x, eType, "<iterator>", null); 205 first.set(); 206 }); 207 indent--; 208 out.arrayEnd(); 209 } 210 211 private void writeString(HjsonWriter out, String s) throws SerializeException { 212 if (s == null) { 213 out.w("null"); 214 return; 215 } 216 if (ctx.useMultilineStrings && s.contains("\n")) 217 out.multilineString(s); 218 else if (out.isSimpleValue(s)) 219 out.quotelessString(s); 220 else 221 out.quotedString(s); 222 } 223 224 protected HjsonWriter writeAnything(HjsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException { 225 if (o == null) { 226 out.w("null"); 227 return out; 228 } 229 if (eType == null) 230 eType = object(); 231 232 var aType = push2(attrName, o, eType); 233 var isRecursion = aType == null; 234 235 if (aType == null) { 236 o = null; 237 aType = object(); 238 } 239 240 if (isOptional(aType)) { 241 o = getOptionalValue(o); 242 eType = getOptionalType(eType); 243 aType = getClassMetaForObject(o, object()); 244 } 245 246 var sType = aType; 247 var swap = aType.getSwap(this); 248 if (nn(swap)) { 249 o = swap(swap, o); 250 sType = swap.getSwapClassMeta(this); 251 if (sType.isObject()) 252 sType = getClassMetaForObject(o); 253 } 254 255 if (o == null || (sType.isChar() && ((Character) o).charValue() == 0)) { 256 out.w("null"); 257 } else if (sType.isBean()) { 258 writeBeanMap(out, toBeanMap(o), getBeanTypeName(this, eType, aType, pMeta), false); 259 } else if (sType.isMap()) { 260 if (sType.isBeanMap()) 261 writeBeanMap(out, (BeanMap) o, getBeanTypeName(this, eType, aType, pMeta), false); 262 else 263 writeMap(out, (Map) o, eType, false); 264 } else if (sType.isCollection()) { 265 writeCollection(out, (Collection) o, eType); 266 } else if (sType.isArray()) { 267 writeCollection(out, toList(sType.inner(), o), eType); 268 } else if (sType.isNumber() || sType.isBoolean()) { 269 out.append(o); 270 } else if (sType.isUri() || (nn(pMeta) && pMeta.isUri())) { 271 writeString(out, getUriResolver().resolve(o)); 272 } else if (sType.isDate()) { 273 writeString(out, writeDate((Date)o, sType)); 274 } else if (sType.isCalendar()) { 275 writeString(out, writeCalendar(o, sType)); 276 } else if (sType.isTemporal()) { 277 writeString(out, writeTemporal((TemporalAccessor)o, sType)); 278 } else if (sType.isDuration()) { 279 var value = writeDuration((Duration)o); 280 if (getDurationFormat().isNumeric()) 281 out.append(value); 282 else 283 writeString(out, value); 284 } else if (sType.isPeriod()) { 285 writeString(out, writePeriod((Period)o)); 286 } else if (sType.isStreamable()) { 287 writeStreamable(out, o, sType, eType); 288 } else if (sType.isReader()) { 289 pipe((Reader) o, out, SerializerSession::handleThrown); 290 } else if (sType.isInputStream()) { 291 pipe((InputStream) o, out, SerializerSession::handleThrown); 292 } else { 293 writeString(out, toString(o)); 294 } 295 296 if (!isRecursion) 297 pop(); 298 return out; 299 } 300 301 private void writeStreamable(HjsonWriter out, Object o, ClassMeta<?> sType, ClassMeta<?> type) throws SerializeException { 302 var eType = type.getElementType(); 303 out.arrayStart(); 304 indent++; 305 var first = Flag.create(); 306 forEachStreamableEntry(o, sType, x -> { 307 if (first.isSet()) 308 out.memberSeparator(indent); 309 else 310 out.cr(indent); 311 writeAnything(out, x, eType, "<iterator>", null); 312 first.set(); 313 }); 314 indent--; 315 out.arrayEnd(); 316 } 317}