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