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