001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 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 = new HashSet<>(allowedValues.size());
374      for (final String s : allowedValues)
375      {
376        lowerValues.add(StaticUtils.toLowerCase(s));
377      }
378      this.allowedValues = Collections.unmodifiableSet(lowerValues);
379    }
380
381    if ((defaultValues == null) || defaultValues.isEmpty())
382    {
383      this.defaultValues = null;
384    }
385    else
386    {
387      this.defaultValues = Collections.unmodifiableList(defaultValues);
388    }
389
390    if ((this.allowedValues != null) && (this.defaultValues != null))
391    {
392      for (final String s : this.defaultValues)
393      {
394        final String lowerDefault = StaticUtils.toLowerCase(s);
395        if (! this.allowedValues.contains(lowerDefault))
396        {
397          throw new ArgumentException(
398               ERR_ARG_DEFAULT_VALUE_NOT_ALLOWED.get(s, getIdentifierString()));
399        }
400      }
401    }
402
403    values                = new ArrayList<>(5);
404    validators            = new ArrayList<>(5);
405    valueRegex            = null;
406    valueRegexExplanation = null;
407  }
408
409
410
411  /**
412   * Creates a new string argument that is a "clean" copy of the provided source
413   * argument.
414   *
415   * @param  source  The source argument to use for this argument.
416   */
417  private StringArgument(final StringArgument source)
418  {
419    super(source);
420
421    allowedValues         = source.allowedValues;
422    defaultValues         = source.defaultValues;
423    valueRegex            = source.valueRegex;
424    valueRegexExplanation = source.valueRegexExplanation;
425    values                = new ArrayList<>(5);
426    validators            = new ArrayList<>(source.validators);
427  }
428
429
430
431  /**
432   * Retrieves the set of allowed values for this argument, if applicable.
433   *
434   * @return  The set of allowed values for this argument, or {@code null} if
435   *          there is no restriction on the allowed values.
436   */
437  public Set<String> getAllowedValues()
438  {
439    return allowedValues;
440  }
441
442
443
444  /**
445   * Retrieves the list of default values for this argument, which will be used
446   * if no values were provided.
447   *
448   * @return   The list of default values for this argument, or {@code null} if
449   *           there are no default values.
450   */
451  public List<String> getDefaultValues()
452  {
453    return defaultValues;
454  }
455
456
457
458  /**
459   * Retrieves the regular expression that values of this argument will be
460   * required to match, if any.
461   *
462   * @return  The regular expression that values of this argument will be
463   *          required to match, or {@code null} if none is defined.
464   */
465  public Pattern getValueRegex()
466  {
467    return valueRegex;
468  }
469
470
471
472  /**
473   * Retrieves a human-readable explanation of the regular expression pattern
474   * that may be required to match any provided values, if any.
475   *
476   * @return  A human-readable explanation of the regular expression pattern, or
477   *          {@code null} if none is available.
478   */
479  public String getValueRegexExplanation()
480  {
481    return valueRegexExplanation;
482  }
483
484
485
486  /**
487   * Specifies the regular expression that values of this argument will be
488   * required to match, if any.
489   *
490   * @param  valueRegex   The regular expression that values of this argument
491   *                      will be required to match.  It may be {@code null} if
492   *                      no pattern matching should be required.
493   * @param  explanation  A human-readable explanation for the pattern which may
494   *                      be used to clarify the kinds of values that are
495   *                      acceptable.  It may be {@code null} if no pattern
496   *                      matching should be required, or if the regular
497   *                      expression pattern should be sufficiently clear for
498   *                      the target audience.
499   */
500  public void setValueRegex(final Pattern valueRegex,
501                            final String explanation)
502  {
503    this.valueRegex = valueRegex;
504    valueRegexExplanation = explanation;
505  }
506
507
508
509  /**
510   * Updates this argument to ensure that the provided validator will be invoked
511   * for any values provided to this argument.  This validator will be invoked
512   * after all other validation has been performed for this argument.
513   *
514   * @param  validator  The argument value validator to be invoked.  It must not
515   *                    be {@code null}.
516   */
517  public void addValueValidator(final ArgumentValueValidator validator)
518  {
519    validators.add(validator);
520  }
521
522
523
524  /**
525   * {@inheritDoc}
526   */
527  @Override()
528  protected void addValue(final String valueString)
529            throws ArgumentException
530  {
531    final String lowerValue = StaticUtils.toLowerCase(valueString);
532    if (allowedValues != null)
533    {
534      if (! allowedValues.contains(lowerValue))
535      {
536        throw new ArgumentException(ERR_ARG_VALUE_NOT_ALLOWED.get(
537                                         valueString, getIdentifierString()));
538      }
539    }
540
541    if (values.size() >= getMaxOccurrences())
542    {
543      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
544                                       getIdentifierString()));
545    }
546
547    if (valueRegex != null)
548    {
549      final Matcher matcher = valueRegex.matcher(valueString);
550      if (! matcher.matches())
551      {
552        final String pattern = valueRegex.pattern();
553        if (valueRegexExplanation == null)
554        {
555          throw new ArgumentException(
556               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITHOUT_EXPLANATION.get(
557                    valueString, getIdentifierString(), pattern));
558        }
559        else
560        {
561          throw new ArgumentException(
562               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITH_EXPLANATION.get(
563                    valueString, getIdentifierString(), pattern,
564                    valueRegexExplanation));
565        }
566      }
567    }
568
569    for (final ArgumentValueValidator v : validators)
570    {
571      v.validateArgumentValue(this, valueString);
572    }
573
574    values.add(valueString);
575  }
576
577
578
579  /**
580   * Retrieves the value for this argument, or the default value if none was
581   * provided.  If this argument has multiple values, then the first will be
582   * returned.
583   *
584   * @return  The value for this argument, or the default value if none was
585   *          provided, or {@code null} if it does not have any values or
586   *          default values.
587   */
588  public String getValue()
589  {
590    if (values.isEmpty())
591    {
592      if ((defaultValues == null) || defaultValues.isEmpty())
593      {
594        return null;
595      }
596      else
597      {
598        return defaultValues.get(0);
599      }
600    }
601
602    return values.get(0);
603  }
604
605
606
607  /**
608   * Retrieves the set of values for this argument, or the default values if
609   * none were provided.
610   *
611   * @return  The set of values for this argument, or the default values if none
612   *          were provided.
613   */
614  public List<String> getValues()
615  {
616    if (values.isEmpty() && (defaultValues != null))
617    {
618      return defaultValues;
619    }
620
621    return Collections.unmodifiableList(values);
622  }
623
624
625
626  /**
627   * {@inheritDoc}
628   */
629  @Override()
630  public List<String> getValueStringRepresentations(final boolean useDefault)
631  {
632    if (! values.isEmpty())
633    {
634      return Collections.unmodifiableList(values);
635    }
636    else if (useDefault && (defaultValues != null))
637    {
638      return Collections.unmodifiableList(defaultValues);
639    }
640    else
641    {
642      return Collections.emptyList();
643    }
644  }
645
646
647
648  /**
649   * {@inheritDoc}
650   */
651  @Override()
652  protected boolean hasDefaultValue()
653  {
654    return ((defaultValues != null) && (! defaultValues.isEmpty()));
655  }
656
657
658
659  /**
660   * {@inheritDoc}
661   */
662  @Override()
663  public String getDataTypeName()
664  {
665    return INFO_STRING_TYPE_NAME.get();
666  }
667
668
669
670  /**
671   * {@inheritDoc}
672   */
673  @Override()
674  public String getValueConstraints()
675  {
676    StringBuilder buffer = null;
677
678    if (valueRegex != null)
679    {
680      buffer = new StringBuilder();
681      final String pattern = valueRegex.pattern();
682      if ((valueRegexExplanation == null) ||
683          (valueRegexExplanation.length() == 0))
684      {
685        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
686             pattern));
687      }
688      else
689      {
690        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
691             pattern, valueRegexExplanation));
692      }
693    }
694
695    if ((allowedValues != null) && (! allowedValues.isEmpty()))
696    {
697      if (buffer == null)
698      {
699        buffer = new StringBuilder();
700      }
701      else
702      {
703        buffer.append("  ");
704      }
705
706      buffer.append(INFO_STRING_CONSTRAINTS_ALLOWED_VALUE.get());
707      buffer.append("  ");
708
709      final Iterator<String> iterator = allowedValues.iterator();
710      while (iterator.hasNext())
711      {
712        buffer.append('\'');
713        buffer.append(iterator.next());
714        buffer.append('\'');
715
716        if (iterator.hasNext())
717        {
718          buffer.append(", ");
719        }
720      }
721      buffer.append('.');
722    }
723
724    if (buffer == null)
725    {
726      return null;
727    }
728    else
729    {
730      return buffer.toString();
731    }
732  }
733
734
735
736  /**
737   * {@inheritDoc}
738   */
739  @Override()
740  protected void reset()
741  {
742    super.reset();
743    values.clear();
744  }
745
746
747
748  /**
749   * {@inheritDoc}
750   */
751  @Override()
752  public StringArgument getCleanCopy()
753  {
754    return new StringArgument(this);
755  }
756
757
758
759  /**
760   * {@inheritDoc}
761   */
762  @Override()
763  protected void addToCommandLine(final List<String> argStrings)
764  {
765    if (values != null)
766    {
767      for (final String s : values)
768      {
769        argStrings.add(getIdentifierString());
770        if (isSensitive())
771        {
772          argStrings.add("***REDACTED***");
773        }
774        else
775        {
776          argStrings.add(s);
777        }
778      }
779    }
780  }
781
782
783
784  /**
785   * {@inheritDoc}
786   */
787  @Override()
788  public void toString(final StringBuilder buffer)
789  {
790    buffer.append("StringArgument(");
791    appendBasicToStringInfo(buffer);
792
793    if ((allowedValues != null) && (! allowedValues.isEmpty()))
794    {
795      buffer.append(", allowedValues={");
796      final Iterator<String> iterator = allowedValues.iterator();
797      while (iterator.hasNext())
798      {
799        buffer.append('\'');
800        buffer.append(iterator.next());
801        buffer.append('\'');
802
803        if (iterator.hasNext())
804        {
805          buffer.append(", ");
806        }
807      }
808      buffer.append('}');
809    }
810
811    if (valueRegex != null)
812    {
813      buffer.append(", valueRegex='");
814      buffer.append(valueRegex.pattern());
815      buffer.append('\'');
816
817      if (valueRegexExplanation != null)
818      {
819        buffer.append(", valueRegexExplanation='");
820        buffer.append(valueRegexExplanation);
821        buffer.append('\'');
822      }
823    }
824
825    if ((defaultValues != null) && (! defaultValues.isEmpty()))
826    {
827      if (defaultValues.size() == 1)
828      {
829        buffer.append(", defaultValue='");
830        buffer.append(defaultValues.get(0));
831      }
832      else
833      {
834        buffer.append(", defaultValues={");
835
836        final Iterator<String> iterator = defaultValues.iterator();
837        while (iterator.hasNext())
838        {
839          buffer.append('\'');
840          buffer.append(iterator.next());
841          buffer.append('\'');
842
843          if (iterator.hasNext())
844          {
845            buffer.append(", ");
846          }
847        }
848
849        buffer.append('}');
850      }
851    }
852
853    buffer.append(')');
854  }
855}