001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.uon; 014 015import java.io.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.internal.*; 019import org.apache.juneau.serializer.*; 020 021/** 022 * Specialized writer for serializing UON-encoded text. 023 * 024 * <ul class='notes'> 025 * <li> 026 * This class is not intended for external use. 027 * </ul> 028 */ 029public final class UonWriter extends SerializerWriter { 030 031 private final UonSerializerSession session; 032 private final boolean encodeChars, plainTextParams; 033 private final char quoteChar; 034 035 // Characters that do not need to be URL-encoded in strings. 036 private static final AsciiSet unencodedChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars(";/?:@-_.!*'$(),~=").build(); 037 038 // Characters that do not need to be URL-encoded in attribute names. 039 // Identical to unencodedChars, but excludes '='. 040 private static final AsciiSet unencodedCharsAttrName = AsciiSet.create().ranges("a-z","A-Z","0-9").chars(";/?:@-_.!*'$(),~").build(); 041 042 // Characters that need to be preceded with an escape character. 043 private static final AsciiSet escapedChars = AsciiSet.create("~'"); 044 045 private static final AsciiSet noChars = AsciiSet.create(""); 046 047 private static char[] hexArray = "0123456789ABCDEF".toCharArray(); 048 049 /** 050 * Constructor. 051 * 052 * @param session The session that created this writer. 053 * @param out The writer being wrapped. 054 * @param useWhitespace If <jk>true</jk>, tabs will be used in output. 055 * @param maxIndent The maximum indentation level. 056 * @param encodeChars If <jk>true</jk>, special characters should be encoded. 057 * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized. 058 * @param plainTextParams If <jk>true</jk>, don't use UON notation for values. 059 * @param quoteChar The quote character to use. If <c>0</c>, defaults to <js>'\''</js>. 060 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form. 061 */ 062 protected UonWriter(UonSerializerSession session, Writer out, boolean useWhitespace, int maxIndent, 063 boolean encodeChars, boolean trimStrings, boolean plainTextParams, char quoteChar, UriResolver uriResolver) { 064 super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver); 065 this.session = session; 066 this.encodeChars = encodeChars; 067 this.plainTextParams = plainTextParams; 068 this.quoteChar = quoteChar; 069 } 070 071 /** 072 * Serializes the specified simple object as a UON string value. 073 * 074 * @param o The object being serialized. 075 * @param isTopAttrName If this is a top-level attribute name we're serializing. 076 * @return This object (for method chaining). 077 * @throws IOException Should never happen. 078 */ 079 public final UonWriter appendObject(Object o, boolean isTopAttrName) throws IOException { 080 081 if (o instanceof Boolean) 082 return appendBoolean(o); 083 if (o instanceof Number) 084 return appendNumber(o); 085 if (o == null) 086 return append("null"); 087 088 String s = session.toString(o); 089 090 boolean needsQuotes = (! plainTextParams) && UonUtils.needsQuotes(s); 091 092 AsciiSet unenc = (isTopAttrName ? unencodedCharsAttrName : unencodedChars); 093 AsciiSet esc = plainTextParams ? noChars : escapedChars; 094 095 if (needsQuotes) 096 append(quoteChar); 097 for (int i = 0; i < s.length(); i++) { 098 char c = s.charAt(i); 099 if (esc.contains(c)) 100 append('~'); 101 if ((!encodeChars) || unenc.contains(c)) 102 append(c); 103 else { 104 if (c == ' ') 105 append('+'); 106 else { 107 int p = s.codePointAt(i); 108 if (p < 0x0080) 109 appendHex(p); 110 else if (p < 0x0800) { 111 int p1=p>>>6; 112 appendHex(p1+192).appendHex((p&63)+128); 113 } else if (p < 0x10000) { 114 int p1=p>>>6, p2=p1>>>6; 115 appendHex(p2+224).appendHex((p1&63)+128).appendHex((p&63)+128); 116 } else { 117 i++; // Two-byte codepoint...skip past surrogate pair lower byte. 118 int p1=p>>>6, p2=p1>>>6, p3=p2>>>6; 119 appendHex(p3+240).appendHex((p2&63)+128).appendHex((p1&63)+128).appendHex((p&63)+128); 120 } 121 } 122 } 123 } 124 if (needsQuotes) 125 append(quoteChar); 126 127 return this; 128 } 129 130 /** 131 * Appends a boolean value to the output. 132 * 133 * @param o The boolean value to append to the output. 134 * @return This object (for method chaining). 135 * @throws IOException Thrown by underlying stream. 136 */ 137 protected UonWriter appendBoolean(Object o) throws IOException { 138 append(o.toString()); 139 return this; 140 } 141 142 /** 143 * Appends a numeric value to the output. 144 * 145 * @param o The numeric value to append to the output. 146 * @return This object (for method chaining). 147 * @throws IOException Thrown by underlying stream. 148 */ 149 protected UonWriter appendNumber(Object o) throws IOException { 150 append(o.toString()); 151 return this; 152 } 153 154 /** 155 * Prints out a two-byte %xx sequence for the given byte value. 156 */ 157 private UonWriter appendHex(int b) throws IOException { 158 if (b > 255) 159 throw new IOException("Invalid value passed to appendHex. Must be in the range 0-255. Value=" + b); 160 append('%').append(hexArray[b>>>4]).append(hexArray[b&0x0F]); 161 return this; 162 } 163 164 /** 165 * Appends a URI to the output. 166 * 167 * @param uri The URI to append to the output. 168 * @return This object (for method chaining). 169 * @throws IOException Thrown by underlying stream. 170 */ 171 @Override 172 public SerializerWriter appendUri(Object uri) throws IOException { 173 return appendObject(uriResolver.resolve(uri), false); 174 } 175 176 177 //----------------------------------------------------------------------------------------------------------------- 178 // Overridden methods 179 //----------------------------------------------------------------------------------------------------------------- 180 181 @Override /* SerializerWriter */ 182 public UonWriter cr(int depth) throws IOException { 183 super.cr(depth); 184 return this; 185 } 186 187 @Override /* SerializerWriter */ 188 public UonWriter cre(int depth) throws IOException { 189 super.cre(depth); 190 return this; 191 } 192 193 @Override /* SerializerWriter */ 194 public UonWriter appendln(int indent, String text) throws IOException { 195 super.appendln(indent, text); 196 return this; 197 } 198 199 @Override /* SerializerWriter */ 200 public UonWriter appendln(String text) throws IOException { 201 super.appendln(text); 202 return this; 203 } 204 205 @Override /* SerializerWriter */ 206 public UonWriter append(int indent, String text) throws IOException { 207 super.append(indent, text); 208 return this; 209 } 210 211 @Override /* SerializerWriter */ 212 public UonWriter append(int indent, char c) throws IOException { 213 super.append(indent, c); 214 return this; 215 } 216 217 @Override /* SerializerWriter */ 218 public UonWriter q() throws IOException { 219 super.q(); 220 return this; 221 } 222 223 @Override /* SerializerWriter */ 224 public UonWriter i(int indent) throws IOException { 225 super.i(indent); 226 return this; 227 } 228 229 @Override /* SerializerWriter */ 230 public UonWriter nl(int indent) throws IOException { 231 super.nl(indent); 232 return this; 233 } 234 235 @Override /* SerializerWriter */ 236 public UonWriter append(Object text) throws IOException { 237 super.append(text); 238 return this; 239 } 240 241 @Override /* SerializerWriter */ 242 public UonWriter append(String text) throws IOException { 243 super.append(text); 244 return this; 245 } 246 247 @Override /* SerializerWriter */ 248 public UonWriter appendIf(boolean b, String text) throws IOException { 249 super.appendIf(b, text); 250 return this; 251 } 252 253 @Override /* SerializerWriter */ 254 public UonWriter appendIf(boolean b, char c) throws IOException { 255 super.appendIf(b, c); 256 return this; 257 } 258 259 @Override /* SerializerWriter */ 260 public UonWriter append(char c) throws IOException { 261 super.append(c); 262 return this; 263 } 264}