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