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.uon; 018 019import static org.apache.juneau.commons.utils.ThrowableUtils.*; 020 021import java.io.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.commons.lang.*; 025import org.apache.juneau.serializer.*; 026 027/** 028 * Specialized writer for serializing UON-encoded text. 029 * 030 * <h5 class='section'>Notes:</h5><ul> 031 * <li class='note'> 032 * This class is not intended for external 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/UonBasics">UON Basics</a> 037 038 * </ul> 039 */ 040@SuppressWarnings("resource") 041public class UonWriter extends SerializerWriter { 042 043 // Characters that do not need to be URL-encoded in strings. 044 private static final AsciiSet unencodedChars = AsciiSet.create().ranges("a-z", "A-Z", "0-9").chars(";/?:@-_.!*'$(),~=").build(); 045 // Characters that do not need to be URL-encoded in attribute names. 046 // Identical to unencodedChars, but excludes '='. 047 private static final AsciiSet unencodedCharsAttrName = AsciiSet.create().ranges("a-z", "A-Z", "0-9").chars(";/?:@-_.!*'$(),~").build(); 048 // Characters that need to be preceded with an escape character. 049 private static final AsciiSet escapedChars = AsciiSet.of("~'"); 050 051 private static final AsciiSet noChars = AsciiSet.of(""); 052 053 private static char[] hexArray = "0123456789ABCDEF".toCharArray(); 054 055 private final UonSerializerSession session; 056 057 private final boolean encodeChars, plainTextParams; 058 059 private final char quoteChar; 060 061 /** 062 * Constructor. 063 * 064 * @param session The session that created this writer. 065 * @param out The writer being wrapped. 066 * @param useWhitespace If <jk>true</jk>, tabs will be used in output. 067 * @param maxIndent The maximum indentation level. 068 * @param encodeChars If <jk>true</jk>, special characters should be encoded. 069 * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized. 070 * @param plainTextParams If <jk>true</jk>, don't use UON notation for values. 071 * @param quoteChar The quote character to use. If <c>0</c>, defaults to <js>'\''</js>. 072 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form. 073 */ 074 protected UonWriter(UonSerializerSession session, Writer out, boolean useWhitespace, int maxIndent, boolean encodeChars, boolean trimStrings, boolean plainTextParams, char quoteChar, 075 UriResolver uriResolver) { 076 super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver); 077 this.session = session; 078 this.encodeChars = encodeChars; 079 this.plainTextParams = plainTextParams; 080 this.quoteChar = quoteChar; 081 } 082 083 @Override /* Overridden from SerializerWriter */ 084 public UonWriter append(char c) { 085 super.append(c); 086 return this; 087 } 088 089 @Override /* Overridden from SerializerWriter */ 090 public UonWriter append(char[] value) { 091 super.append(value); 092 return this; 093 } 094 095 @Override /* Overridden from SerializerWriter */ 096 public UonWriter append(int indent, char c) { 097 super.append(indent, c); 098 return this; 099 } 100 101 @Override /* Overridden from SerializerWriter */ 102 public UonWriter append(int indent, String text) { 103 super.append(indent, text); 104 return this; 105 } 106 107 @Override /* Overridden from SerializerWriter */ 108 public UonWriter append(Object text) { 109 super.append(text); 110 return this; 111 } 112 113 @Override /* Overridden from SerializerWriter */ 114 public UonWriter append(String text) { 115 super.append(text); 116 return this; 117 } 118 119 @Override /* Overridden from SerializerWriter */ 120 public UonWriter appendIf(boolean b, char c) { 121 super.appendIf(b, c); 122 return this; 123 } 124 125 @Override /* Overridden from SerializerWriter */ 126 public UonWriter appendIf(boolean b, String text) { 127 super.appendIf(b, text); 128 return this; 129 } 130 131 @Override /* Overridden from SerializerWriter */ 132 public UonWriter appendln(int indent, String text) { 133 super.appendln(indent, text); 134 return this; 135 } 136 137 @Override /* Overridden from SerializerWriter */ 138 public UonWriter appendln(String text) { 139 super.appendln(text); 140 return this; 141 } 142 143 /** 144 * Serializes the specified simple object as a UON string value. 145 * 146 * @param o The object being serialized. 147 * @param isTopAttrName If this is a top-level attribute name we're serializing. 148 * @return This object. 149 */ 150 public UonWriter appendObject(Object o, boolean isTopAttrName) { 151 152 if (o instanceof Boolean) 153 return appendBoolean(o); 154 if (o instanceof Number) 155 return appendNumber(o); 156 if (o == null) 157 return append("null"); 158 159 var s = session.toString(o); 160 161 var needsQuotes = (! plainTextParams) && UonUtils.needsQuotes(s); 162 163 var unenc = (isTopAttrName ? unencodedCharsAttrName : unencodedChars); 164 var esc = plainTextParams ? noChars : escapedChars; 165 166 if (needsQuotes) 167 w(quoteChar); 168 for (var i = 0; i < s.length(); i++) { 169 var c = s.charAt(i); 170 if (esc.contains(c)) 171 w('~'); 172 if ((! encodeChars) || unenc.contains(c)) 173 w(c); 174 else { 175 if (c == ' ') 176 w('+'); 177 else { 178 var p = s.codePointAt(i); 179 if (p < 0x0080) 180 appendHex(p); 181 else if (p < 0x0800) { 182 var p1 = p >>> 6; 183 appendHex(p1 + 192).appendHex((p & 63) + 128); 184 } else if (p < 0x10000) { 185 var p1 = p >>> 6; 186 var p2 = p1 >>> 6; 187 appendHex(p2 + 224).appendHex((p1 & 63) + 128).appendHex((p & 63) + 128); 188 } else { 189 i++; // Two-byte codepoint...skip past surrogate pair lower byte. 190 var p1 = p >>> 6; 191 var p2 = p1 >>> 6; 192 var p3 = p2 >>> 6; 193 appendHex(p3 + 240).appendHex((p2 & 63) + 128).appendHex((p1 & 63) + 128).appendHex((p & 63) + 128); 194 } 195 } 196 } 197 } 198 if (needsQuotes) 199 w(quoteChar); 200 201 return this; 202 } 203 204 /** 205 * Appends a URI to the output. 206 * 207 * @param uri The URI to append to the output. 208 * @return This object. 209 */ 210 @Override /* Overridden from SerializerWriter */ 211 public UonWriter appendUri(Object uri) { 212 return appendObject(uriResolver.resolve(uri), false); 213 } 214 215 @Override /* Overridden from SerializerWriter */ 216 public UonWriter cr(int depth) { 217 super.cr(depth); 218 return this; 219 } 220 221 @Override /* Overridden from SerializerWriter */ 222 public UonWriter cre(int depth) { 223 super.cre(depth); 224 return this; 225 } 226 227 @Override /* Overridden from SerializerWriter */ 228 public UonWriter i(int indent) { 229 super.i(indent); 230 return this; 231 } 232 233 @Override /* Overridden from SerializerWriter */ 234 public UonWriter ie(int indent) { 235 super.ie(indent); 236 return this; 237 } 238 239 @Override /* Overridden from SerializerWriter */ 240 public UonWriter nl(int indent) { 241 super.nl(indent); 242 return this; 243 } 244 245 @Override /* Overridden from SerializerWriter */ 246 public UonWriter nlIf(boolean flag, int indent) { 247 super.nlIf(flag, indent); 248 return this; 249 } 250 251 @Override /* Overridden from SerializerWriter */ 252 public UonWriter q() { 253 super.q(); 254 return this; 255 } 256 257 @Override /* Overridden from SerializerWriter */ 258 public UonWriter s() { 259 super.s(); 260 return this; 261 } 262 263 @Override /* Overridden from SerializerWriter */ 264 public UonWriter sIf(boolean flag) { 265 super.sIf(flag); 266 return this; 267 } 268 269 @Override /* Overridden from SerializerWriter */ 270 public UonWriter w(char value) { 271 super.w(value); 272 return this; 273 } 274 275 @Override /* Overridden from SerializerWriter */ 276 public UonWriter w(String value) { 277 super.w(value); 278 return this; 279 } 280 281 /** 282 * Prints out a two-byte %xx sequence for the given byte value. 283 */ 284 private UonWriter appendHex(int b) { 285 if (b > 255) 286 throw rex("Invalid value passed to appendHex. Must be in the range 0-255. Value={0}", b); 287 w('%').w(hexArray[b >>> 4]).w(hexArray[b & 0x0F]); 288 return this; 289 } 290 291 /** 292 * Appends a boolean value to the output. 293 * 294 * @param o The boolean value to append to the output. 295 * @return This object. 296 */ 297 protected UonWriter appendBoolean(Object o) { 298 append(o.toString()); 299 return this; 300 } 301 302 /** 303 * Appends a numeric value to the output. 304 * 305 * @param o The numeric value to append to the output. 306 * @return This object. 307 */ 308 protected UonWriter appendNumber(Object o) { 309 append(o.toString()); 310 return this; 311 } 312}