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 * <h5 class='section'>Notes:</h5> 025 * <ul class='spaced-list'> 026 * <li> 027 * This class is not intended for external use. 028 * </ul> 029 */ 030public final class UonWriter extends SerializerWriter { 031 032 private final UonSerializerSession session; 033 private final boolean encodeChars, plainTextParams; 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 uriResolver The URI resolver for resolving URIs to absolute or root-relative form. 060 */ 061 protected UonWriter(UonSerializerSession session, Writer out, boolean useWhitespace, int maxIndent, 062 boolean encodeChars, boolean trimStrings, boolean plainTextParams, UriResolver uriResolver) { 063 super(out, useWhitespace, maxIndent, trimStrings, '\'', uriResolver); 064 this.session = session; 065 this.encodeChars = encodeChars; 066 this.plainTextParams = plainTextParams; 067 } 068 069 /** 070 * Serializes the specified simple object as a UON string value. 071 * 072 * @param o The object being serialized. 073 * @param isTopAttrName If this is a top-level attribute name we're serializing. 074 * @return This object (for method chaining). 075 * @throws IOException Should never happen. 076 */ 077 public final UonWriter appendObject(Object o, boolean isTopAttrName) throws IOException { 078 079 if (o instanceof Boolean) 080 return appendBoolean(o); 081 if (o instanceof Number) 082 return appendNumber(o); 083 if (o == null) 084 return append("null"); 085 086 String s = session.toString(o); 087 088 boolean needsQuotes = (! plainTextParams) && UonUtils.needsQuotes(s); 089 090 AsciiSet unenc = (isTopAttrName ? unencodedCharsAttrName : unencodedChars); 091 AsciiSet esc = plainTextParams ? noChars : escapedChars; 092 093 if (needsQuotes) 094 append('\''); 095 for (int i = 0; i < s.length(); i++) { 096 char c = s.charAt(i); 097 if (esc.contains(c)) 098 append('~'); 099 if ((!encodeChars) || unenc.contains(c)) 100 append(c); 101 else { 102 if (c == ' ') 103 append('+'); 104 else { 105 int p = s.codePointAt(i); 106 if (p < 0x0080) 107 appendHex(p); 108 else if (p < 0x0800) { 109 int p1=p>>>6; 110 appendHex(p1+192).appendHex((p&63)+128); 111 } else if (p < 0x10000) { 112 int p1=p>>>6, p2=p1>>>6; 113 appendHex(p2+224).appendHex((p1&63)+128).appendHex((p&63)+128); 114 } else { 115 i++; // Two-byte codepoint...skip past surrogate pair lower byte. 116 int p1=p>>>6, p2=p1>>>6, p3=p2>>>6; 117 appendHex(p3+240).appendHex((p2&63)+128).appendHex((p1&63)+128).appendHex((p&63)+128); 118 } 119 } 120 } 121 } 122 if (needsQuotes) 123 append('\''); 124 125 return this; 126 } 127 128 /** 129 * Appends a boolean value to the output. 130 * 131 * @param o The boolean value to append to the output. 132 * @return This object (for method chaining). 133 * @throws IOException 134 */ 135 protected UonWriter appendBoolean(Object o) throws IOException { 136 append(o.toString()); 137 return this; 138 } 139 140 /** 141 * Appends a numeric value to the output. 142 * 143 * @param o The numeric value to append to the output. 144 * @return This object (for method chaining). 145 * @throws IOException 146 */ 147 protected UonWriter appendNumber(Object o) throws IOException { 148 append(o.toString()); 149 return this; 150 } 151 152 /** 153 * Prints out a two-byte %xx sequence for the given byte value. 154 */ 155 private UonWriter appendHex(int b) throws IOException { 156 if (b > 255) 157 throw new IOException("Invalid value passed to appendHex. Must be in the range 0-255. Value=" + b); 158 append('%').append(hexArray[b>>>4]).append(hexArray[b&0x0F]); 159 return this; 160 } 161 162 /** 163 * Appends a URI to the output. 164 * 165 * @param uri The URI to append to the output. 166 * @return This object (for method chaining). 167 * @throws IOException 168 */ 169 @Override 170 public SerializerWriter appendUri(Object uri) throws IOException { 171 return appendObject(uriResolver.resolve(uri), false); 172 } 173 174 175 //-------------------------------------------------------------------------------- 176 // Overridden methods 177 //-------------------------------------------------------------------------------- 178 179 @Override /* SerializerWriter */ 180 public UonWriter cr(int depth) throws IOException { 181 super.cr(depth); 182 return this; 183 } 184 185 @Override /* SerializerWriter */ 186 public UonWriter cre(int depth) throws IOException { 187 super.cre(depth); 188 return this; 189 } 190 191 @Override /* SerializerWriter */ 192 public UonWriter appendln(int indent, String text) throws IOException { 193 super.appendln(indent, text); 194 return this; 195 } 196 197 @Override /* SerializerWriter */ 198 public UonWriter appendln(String text) throws IOException { 199 super.appendln(text); 200 return this; 201 } 202 203 @Override /* SerializerWriter */ 204 public UonWriter append(int indent, String text) throws IOException { 205 super.append(indent, text); 206 return this; 207 } 208 209 @Override /* SerializerWriter */ 210 public UonWriter append(int indent, char c) throws IOException { 211 super.append(indent, c); 212 return this; 213 } 214 215 @Override /* SerializerWriter */ 216 public UonWriter q() throws IOException { 217 super.q(); 218 return this; 219 } 220 221 @Override /* SerializerWriter */ 222 public UonWriter i(int indent) throws IOException { 223 super.i(indent); 224 return this; 225 } 226 227 @Override /* SerializerWriter */ 228 public UonWriter nl(int indent) throws IOException { 229 super.nl(indent); 230 return this; 231 } 232 233 @Override /* SerializerWriter */ 234 public UonWriter append(Object text) throws IOException { 235 super.append(text); 236 return this; 237 } 238 239 @Override /* SerializerWriter */ 240 public UonWriter append(String text) throws IOException { 241 super.append(text); 242 return this; 243 } 244 245 @Override /* SerializerWriter */ 246 public UonWriter appendIf(boolean b, String text) throws IOException { 247 super.appendIf(b, text); 248 return this; 249 } 250 251 @Override /* SerializerWriter */ 252 public UonWriter appendIf(boolean b, char c) throws IOException { 253 super.appendIf(b, c); 254 return this; 255 } 256 257 @Override /* SerializerWriter */ 258 public UonWriter append(char c) throws IOException { 259 super.append(c); 260 return this; 261 } 262}