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