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 final 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}