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.toml; 018 019import static org.apache.juneau.commons.utils.StringUtils.*; 020 021import java.io.*; 022import java.time.*; 023import java.time.temporal.*; 024 025import org.apache.juneau.marshall.*; 026import org.apache.juneau.marshall.serializer.*; 027 028/** 029 * Specialized writer for serializing TOML v1.0.0. 030 * 031 * <p> 032 * Extends {@link SerializerWriter} with TOML-specific methods for tables, key-value pairs, 033 * and typed values. 034 * 035 * <h5 class='section'>Notes:</h5><ul> 036 * <li class='note'>This class is not intended for external use. 037 * </ul> 038 * 039 * <h5 class='section'>See Also:</h5><ul> 040 * <li class='link'><a href="https://toml.io/en/v1.0.0">TOML v1.0.0 Specification</a> 041 * </ul> 042 */ 043@SuppressWarnings({ 044 "resource" // Writer resource managed by calling code 045}) 046public class TomlWriter extends SerializerWriter<TomlWriter> { 047 048 /** 049 * Constructor. 050 * 051 * @param out The writer being wrapped. 052 * @param useWhitespace If <jk>true</jk>, use whitespace for formatting. 053 * @param maxIndent The maximum indentation level. 054 * @param trimStrings If <jk>true</jk>, trim strings before serialization. 055 * @param uriResolver The URI resolver. 056 */ 057 protected TomlWriter(Writer out, boolean useWhitespace, int maxIndent, boolean trimStrings, UriResolver uriResolver) { 058 super(out, useWhitespace, maxIndent, trimStrings, '"', uriResolver); 059 } 060 061 TomlWriter tableHeader(String path) { 062 w('[').w(path).w(']').w('\n'); 063 return this; 064 } 065 066 TomlWriter arrayOfTablesHeader(String path) { 067 w("[[").w(path).w("]]").w('\n'); 068 return this; 069 } 070 071 TomlWriter keyValue(String key, String value) { 072 writeKey(key); 073 w(" = "); 074 stringValue(value); 075 w('\n'); 076 return this; 077 } 078 079 TomlWriter bareKey(String key) { 080 w(key); 081 return this; 082 } 083 084 TomlWriter quotedKey(String key) { 085 w('"').w(escapeBasicString(key)).w('"'); 086 return this; 087 } 088 089 private void writeKey(String key) { 090 if (isBareKey(key)) 091 bareKey(key); 092 else 093 quotedKey(key); 094 } 095 096 TomlWriter stringValue(String value) { 097 if (trimStrings) 098 value = trim(value); 099 w('"').w(escapeBasicString(value == null ? "" : value)).w('"'); 100 return this; 101 } 102 103 TomlWriter literalString(String value) { 104 if (trimStrings) 105 value = trim(value); 106 w('\'').w(value == null ? "" : value.replace("'", "''")).w('\''); 107 return this; 108 } 109 110 TomlWriter multiLineString(String value) { 111 if (trimStrings) 112 value = trim(value); 113 w("\"\"\"").w('\n').w(value == null ? "" : value).w("\"\"\""); 114 return this; 115 } 116 117 TomlWriter integerValue(long value) { 118 w(Long.toString(value)); 119 return this; 120 } 121 122 TomlWriter floatValue(double value) { 123 if (Double.isNaN(value)) 124 w("nan"); 125 else if (value == Double.POSITIVE_INFINITY) 126 w("inf"); 127 else if (value == Double.NEGATIVE_INFINITY) 128 w("-inf"); 129 else { 130 String s = Double.toString(value); 131 if (s.endsWith(".0")) 132 s = s.substring(0, s.length() - 2); 133 w(s); 134 } 135 return this; 136 } 137 138 TomlWriter booleanValue(boolean value) { 139 w(value ? "true" : "false"); 140 return this; 141 } 142 143 TomlWriter dateTimeValue(Object value) { 144 if (value instanceof TemporalAccessor ta) { 145 if (ta instanceof Instant i) 146 w(OffsetDateTime.ofInstant(i, ZoneOffset.UTC).format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)); 147 else if (ta instanceof OffsetDateTime odt) 148 w(odt.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)); 149 else if (ta instanceof ZonedDateTime zdt) 150 w(zdt.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)); 151 else if (ta instanceof LocalDateTime ldt) 152 w(ldt.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 153 else if (ta instanceof LocalDate ld) 154 w(ld.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)); 155 else if (ta instanceof LocalTime lt) 156 w(lt.format(java.time.format.DateTimeFormatter.ISO_LOCAL_TIME)); 157 else 158 w(value.toString()); 159 } else { 160 w(value.toString()); 161 } 162 return this; 163 } 164 165 TomlWriter localDateTimeValue(Object value) { 166 if (value instanceof LocalDateTime value2) 167 w(value2.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 168 else 169 w(value.toString()); 170 return this; 171 } 172 173 TomlWriter localDateValue(Object value) { 174 if (value instanceof LocalDate value2) 175 w(value2.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)); 176 else 177 w(value.toString()); 178 return this; 179 } 180 181 TomlWriter localTimeValue(Object value) { 182 if (value instanceof LocalTime value2) 183 w(value2.format(java.time.format.DateTimeFormatter.ISO_LOCAL_TIME)); 184 else 185 w(value.toString()); 186 return this; 187 } 188 189 TomlWriter arrayStart() { 190 w('['); 191 return this; 192 } 193 194 TomlWriter arrayEnd() { 195 w(']'); 196 return this; 197 } 198 199 TomlWriter inlineTableStart() { 200 w('{'); 201 return this; 202 } 203 204 TomlWriter inlineTableEnd() { 205 w('}'); 206 return this; 207 } 208 209 TomlWriter comment(String text) { 210 w("# ").w(text).w('\n'); 211 return this; 212 } 213 214 TomlWriter blankLine() { 215 w('\n'); 216 return this; 217 } 218 219 String escapeBasicString(String text) { 220 if (text == null) 221 return ""; 222 var sb = new StringBuilder(); 223 for (int i = 0; i < text.length(); i++) { 224 char c = text.charAt(i); 225 switch (c) { 226 case '\\' -> sb.append("\\\\"); 227 case '"' -> sb.append("\\\""); 228 case '\b' -> sb.append("\\b"); 229 case '\t' -> sb.append("\\t"); 230 case '\n' -> sb.append("\\n"); 231 case '\f' -> sb.append("\\f"); 232 case '\r' -> sb.append("\\r"); 233 default -> sb.append(c); 234 } 235 } 236 return sb.toString(); 237 } 238 239 boolean isBareKey(String key) { 240 if (isEmpty(key)) 241 return false; 242 for (int i = 0; i < key.length(); i++) { 243 char c = key.charAt(i); 244 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')) 245 return false; 246 } 247 return true; 248 } 249}