001/*
002 * Copyright 2015-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2015-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2015-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util.json;
037
038
039
040import com.unboundid.util.ByteStringBuffer;
041import com.unboundid.util.NotMutable;
042import com.unboundid.util.StaticUtils;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045
046
047
048/**
049 * This class provides an implementation of a JSON value that represents a
050 * string of Unicode characters.  The string representation of a JSON string
051 * must start and end with the double quotation mark character, and a Unicode
052 * (preferably UTF-8) representation of the string between the quotes.  The
053 * following special characters must be escaped:
054 * <UL>
055 *   <LI>
056 *     The double quotation mark (Unicode character U+0022) must be escaped as
057 *     either {@code \"} or {@code \}{@code u0022}.
058 *   </LI>
059 *   <LI>
060 *     The backslash (Unicode character U+005C) must be escaped as either
061 *     {@code \\} or {@code \}{@code u005C}.
062 *   </LI>
063 *   <LI>
064 *     All ASCII control characters (Unicode characters U+0000 through U+001F)
065 *     must be escaped.  They can all be escaped by prefixing the
066 *     four-hexadecimal-digit Unicode character code with {@code \}{@code u},
067 *     like {@code \}{@code u0000} to represent the ASCII null character U+0000.
068 *     For certain characters, a more user-friendly escape sequence is also
069 *     defined:
070 *     <UL>
071 *       <LI>
072 *         The horizontal tab character can be escaped as either {@code \t} or
073 *         {@code \}{@code u0009}.
074 *       </LI>
075 *       <LI>
076 *         The newline character can be escaped as either {@code \n} or
077 *         {@code \}{@code u000A}.
078 *       </LI>
079 *       <LI>
080 *         The formfeed character can be escaped as either {@code \f} or
081 *         {@code \}{@code u000C}.
082 *       </LI>
083 *       <LI>
084 *         The carriage return character can be escaped as either {@code \r} or
085 *         {@code \}{@code u000D}.
086 *       </LI>
087 *     </UL>
088 *   </LI>
089 * </UL>
090 * In addition, any other character may optionally be escaped by placing the
091 * {@code \}{@code u} prefix in front of each four-hexadecimal digit sequence in
092 * the UTF-16 representation of that character.  For example, the "LATIN SMALL
093 * LETTER N WITH TILDE" character U+00F1 may be escaped as
094 * {@code \}{@code u00F1}, while the "MUSICAL SYMBOL G CLEF" character U+1D11E
095 * may be escaped as {@code \}{@code uD834}{@code \}{@code uDD1E}.  And while
096 * the forward slash character is not required to be escaped in JSON strings, it
097 * can be escaped using {@code \/} as a more human-readable alternative to
098 * {@code \}{@code u002F}.
099 * <BR><BR>
100 * The string provided to the {@link #JSONString(String)} constructor should not
101 * have any escaping performed, and the string returned by the
102 * {@link #stringValue()} method will not have any escaping performed.  These
103 * methods work with the Java string that is represented by the JSON string.
104 * <BR><BR>
105 * If this JSON string was parsed from the string representation of a JSON
106 * object, then the value returned by the {@link #toString()} method (or
107 * appended to the buffer provided to the {@link #toString(StringBuilder)}
108 * method) will be the string representation used in the JSON object that was
109 * parsed.  Otherwise, this class will generate an appropriate string
110 * representation, which will be surrounded by quotation marks and will have the
111 * minimal required encoding applied.
112 * <BR><BR>
113 * The string returned by the {@link #toNormalizedString()} method (or appended
114 * to the buffer provided to the {@link #toNormalizedString(StringBuilder)}
115 * method) will be generated by converting it to lowercase, surrounding it with
116 * quotation marks, and using the {@code \}{@code u}-style escaping for all
117 * characters other than the following (as contained in the LDAP printable
118 * character set defined in <A HREF="http://www.ietf.org/rfc/rfc4517.txt">RFC
119 * 4517</A> section 3.2, and indicated by the
120 * {@link StaticUtils#isPrintable(char)} method):
121 * <UL>
122 *   <LI>All uppercase ASCII alphabetic letters (U+0041 through U+005A).</LI>
123 *   <LI>All lowercase ASCII alphabetic letters (U+0061 through U+007A).</LI>
124 *   <LI>All ASCII numeric digits (U+0030 through U+0039).</LI>
125 *   <LI>The ASCII space character U+0020.</LI>
126 *   <LI>The ASCII single quote (aka apostrophe) character U+0027.</LI>
127 *   <LI>The ASCII left parenthesis character U+0028.</LI>
128 *   <LI>The ASCII right parenthesis character U+0029.</LI>
129 *   <LI>The ASCII plus sign character U+002B.</LI>
130 *   <LI>The ASCII comma character U+002C.</LI>
131 *   <LI>The ASCII minus sign (aka hyphen) character U+002D.</LI>
132 *   <LI>The ASCII period character U+002E.</LI>
133 *   <LI>The ASCII forward slash character U+002F.</LI>
134 *   <LI>The ASCII colon character U+003A.</LI>
135 *   <LI>The ASCII equals sign character U+003D.</LI>
136 *   <LI>The ASCII question mark character U+003F.</LI>
137 * </UL>
138 */
139@NotMutable()
140@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
141public final class JSONString
142       extends JSONValue
143{
144  /**
145   * The serial version UID for this serializable class.
146   */
147  private static final long serialVersionUID = -4677194657299153890L;
148
149
150
151  // The JSON-formatted string representation for this JSON string.  It will be
152  // surrounded by quotation marks and any necessary escaping will have been
153  // performed.
154  private String jsonStringRepresentation;
155
156  // The string value for this object.
157  private final String value;
158
159
160
161  /**
162   * Creates a new JSON string.
163   *
164   * @param  value  The string to represent in this JSON value.  It must not be
165   *                {@code null}.
166   */
167  public JSONString(final String value)
168  {
169    this.value = value;
170    jsonStringRepresentation = null;
171  }
172
173
174
175  /**
176   * Creates a new JSON string.  This method should be used for strings parsed
177   * from the string representation of a JSON object.
178   *
179   * @param  javaString  The Java string to represent.
180   * @param  jsonString  The JSON string representation to use for the Java
181   *                     string.
182   */
183  JSONString(final String javaString, final String jsonString)
184  {
185    value = javaString;
186    jsonStringRepresentation = jsonString;
187  }
188
189
190
191  /**
192   * Retrieves the string value for this object.  This will be the interpreted
193   * value, without the surrounding quotation marks or escaping.
194   *
195   * @return  The string value for this object.
196   */
197  public String stringValue()
198  {
199    return value;
200  }
201
202
203
204  /**
205   * {@inheritDoc}
206   */
207  @Override()
208  public int hashCode()
209  {
210    return stringValue().hashCode();
211  }
212
213
214
215  /**
216   * {@inheritDoc}
217   */
218  @Override()
219  public boolean equals(final Object o)
220  {
221    if (o == this)
222    {
223      return true;
224    }
225
226    if (o instanceof JSONString)
227    {
228      final JSONString s = (JSONString) o;
229      return value.equals(s.value);
230    }
231
232    return false;
233  }
234
235
236
237  /**
238   * Indicates whether the value of this JSON string matches that of the
239   * provided string, optionally ignoring differences in capitalization.
240   *
241   * @param  s           The JSON string to compare against this JSON string.
242   *                     It must not be {@code null}.
243   * @param  ignoreCase  Indicates whether to ignore differences in
244   *                     capitalization.
245   *
246   * @return  {@code true} if the value of this JSON string matches the value of
247   *          the provided string (optionally ignoring differences in
248   *          capitalization), or {@code false} if not.
249   */
250  public boolean equals(final JSONString s, final boolean ignoreCase)
251  {
252    if (ignoreCase)
253    {
254      return value.equalsIgnoreCase(s.value);
255    }
256    else
257    {
258      return value.equals(s.value);
259    }
260  }
261
262
263
264  /**
265   * {@inheritDoc}
266   */
267  @Override()
268  public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
269                        final boolean ignoreValueCase,
270                        final boolean ignoreArrayOrder)
271  {
272    return ((v instanceof JSONString) &&
273         equals((JSONString) v, ignoreValueCase));
274  }
275
276
277
278  /**
279   * Retrieves a string representation of this JSON string as it should appear
280   * in a JSON object, including the surrounding quotation marks and any
281   * appropriate escaping  To obtain the string to which this value refers
282   * without the surrounding quotation marks or escaping, use the
283   * {@link #stringValue()} method.
284   * <BR><BR>
285   * If the object containing this string was decoded from a string, then this
286   * method will use the same string representation as in that original object.
287   * Otherwise, the string representation will be constructed.
288   *
289   * @return  A string representation of this value as it should appear in a
290   *          JSON object.
291   */
292  @Override()
293  public String toString()
294  {
295    if (jsonStringRepresentation == null)
296    {
297      final StringBuilder buffer = new StringBuilder();
298      toString(buffer);
299      jsonStringRepresentation = buffer.toString();
300    }
301
302    return jsonStringRepresentation;
303  }
304
305
306
307  /**
308   * Appends a string representation of this JSON string as it should appear
309   * in a JSON object, including the surrounding quotation marks and any
310   * appropriate escaping, to the provided buffer.  To obtain the string to
311   * which this value refers without the surrounding quotation marks or
312   * escaping, use the {@link #stringValue()} method.
313   * <BR><BR>
314   * If the object containing this string was decoded from a string, then this
315   * method will use the same string representation as in that original object.
316   * Otherwise, the string representation will be constructed.
317   *
318   * @param  buffer  The buffer to which the information should be appended.
319   */
320  @Override()
321  public void toString(final StringBuilder buffer)
322  {
323    if (jsonStringRepresentation != null)
324    {
325      buffer.append(jsonStringRepresentation);
326    }
327    else
328    {
329      final boolean emptyBufferProvided = (buffer.length() == 0);
330      encodeString(value, buffer);
331
332      if (emptyBufferProvided)
333      {
334        jsonStringRepresentation = buffer.toString();
335      }
336    }
337  }
338
339
340
341  /**
342   * Retrieves a single-line representation of this JSON string as it should
343   * appear in a JSON object, including the surrounding quotation marks and any
344   * appropriate escaping.  To obtain the string to which this value refers
345   * without the surrounding quotation marks or escaping, use the
346   * {@link #stringValue()} method.
347   *
348   * @return  A single-line representation of this value as it should appear in
349   *          a JSON object.
350   */
351  @Override()
352  public String toSingleLineString()
353  {
354    return toString();
355  }
356
357
358
359  /**
360   * Appends a single-line string representation of this JSON string as it
361   * should appear in a JSON object, including the surrounding quotation marks
362   * and any appropriate escaping, to the provided buffer.  To obtain the string
363   * to which this value refers without the surrounding quotation marks or
364   * escaping, use the {@link #stringValue()} method.
365   *
366   * @param  buffer  The buffer to which the information should be appended.
367   */
368  @Override()
369  public void toSingleLineString(final StringBuilder buffer)
370  {
371    toString(buffer);
372  }
373
374
375
376  /**
377   * Appends a minimally-escaped JSON representation of the provided string to
378   * the given buffer.  When escaping is required, the most user-friendly form
379   * of escaping will be used.
380   *
381   * @param  s       The string to be encoded.
382   * @param  buffer  The buffer to which the encoded representation should be
383   *                 appended.
384   */
385  static void encodeString(final String s, final StringBuilder buffer)
386  {
387    buffer.append('"');
388
389    for (final char c : s.toCharArray())
390    {
391      switch (c)
392      {
393        case '"':
394          buffer.append("\\\"");
395          break;
396        case '\\':
397          buffer.append("\\\\");
398          break;
399        case '\b': // backspace
400          buffer.append("\\b");
401          break;
402        case '\f': // formfeed
403          buffer.append("\\f");
404          break;
405        case '\n': // newline
406          buffer.append("\\n");
407          break;
408        case '\r': // carriage return
409          buffer.append("\\r");
410          break;
411        case '\t': // horizontal tab
412          buffer.append("\\t");
413          break;
414        default:
415          if (c <= '\u001F')
416          {
417            buffer.append("\\u");
418            buffer.append(String.format("%04X", (int) c));
419          }
420          else
421          {
422            buffer.append(c);
423          }
424          break;
425      }
426    }
427
428    buffer.append('"');
429  }
430
431
432
433  /**
434   * Appends a minimally-escaped JSON representation of the provided string to
435   * the given buffer.  When escaping is required, the most user-friendly form
436   * of escaping will be used.
437   *
438   * @param  s       The string to be encoded.
439   * @param  buffer  The buffer to which the encoded representation should be
440   *                 appended.
441   */
442  static void encodeString(final String s, final ByteStringBuffer buffer)
443  {
444    buffer.append('"');
445
446    for (final char c : s.toCharArray())
447    {
448      switch (c)
449      {
450        case '"':
451          buffer.append("\\\"");
452          break;
453        case '\\':
454          buffer.append("\\\\");
455          break;
456        case '\b': // backspace
457          buffer.append("\\b");
458          break;
459        case '\f': // formfeed
460          buffer.append("\\f");
461          break;
462        case '\n': // newline
463          buffer.append("\\n");
464          break;
465        case '\r': // carriage return
466          buffer.append("\\r");
467          break;
468        case '\t': // horizontal tab
469          buffer.append("\\t");
470          break;
471        default:
472          if (c <= '\u001F')
473          {
474            buffer.append("\\u");
475            buffer.append(String.format("%04X", (int) c));
476          }
477          else
478          {
479            buffer.append(c);
480          }
481          break;
482      }
483    }
484
485    buffer.append('"');
486  }
487
488
489
490  /**
491   * Retrieves a normalized representation of this JSON string as it should
492   * appear in a JSON object, including the surrounding quotes and any
493   * appropriate escaping.  The normalized representation will use the unescaped
494   * ASCII representation of all of the following characters:
495   * <UL>
496   *   <LI>The letters a through z (ASCII character codes 0x61 through
497   *       0x7A).</LI>
498   *   <LI>The digits 0 through 9 (ASCII character codes 0x30 through
499   *       0x39).</LI>
500   *   <LI>The space (ASCII character code 0x20).</LI>
501   *   <LI>The single quote (ASCII character code 0x27).</LI>
502   *   <LI>The left parenthesis (ASCII character code 0x28).</LI>
503   *   <LI>The right parenthesis (ASCII character code 0x29).</LI>
504   *   <LI>The plus sign (ASCII character code 0x2B).</LI>
505   *   <LI>The comma (ASCII character code 0x2C).</LI>
506   *   <LI>The hyphen (ASCII character code 0x2D).</LI>
507   *   <LI>The period (ASCII character code 0x2E).</LI>
508   *   <LI>The forward slash (ASCII character code 0x2F).</LI>
509   *   <LI>The colon (ASCII character code 0x3A).</LI>
510   *   <LI>The equal sign (ASCII character code 0x3D).</LI>
511   *   <LI>The question mark (ASCII character code 0x3F).</LI>
512   * </UL>
513   * All characters except those listed above will be escaped using their
514   * Unicode representation.
515   *
516   * @return  A normalized representation of this JSON string as it should
517   *          appear in a JSON object, including
518   */
519  @Override()
520  public String toNormalizedString()
521  {
522    final StringBuilder buffer = new StringBuilder();
523    toNormalizedString(buffer);
524    return buffer.toString();
525  }
526
527
528
529  /**
530   * Appends a normalized representation of this JSON string as it should
531   * appear in a JSON object, including the surrounding quotes and any
532   * appropriate escaping, to the provided buffer.  The normalized
533   * representation will use the unescaped ASCII representation of all of the
534   * following characters:
535   * <UL>
536   *   <LI>The letters a through z (ASCII character codes 0x61 through
537   *       0x7A).</LI>
538   *   <LI>The digits 0 through 9 (ASCII character codes 0x30 through
539   *       0x39).</LI>
540   *   <LI>The space (ASCII character code 0x20).</LI>
541   *   <LI>The single quote (ASCII character code 0x27).</LI>
542   *   <LI>The left parenthesis (ASCII character code 0x28).</LI>
543   *   <LI>The right parenthesis (ASCII character code 0x29).</LI>
544   *   <LI>The plus sign (ASCII character code 0x2B).</LI>
545   *   <LI>The comma (ASCII character code 0x2C).</LI>
546   *   <LI>The hyphen (ASCII character code 0x2D).</LI>
547   *   <LI>The period (ASCII character code 0x2E).</LI>
548   *   <LI>The forward slash (ASCII character code 0x2F).</LI>
549   *   <LI>The colon (ASCII character code 0x3A).</LI>
550   *   <LI>The equal sign (ASCII character code 0x3D).</LI>
551   *   <LI>The question mark (ASCII character code 0x3F).</LI>
552   * </UL>
553   * All characters except those listed above will be escaped using their
554   * Unicode representation.
555   *
556   * @param  buffer  The buffer to which the information should be appended.
557   */
558  @Override()
559  public void toNormalizedString(final StringBuilder buffer)
560  {
561    toNormalizedString(buffer, false, true, false);
562  }
563
564
565
566  /**
567   * Retrieves a normalized representation of this JSON string as it should
568   * appear in a JSON object, including the surrounding quotes and any
569   * appropriate escaping.  The normalized representation will use the unescaped
570   * ASCII representation of all of the following characters:
571   * <UL>
572   *   <LI>The letters a through z (ASCII character codes 0x61 through
573   *       0x7A).</LI>
574   *   <LI>The letters A through Z (ASCII character codes 0x41 through 0x5A).
575   *       These characters will only be used if {@code ignoreValueCase} is
576   *       {@code false}.</LI>
577   *   <LI>The digits 0 through 9 (ASCII character codes 0x30 through
578   *       0x39).</LI>
579   *   <LI>The space (ASCII character code 0x20).</LI>
580   *   <LI>The single quote (ASCII character code 0x27).</LI>
581   *   <LI>The left parenthesis (ASCII character code 0x28).</LI>
582   *   <LI>The right parenthesis (ASCII character code 0x29).</LI>
583   *   <LI>The plus sign (ASCII character code 0x2B).</LI>
584   *   <LI>The comma (ASCII character code 0x2C).</LI>
585   *   <LI>The hyphen (ASCII character code 0x2D).</LI>
586   *   <LI>The period (ASCII character code 0x2E).</LI>
587   *   <LI>The forward slash (ASCII character code 0x2F).</LI>
588   *   <LI>The colon (ASCII character code 0x3A).</LI>
589   *   <LI>The equal sign (ASCII character code 0x3D).</LI>
590   *   <LI>The question mark (ASCII character code 0x3F).</LI>
591   * </UL>
592   * All characters except those listed above will be escaped using their
593   * Unicode representation.
594   *
595   * @param  ignoreFieldNameCase  Indicates whether field names should be
596   *                              treated in a case-sensitive (if {@code false})
597   *                              or case-insensitive (if {@code true}) manner.
598   * @param  ignoreValueCase      Indicates whether string field values should
599   *                              be treated in a case-sensitive (if
600   *                              {@code false}) or case-insensitive (if
601   *                              {@code true}) manner.
602   * @param  ignoreArrayOrder     Indicates whether the order of elements in an
603   *                              array should be considered significant (if
604   *                              {@code false}) or insignificant (if
605   *                              {@code true}).
606   *
607   * @return  A normalized representation of this JSON string as it should
608   *          appear in a JSON object, including
609   */
610  @Override()
611  public String toNormalizedString(final boolean ignoreFieldNameCase,
612                                   final boolean ignoreValueCase,
613                                   final boolean ignoreArrayOrder)
614  {
615    final StringBuilder buffer = new StringBuilder();
616    toNormalizedString(buffer, ignoreFieldNameCase, ignoreValueCase,
617         ignoreArrayOrder);
618    return buffer.toString();
619  }
620
621
622
623  /**
624   * Appends a normalized representation of this JSON string as it should
625   * appear in a JSON object, including the surrounding quotes and any
626   * appropriate escaping, to the provided buffer.  The normalized
627   * representation will use the unescaped ASCII representation of all of the
628   * following characters:
629   * <UL>
630   *   <LI>The letters a through z (ASCII character codes 0x61 through
631   *       0x7A).</LI>
632   *   <LI>The letters A through Z (ASCII character codes 0x41 through 0x5A).
633   *       These characters will only be used if {@code ignoreValueCase} is
634   *       {@code false}.</LI>
635   *   <LI>The digits 0 through 9 (ASCII character codes 0x30 through
636   *       0x39).</LI>
637   *   <LI>The space (ASCII character code 0x20).</LI>
638   *   <LI>The single quote (ASCII character code 0x27).</LI>
639   *   <LI>The left parenthesis (ASCII character code 0x28).</LI>
640   *   <LI>The right parenthesis (ASCII character code 0x29).</LI>
641   *   <LI>The plus sign (ASCII character code 0x2B).</LI>
642   *   <LI>The comma (ASCII character code 0x2C).</LI>
643   *   <LI>The hyphen (ASCII character code 0x2D).</LI>
644   *   <LI>The period (ASCII character code 0x2E).</LI>
645   *   <LI>The forward slash (ASCII character code 0x2F).</LI>
646   *   <LI>The colon (ASCII character code 0x3A).</LI>
647   *   <LI>The equal sign (ASCII character code 0x3D).</LI>
648   *   <LI>The question mark (ASCII character code 0x3F).</LI>
649   * </UL>
650   * All characters except those listed above will be escaped using their
651   * Unicode representation.
652   *
653   * @param  buffer               The buffer to which the information should be
654   *                              appended.
655   * @param  ignoreFieldNameCase  Indicates whether field names should be
656   *                              treated in a case-sensitive (if {@code false})
657   *                              or case-insensitive (if {@code true}) manner.
658   * @param  ignoreValueCase      Indicates whether string field values should
659   *                              be treated in a case-sensitive (if
660   *                              {@code false}) or case-insensitive (if
661   *                              {@code true}) manner.
662   * @param  ignoreArrayOrder     Indicates whether the order of elements in an
663   *                              array should be considered significant (if
664   *                              {@code false}) or insignificant (if
665   *                              {@code true}).
666   */
667  @Override()
668  public void toNormalizedString(final StringBuilder buffer,
669                                 final boolean ignoreFieldNameCase,
670                                 final boolean ignoreValueCase,
671                                 final boolean ignoreArrayOrder)
672  {
673    buffer.append('"');
674
675    final char[] charArray;
676    if (ignoreValueCase)
677    {
678      charArray = StaticUtils.toLowerCase(value).toCharArray();
679    }
680    else
681    {
682      charArray = value.toCharArray();
683    }
684
685    for (final char c : charArray)
686    {
687      if (StaticUtils.isPrintable(c))
688      {
689        buffer.append(c);
690      }
691      else
692      {
693        buffer.append("\\u");
694        buffer.append(String.format("%04X", (int) c));
695      }
696    }
697
698    buffer.append('"');
699  }
700
701
702
703  /**
704   * {@inheritDoc}
705   */
706  @Override()
707  public void appendToJSONBuffer(final JSONBuffer buffer)
708  {
709    buffer.appendString(value);
710  }
711
712
713
714  /**
715   * {@inheritDoc}
716   */
717  @Override()
718  public void appendToJSONBuffer(final String fieldName,
719                                 final JSONBuffer buffer)
720  {
721    buffer.appendString(fieldName, value);
722  }
723}