001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.args;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Set;
031import java.util.regex.Matcher;
032import java.util.regex.Pattern;
033
034import com.unboundid.util.Mutable;
035import com.unboundid.util.StaticUtils;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038
039import static com.unboundid.util.args.ArgsMessages.*;
040
041
042
043/**
044 * This class defines an argument that is intended to hold one or more string
045 * values.  String arguments must take values.  By default, any value will be
046 * allowed, but it is possible to restrict the set of values so that only values
047 * from a specified set (ignoring differences in capitalization) will be
048 * allowed.
049 */
050@Mutable()
051@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
052public final class StringArgument
053       extends Argument
054{
055  /**
056   * The serial version UID for this serializable class.
057   */
058  private static final long serialVersionUID = 1088032496970585118L;
059
060
061
062  // The set of values assigned to this argument.
063  private final ArrayList<String> values;
064
065  // The argument value validators that have been registered for this argument.
066  private final List<ArgumentValueValidator> validators;
067
068  // The list of default values that will be used if no values were provided.
069  private final List<String> defaultValues;
070
071  // A regular expression that may be enforced for values of this argument.
072  private volatile Pattern valueRegex;
073
074  // The set of allowed values for this argument.
075  private final Set<String> allowedValues;
076
077  // A human-readable explanation of the regular expression pattern.
078  private volatile String valueRegexExplanation;
079
080
081
082  /**
083   * Creates a new string argument with the provided information.  It will not
084   * be required, will permit at most one value, will use a default placeholder,
085   * will not have any default value, and will not place any restriction on
086   * values that may be assigned.
087   *
088   * @param  shortIdentifier   The short identifier for this argument.  It may
089   *                           not be {@code null} if the long identifier is
090   *                           {@code null}.
091   * @param  longIdentifier    The long identifier for this argument.  It may
092   *                           not be {@code null} if the short identifier is
093   *                           {@code null}.
094   * @param  description       A human-readable description for this argument.
095   *                           It must not be {@code null}.
096   *
097   * @throws  ArgumentException  If there is a problem with the definition of
098   *                             this argument.
099   */
100  public StringArgument(final Character shortIdentifier,
101                        final String longIdentifier, final String description)
102         throws ArgumentException
103  {
104    this(shortIdentifier, longIdentifier, false, 1, null, description);
105  }
106
107
108
109  /**
110   * Creates a new string argument with the provided information.  There will
111   * not be any default values, nor will there be any restriction on values that
112   * may be assigned to this argument.
113   *
114   * @param  shortIdentifier   The short identifier for this argument.  It may
115   *                           not be {@code null} if the long identifier is
116   *                           {@code null}.
117   * @param  longIdentifier    The long identifier for this argument.  It may
118   *                           not be {@code null} if the short identifier is
119   *                           {@code null}.
120   * @param  isRequired        Indicates whether this argument is required to
121   *                           be provided.
122   * @param  maxOccurrences    The maximum number of times this argument may be
123   *                           provided on the command line.  A value less than
124   *                           or equal to zero indicates that it may be present
125   *                           any number of times.
126   * @param  valuePlaceholder  A placeholder to display in usage information to
127   *                           indicate that a value must be provided.  It may
128   *                           be {@code null} if a default placeholder should
129   *                           be used.
130   * @param  description       A human-readable description for this argument.
131   *                           It must not be {@code null}.
132   *
133   * @throws  ArgumentException  If there is a problem with the definition of
134   *                             this argument.
135   */
136  public StringArgument(final Character shortIdentifier,
137                        final String longIdentifier, final boolean isRequired,
138                        final int maxOccurrences, final String valuePlaceholder,
139                        final String description)
140         throws ArgumentException
141  {
142    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
143         valuePlaceholder, description, null, (List<String>) null);
144  }
145
146
147
148  /**
149   * Creates a new string argument with the provided information.  There will
150   * not be any default values.
151   *
152   * @param  shortIdentifier   The short identifier for this argument.  It may
153   *                           not be {@code null} if the long identifier is
154   *                           {@code null}.
155   * @param  longIdentifier    The long identifier for this argument.  It may
156   *                           not be {@code null} if the short identifier is
157   *                           {@code null}.
158   * @param  isRequired        Indicates whether this argument is required to
159   *                           be provided.
160   * @param  maxOccurrences    The maximum number of times this argument may be
161   *                           provided on the command line.  A value less than
162   *                           or equal to zero indicates that it may be present
163   *                           any number of times.
164   * @param  valuePlaceholder  A placeholder to display in usage information to
165   *                           indicate that a value must be provided.  It may
166   *                           be {@code null} if a default placeholder should
167   *                           be used.
168   * @param  description       A human-readable description for this argument.
169   *                           It must not be {@code null}.
170   * @param  allowedValues     The set of allowed values for this argument, or
171   *                           {@code null} if it should not be restricted.
172   *
173   * @throws  ArgumentException  If there is a problem with the definition of
174   *                             this argument.
175   */
176  public StringArgument(final Character shortIdentifier,
177                        final String longIdentifier, final boolean isRequired,
178                        final int maxOccurrences, final String valuePlaceholder,
179                        final String description,
180                        final Set<String> allowedValues)
181         throws ArgumentException
182  {
183    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
184         valuePlaceholder, description, allowedValues, (List<String>) null);
185  }
186
187
188
189  /**
190   * Creates a new string argument with the provided information.  There will
191   * not be any restriction on values that may be assigned to this argument.
192   *
193   * @param  shortIdentifier   The short identifier for this argument.  It may
194   *                           not be {@code null} if the long identifier is
195   *                           {@code null}.
196   * @param  longIdentifier    The long identifier for this argument.  It may
197   *                           not be {@code null} if the short identifier is
198   *                           {@code null}.
199   * @param  isRequired        Indicates whether this argument is required to
200   *                           be provided.
201   * @param  maxOccurrences    The maximum number of times this argument may be
202   *                           provided on the command line.  A value less than
203   *                           or equal to zero indicates that it may be present
204   *                           any number of times.
205   * @param  valuePlaceholder  A placeholder to display in usage information to
206   *                           indicate that a value must be provided.  It may
207   *                           be {@code null} if a default placeholder should
208   *                           be used.
209   * @param  description       A human-readable description for this argument.
210   *                           It must not be {@code null}.
211   * @param  defaultValue      The default value that will be used for this
212   *                           argument if no values are provided.  It may be
213   *                           {@code null} if there should not be a default
214   *                           value.
215   *
216   * @throws  ArgumentException  If there is a problem with the definition of
217   *                             this argument.
218   */
219  public StringArgument(final Character shortIdentifier,
220                        final String longIdentifier, final boolean isRequired,
221                        final int maxOccurrences, final String valuePlaceholder,
222                        final String description,
223                        final String defaultValue)
224         throws ArgumentException
225  {
226    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
227         valuePlaceholder, description, null,
228         ((defaultValue == null)
229              ? null
230              : Collections.singletonList(defaultValue)));
231  }
232
233
234
235  /**
236   * Creates a new string argument with the provided information.  There will
237   * not be any restriction on values that may be assigned to this argument.
238   *
239   * @param  shortIdentifier   The short identifier for this argument.  It may
240   *                           not be {@code null} if the long identifier is
241   *                           {@code null}.
242   * @param  longIdentifier    The long identifier for this argument.  It may
243   *                           not be {@code null} if the short identifier is
244   *                           {@code null}.
245   * @param  isRequired        Indicates whether this argument is required to
246   *                           be provided.
247   * @param  maxOccurrences    The maximum number of times this argument may be
248   *                           provided on the command line.  A value less than
249   *                           or equal to zero indicates that it may be present
250   *                           any number of times.
251   * @param  valuePlaceholder  A placeholder to display in usage information to
252   *                           indicate that a value must be provided.  It may
253   *                           be {@code null} if a default placeholder should
254   *                           be used.
255   * @param  description       A human-readable description for this argument.
256   *                           It must not be {@code null}.
257   * @param  defaultValues     The set of default values that will be used for
258   *                           this argument if no values are provided.
259   *
260   * @throws  ArgumentException  If there is a problem with the definition of
261   *                             this argument.
262   */
263  public StringArgument(final Character shortIdentifier,
264                        final String longIdentifier, final boolean isRequired,
265                        final int maxOccurrences, final String valuePlaceholder,
266                        final String description,
267                        final List<String> defaultValues)
268         throws ArgumentException
269  {
270    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
271         valuePlaceholder, description, null, defaultValues);
272  }
273
274
275
276  /**
277   * Creates a new string argument with the provided information.
278   *
279   * @param  shortIdentifier   The short identifier for this argument.  It may
280   *                           not be {@code null} if the long identifier is
281   *                           {@code null}.
282   * @param  longIdentifier    The long identifier for this argument.  It may
283   *                           not be {@code null} if the short identifier is
284   *                           {@code null}.
285   * @param  isRequired        Indicates whether this argument is required to
286   *                           be provided.
287   * @param  maxOccurrences    The maximum number of times this argument may be
288   *                           provided on the command line.  A value less than
289   *                           or equal to zero indicates that it may be present
290   *                           any number of times.
291   * @param  valuePlaceholder  A placeholder to display in usage information to
292   *                           indicate that a value must be provided.  It may
293   *                           be {@code null} if a default placeholder should
294   *                           be used.
295   * @param  description       A human-readable description for this argument.
296   *                           It must not be {@code null}.
297   * @param  allowedValues     The set of allowed values for this argument, or
298   *                           {@code null} if it should not be restricted.
299   * @param  defaultValue      The default value that will be used for this
300   *                           argument if no values are provided.  It may be
301   *                           {@code null} if there should not be a default
302   *                           value.
303   *
304   * @throws  ArgumentException  If there is a problem with the definition of
305   *                             this argument.
306   */
307  public StringArgument(final Character shortIdentifier,
308                        final String longIdentifier, final boolean isRequired,
309                        final int maxOccurrences, final String valuePlaceholder,
310                        final String description,
311                        final Set<String> allowedValues,
312                        final String defaultValue)
313         throws ArgumentException
314  {
315    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
316         valuePlaceholder, description, allowedValues,
317         ((defaultValue == null)
318              ? null
319              : Collections.singletonList(defaultValue)));
320  }
321
322
323
324  /**
325   * Creates a new string argument with the provided information.
326   *
327   * @param  shortIdentifier   The short identifier for this argument.  It may
328   *                           not be {@code null} if the long identifier is
329   *                           {@code null}.
330   * @param  longIdentifier    The long identifier for this argument.  It may
331   *                           not be {@code null} if the short identifier is
332   *                           {@code null}.
333   * @param  isRequired        Indicates whether this argument is required to
334   *                           be provided.
335   * @param  maxOccurrences    The maximum number of times this argument may be
336   *                           provided on the command line.  A value less than
337   *                           or equal to zero indicates that it may be present
338   *                           any number of times.
339   * @param  valuePlaceholder  A placeholder to display in usage information to
340   *                           indicate that a value must be provided.  It may
341   *                           be {@code null} if a default placeholder should
342   *                           be used.
343   * @param  description       A human-readable description for this argument.
344   *                           It must not be {@code null}.
345   * @param  allowedValues     The set of allowed values for this argument, or
346   *                           {@code null} if it should not be restricted.
347   * @param  defaultValues     The set of default values that will be used for
348   *                           this argument if no values are provided.
349   *
350   * @throws  ArgumentException  If there is a problem with the definition of
351   *                             this argument.
352   */
353  public StringArgument(final Character shortIdentifier,
354                        final String longIdentifier, final boolean isRequired,
355                        final int maxOccurrences, final String valuePlaceholder,
356                        final String description,
357                        final Set<String> allowedValues,
358                        final List<String> defaultValues)
359         throws ArgumentException
360  {
361    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
362         (valuePlaceholder == null)
363              ? INFO_PLACEHOLDER_VALUE.get()
364              : valuePlaceholder,
365         description);
366
367    if ((allowedValues == null) || allowedValues.isEmpty())
368    {
369      this.allowedValues = null;
370    }
371    else
372    {
373      final HashSet<String> lowerValues =
374           new HashSet<>(StaticUtils.computeMapCapacity(allowedValues.size()));
375      for (final String s : allowedValues)
376      {
377        lowerValues.add(StaticUtils.toLowerCase(s));
378      }
379      this.allowedValues = Collections.unmodifiableSet(lowerValues);
380    }
381
382    if ((defaultValues == null) || defaultValues.isEmpty())
383    {
384      this.defaultValues = null;
385    }
386    else
387    {
388      this.defaultValues = Collections.unmodifiableList(defaultValues);
389    }
390
391    if ((this.allowedValues != null) && (this.defaultValues != null))
392    {
393      for (final String s : this.defaultValues)
394      {
395        final String lowerDefault = StaticUtils.toLowerCase(s);
396        if (! this.allowedValues.contains(lowerDefault))
397        {
398          throw new ArgumentException(
399               ERR_ARG_DEFAULT_VALUE_NOT_ALLOWED.get(s, getIdentifierString()));
400        }
401      }
402    }
403
404    values                = new ArrayList<>(5);
405    validators            = new ArrayList<>(5);
406    valueRegex            = null;
407    valueRegexExplanation = null;
408  }
409
410
411
412  /**
413   * Creates a new string argument that is a "clean" copy of the provided source
414   * argument.
415   *
416   * @param  source  The source argument to use for this argument.
417   */
418  private StringArgument(final StringArgument source)
419  {
420    super(source);
421
422    allowedValues         = source.allowedValues;
423    defaultValues         = source.defaultValues;
424    valueRegex            = source.valueRegex;
425    valueRegexExplanation = source.valueRegexExplanation;
426    values                = new ArrayList<>(5);
427    validators            = new ArrayList<>(source.validators);
428  }
429
430
431
432  /**
433   * Retrieves the set of allowed values for this argument, if applicable.
434   *
435   * @return  The set of allowed values for this argument, or {@code null} if
436   *          there is no restriction on the allowed values.
437   */
438  public Set<String> getAllowedValues()
439  {
440    return allowedValues;
441  }
442
443
444
445  /**
446   * Retrieves the list of default values for this argument, which will be used
447   * if no values were provided.
448   *
449   * @return   The list of default values for this argument, or {@code null} if
450   *           there are no default values.
451   */
452  public List<String> getDefaultValues()
453  {
454    return defaultValues;
455  }
456
457
458
459  /**
460   * Retrieves the regular expression that values of this argument will be
461   * required to match, if any.
462   *
463   * @return  The regular expression that values of this argument will be
464   *          required to match, or {@code null} if none is defined.
465   */
466  public Pattern getValueRegex()
467  {
468    return valueRegex;
469  }
470
471
472
473  /**
474   * Retrieves a human-readable explanation of the regular expression pattern
475   * that may be required to match any provided values, if any.
476   *
477   * @return  A human-readable explanation of the regular expression pattern, or
478   *          {@code null} if none is available.
479   */
480  public String getValueRegexExplanation()
481  {
482    return valueRegexExplanation;
483  }
484
485
486
487  /**
488   * Specifies the regular expression that values of this argument will be
489   * required to match, if any.
490   *
491   * @param  valueRegex   The regular expression that values of this argument
492   *                      will be required to match.  It may be {@code null} if
493   *                      no pattern matching should be required.
494   * @param  explanation  A human-readable explanation for the pattern which may
495   *                      be used to clarify the kinds of values that are
496   *                      acceptable.  It may be {@code null} if no pattern
497   *                      matching should be required, or if the regular
498   *                      expression pattern should be sufficiently clear for
499   *                      the target audience.
500   */
501  public void setValueRegex(final Pattern valueRegex,
502                            final String explanation)
503  {
504    this.valueRegex = valueRegex;
505    valueRegexExplanation = explanation;
506  }
507
508
509
510  /**
511   * Updates this argument to ensure that the provided validator will be invoked
512   * for any values provided to this argument.  This validator will be invoked
513   * after all other validation has been performed for this argument.
514   *
515   * @param  validator  The argument value validator to be invoked.  It must not
516   *                    be {@code null}.
517   */
518  public void addValueValidator(final ArgumentValueValidator validator)
519  {
520    validators.add(validator);
521  }
522
523
524
525  /**
526   * {@inheritDoc}
527   */
528  @Override()
529  protected void addValue(final String valueString)
530            throws ArgumentException
531  {
532    final String lowerValue = StaticUtils.toLowerCase(valueString);
533    if (allowedValues != null)
534    {
535      if (! allowedValues.contains(lowerValue))
536      {
537        throw new ArgumentException(ERR_ARG_VALUE_NOT_ALLOWED.get(
538                                         valueString, getIdentifierString()));
539      }
540    }
541
542    if (values.size() >= getMaxOccurrences())
543    {
544      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
545                                       getIdentifierString()));
546    }
547
548    if (valueRegex != null)
549    {
550      final Matcher matcher = valueRegex.matcher(valueString);
551      if (! matcher.matches())
552      {
553        final String pattern = valueRegex.pattern();
554        if (valueRegexExplanation == null)
555        {
556          throw new ArgumentException(
557               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITHOUT_EXPLANATION.get(
558                    valueString, getIdentifierString(), pattern));
559        }
560        else
561        {
562          throw new ArgumentException(
563               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITH_EXPLANATION.get(
564                    valueString, getIdentifierString(), pattern,
565                    valueRegexExplanation));
566        }
567      }
568    }
569
570    for (final ArgumentValueValidator v : validators)
571    {
572      v.validateArgumentValue(this, valueString);
573    }
574
575    values.add(valueString);
576  }
577
578
579
580  /**
581   * Retrieves the value for this argument, or the default value if none was
582   * provided.  If this argument has multiple values, then the first will be
583   * returned.
584   *
585   * @return  The value for this argument, or the default value if none was
586   *          provided, or {@code null} if it does not have any values or
587   *          default values.
588   */
589  public String getValue()
590  {
591    if (values.isEmpty())
592    {
593      if ((defaultValues == null) || defaultValues.isEmpty())
594      {
595        return null;
596      }
597      else
598      {
599        return defaultValues.get(0);
600      }
601    }
602
603    return values.get(0);
604  }
605
606
607
608  /**
609   * Retrieves the set of values for this argument, or the default values if
610   * none were provided.
611   *
612   * @return  The set of values for this argument, or the default values if none
613   *          were provided.
614   */
615  public List<String> getValues()
616  {
617    if (values.isEmpty() && (defaultValues != null))
618    {
619      return defaultValues;
620    }
621
622    return Collections.unmodifiableList(values);
623  }
624
625
626
627  /**
628   * {@inheritDoc}
629   */
630  @Override()
631  public List<String> getValueStringRepresentations(final boolean useDefault)
632  {
633    if (! values.isEmpty())
634    {
635      return Collections.unmodifiableList(values);
636    }
637    else if (useDefault && (defaultValues != null))
638    {
639      return Collections.unmodifiableList(defaultValues);
640    }
641    else
642    {
643      return Collections.emptyList();
644    }
645  }
646
647
648
649  /**
650   * {@inheritDoc}
651   */
652  @Override()
653  protected boolean hasDefaultValue()
654  {
655    return ((defaultValues != null) && (! defaultValues.isEmpty()));
656  }
657
658
659
660  /**
661   * {@inheritDoc}
662   */
663  @Override()
664  public String getDataTypeName()
665  {
666    return INFO_STRING_TYPE_NAME.get();
667  }
668
669
670
671  /**
672   * {@inheritDoc}
673   */
674  @Override()
675  public String getValueConstraints()
676  {
677    StringBuilder buffer = null;
678
679    if (valueRegex != null)
680    {
681      buffer = new StringBuilder();
682      final String pattern = valueRegex.pattern();
683      if ((valueRegexExplanation == null) ||
684          (valueRegexExplanation.length() == 0))
685      {
686        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
687             pattern));
688      }
689      else
690      {
691        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
692             pattern, valueRegexExplanation));
693      }
694    }
695
696    if ((allowedValues != null) && (! allowedValues.isEmpty()))
697    {
698      if (buffer == null)
699      {
700        buffer = new StringBuilder();
701      }
702      else
703      {
704        buffer.append("  ");
705      }
706
707      buffer.append(INFO_STRING_CONSTRAINTS_ALLOWED_VALUE.get());
708      buffer.append("  ");
709
710      final Iterator<String> iterator = allowedValues.iterator();
711      while (iterator.hasNext())
712      {
713        buffer.append('\'');
714        buffer.append(iterator.next());
715        buffer.append('\'');
716
717        if (iterator.hasNext())
718        {
719          buffer.append(", ");
720        }
721      }
722      buffer.append('.');
723    }
724
725    if (buffer == null)
726    {
727      return null;
728    }
729    else
730    {
731      return buffer.toString();
732    }
733  }
734
735
736
737  /**
738   * {@inheritDoc}
739   */
740  @Override()
741  protected void reset()
742  {
743    super.reset();
744    values.clear();
745  }
746
747
748
749  /**
750   * {@inheritDoc}
751   */
752  @Override()
753  public StringArgument getCleanCopy()
754  {
755    return new StringArgument(this);
756  }
757
758
759
760  /**
761   * {@inheritDoc}
762   */
763  @Override()
764  protected void addToCommandLine(final List<String> argStrings)
765  {
766    if (values != null)
767    {
768      for (final String s : values)
769      {
770        argStrings.add(getIdentifierString());
771        if (isSensitive())
772        {
773          argStrings.add("***REDACTED***");
774        }
775        else
776        {
777          argStrings.add(s);
778        }
779      }
780    }
781  }
782
783
784
785  /**
786   * {@inheritDoc}
787   */
788  @Override()
789  public void toString(final StringBuilder buffer)
790  {
791    buffer.append("StringArgument(");
792    appendBasicToStringInfo(buffer);
793
794    if ((allowedValues != null) && (! allowedValues.isEmpty()))
795    {
796      buffer.append(", allowedValues={");
797      final Iterator<String> iterator = allowedValues.iterator();
798      while (iterator.hasNext())
799      {
800        buffer.append('\'');
801        buffer.append(iterator.next());
802        buffer.append('\'');
803
804        if (iterator.hasNext())
805        {
806          buffer.append(", ");
807        }
808      }
809      buffer.append('}');
810    }
811
812    if (valueRegex != null)
813    {
814      buffer.append(", valueRegex='");
815      buffer.append(valueRegex.pattern());
816      buffer.append('\'');
817
818      if (valueRegexExplanation != null)
819      {
820        buffer.append(", valueRegexExplanation='");
821        buffer.append(valueRegexExplanation);
822        buffer.append('\'');
823      }
824    }
825
826    if ((defaultValues != null) && (! defaultValues.isEmpty()))
827    {
828      if (defaultValues.size() == 1)
829      {
830        buffer.append(", defaultValue='");
831        buffer.append(defaultValues.get(0));
832      }
833      else
834      {
835        buffer.append(", defaultValues={");
836
837        final Iterator<String> iterator = defaultValues.iterator();
838        while (iterator.hasNext())
839        {
840          buffer.append('\'');
841          buffer.append(iterator.next());
842          buffer.append('\'');
843
844          if (iterator.hasNext())
845          {
846            buffer.append(", ");
847          }
848        }
849
850        buffer.append('}');
851      }
852    }
853
854    buffer.append(')');
855  }
856}