001/*
002 * Copyright 2015-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-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.Collections;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.ldap.sdk.Control;
034import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
035import com.unboundid.ldap.sdk.controls.DontUseCopyRequestControl;
036import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
037import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
038import com.unboundid.ldap.sdk.controls.SubentriesRequestControl;
039import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
040import com.unboundid.ldap.sdk.experimental.
041            DraftBeheraLDAPPasswordPolicy10RequestControl;
042import com.unboundid.ldap.sdk.experimental.
043            DraftZeilengaLDAPNoOp12RequestControl;
044import com.unboundid.util.Base64;
045import com.unboundid.util.Debug;
046import com.unboundid.util.Mutable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050
051import static com.unboundid.util.args.ArgsMessages.*;
052
053
054
055/**
056 * This class defines an argument that is intended to hold information about one
057 * or more LDAP controls.  Values for this argument must be in one of the
058 * following formats:
059 * <UL>
060 *   <LI>
061 *     oid -- The numeric OID for the control.  The control will not be critical
062 *     and will not have a value.
063 *   </LI>
064 *   <LI>
065 *     oid:criticality -- The numeric OID followed by a colon and the
066 *     criticality.  The control will be critical if the criticality value is
067 *     any of the following:  {@code true}, {@code t}, {@code yes}, {@code y},
068 *     {@code on}, or {@code 1}.  The control will be non-critical if the
069 *     criticality value is any of the following:  {@code false}, {@code f},
070 *     {@code no}, {@code n}, {@code off}, or {@code 0}.  No other criticality
071 *     values will be accepted.
072 *   </LI>
073 *   <LI>
074 *     oid:criticality:value -- The numeric OID followed by a colon and the
075 *     criticality, then a colon and then a string that represents the value for
076 *     the control.
077 *   </LI>
078 *   <LI>
079 *     oid:criticality::base64value -- The numeric OID  followed by a colon and
080 *     the criticality, then two colons and then a string that represents the
081 *     base64-encoded value for the control.
082 *   </LI>
083 * </UL>
084 */
085@Mutable()
086@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
087public final class ControlArgument
088       extends Argument
089{
090  /**
091   * A map of human-readable names to the corresponding numeric OIDs.
092   */
093  private static final Map<String,String> OIDS_BY_NAME;
094  static
095  {
096    final HashMap<String,String> oidsByName =
097         new HashMap<String,String>(100);
098
099    // The authorization identity request control.
100    oidsByName.put("authzid",
101         AuthorizationIdentityRequestControl.
102              AUTHORIZATION_IDENTITY_REQUEST_OID);
103    oidsByName.put("authorizationidentity",
104         AuthorizationIdentityRequestControl.
105              AUTHORIZATION_IDENTITY_REQUEST_OID);
106    oidsByName.put("authorization-identity",
107         AuthorizationIdentityRequestControl.
108              AUTHORIZATION_IDENTITY_REQUEST_OID);
109
110    // The don't use copy request control.
111    oidsByName.put("nocopy",
112         DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
113    oidsByName.put("dontusecopy",
114         DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
115    oidsByName.put("no-copy",
116         DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
117    oidsByName.put("dont-use-copy",
118         DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
119
120    // The LDAP no-operation request control.
121    oidsByName.put("noop",
122         DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID);
123    oidsByName.put("nooperation",
124         DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID);
125    oidsByName.put("no-op",
126         DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID);
127    oidsByName.put("no-operation",
128         DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID);
129
130    // The LDAP subentries request control.
131    oidsByName.put("subentries",
132         SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
133    oidsByName.put("ldapsubentries",
134         SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
135    oidsByName.put("ldap-subentries",
136         SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
137
138    // The manage DSA IT request control.
139    oidsByName.put("managedsait",
140         ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
141    oidsByName.put("manage-dsa-it",
142         ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
143
144    // The permissive modify request control.
145    oidsByName.put("permissivemodify",
146         PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID);
147    oidsByName.put("permissive-modify",
148         PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID);
149
150    // The password policy request control.
151    oidsByName.put("pwpolicy",
152         DraftBeheraLDAPPasswordPolicy10RequestControl.
153              PASSWORD_POLICY_REQUEST_OID);
154    oidsByName.put("passwordpolicy",
155         DraftBeheraLDAPPasswordPolicy10RequestControl.
156              PASSWORD_POLICY_REQUEST_OID);
157    oidsByName.put("pw-policy",
158         DraftBeheraLDAPPasswordPolicy10RequestControl.
159              PASSWORD_POLICY_REQUEST_OID);
160    oidsByName.put("password-policy",
161         DraftBeheraLDAPPasswordPolicy10RequestControl.
162              PASSWORD_POLICY_REQUEST_OID);
163
164    // The subtree delete request control.
165    oidsByName.put("subtreedelete",
166         SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
167    oidsByName.put("treedelete",
168         SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
169    oidsByName.put("subtree-delete",
170         SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
171    oidsByName.put("tree-delete",
172         SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
173
174    // The account usable request control.
175    oidsByName.put("accountusable", "1.3.6.1.4.1.42.2.27.9.5.8");
176    oidsByName.put("account-usable", "1.3.6.1.4.1.42.2.27.9.5.8");
177
178    // The get backend set ID request control.
179    oidsByName.put("backendsetid", "1.3.6.1.4.1.30221.2.5.33");
180    oidsByName.put("getbackendsetid", "1.3.6.1.4.1.30221.2.5.33");
181    oidsByName.put("backendset-id", "1.3.6.1.4.1.30221.2.5.33");
182    oidsByName.put("get-backendset-id", "1.3.6.1.4.1.30221.2.5.33");
183
184    // The get effective rights request control.
185    oidsByName.put("effectiverights", "1.3.6.1.4.1.42.2.27.9.5.2");
186    oidsByName.put("geteffectiverights", "1.3.6.1.4.1.42.2.27.9.5.2");
187    oidsByName.put("effective-rights", "1.3.6.1.4.1.42.2.27.9.5.2");
188    oidsByName.put("get-effective-rights", "1.3.6.1.4.1.42.2.27.9.5.2");
189
190    // The get password policy state issues request control.
191    oidsByName.put("pwpolicystateissues", "1.3.6.1.4.1.30221.2.5.46");
192    oidsByName.put("getpwpolicystateissues", "1.3.6.1.4.1.30221.2.5.46");
193    oidsByName.put("passwordpolicystateissues", "1.3.6.1.4.1.30221.2.5.46");
194    oidsByName.put("getpasswordpolicystateissues", "1.3.6.1.4.1.30221.2.5.46");
195    oidsByName.put("pw-policy-state-issues", "1.3.6.1.4.1.30221.2.5.46");
196    oidsByName.put("get-pw-policy-state-issues", "1.3.6.1.4.1.30221.2.5.46");
197    oidsByName.put("password-policy-state-issues", "1.3.6.1.4.1.30221.2.5.46");
198    oidsByName.put("get-password-policy-state-issues",
199         "1.3.6.1.4.1.30221.2.5.46");
200
201    // The get server ID request control.
202    oidsByName.put("serverid", "1.3.6.1.4.1.30221.2.5.14");
203    oidsByName.put("getserverid", "1.3.6.1.4.1.30221.2.5.14");
204    oidsByName.put("server-id", "1.3.6.1.4.1.30221.2.5.14");
205    oidsByName.put("get-server-id", "1.3.6.1.4.1.30221.2.5.14");
206
207    // The get user resource limits request control.
208    oidsByName.put("userresourcelimits", "1.3.6.1.4.1.30221.2.5.25");
209    oidsByName.put("getuserresourcelimits", "1.3.6.1.4.1.30221.2.5.25");
210    oidsByName.put("user-resource-limits", "1.3.6.1.4.1.30221.2.5.25");
211    oidsByName.put("get-user-resource-limits", "1.3.6.1.4.1.30221.2.5.25");
212
213    // The hard delete request control.
214    oidsByName.put("harddelete", "1.3.6.1.4.1.30221.2.5.22");
215    oidsByName.put("hard-delete", "1.3.6.1.4.1.30221.2.5.22");
216
217    // The ignore NO-USER-MODIFICATION request control.
218    oidsByName.put("ignorenousermod", "1.3.6.1.4.1.30221.2.5.5");
219    oidsByName.put("ignorenousermodification", "1.3.6.1.4.1.30221.2.5.5");
220    oidsByName.put("ignore-no-user-mod", "1.3.6.1.4.1.30221.2.5.5");
221    oidsByName.put("ignore-no-user-modification", "1.3.6.1.4.1.30221.2.5.5");
222
223    // The purge retired password request control.
224    oidsByName.put("purgepassword", "1.3.6.1.4.1.30221.2.5.32");
225    oidsByName.put("purgeretiredpassword", "1.3.6.1.4.1.30221.2.5.32");
226    oidsByName.put("purge-password", "1.3.6.1.4.1.30221.2.5.32");
227    oidsByName.put("purge-retired-password", "1.3.6.1.4.1.30221.2.5.32");
228
229    // The real attributes only request control.
230    oidsByName.put("realattrsonly", "2.16.840.1.113730.3.4.17");
231    oidsByName.put("realattributesonly", "2.16.840.1.113730.3.4.17");
232    oidsByName.put("real-attrs-only", "2.16.840.1.113730.3.4.17");
233    oidsByName.put("real-attributes-only", "2.16.840.1.113730.3.4.17");
234
235    // The replication repair request control.
236    oidsByName.put("replrepair", "1.3.6.1.4.1.30221.1.5.2");
237    oidsByName.put("replicationrepair", "1.3.6.1.4.1.30221.1.5.2");
238    oidsByName.put("repl-repair", "1.3.6.1.4.1.30221.1.5.2");
239    oidsByName.put("replication-repair", "1.3.6.1.4.1.30221.1.5.2");
240
241    // The retain identity request control.
242    oidsByName.put("retainidentity", "1.3.6.1.4.1.30221.2.5.3");
243    oidsByName.put("retain-identity", "1.3.6.1.4.1.30221.2.5.3");
244
245    // The retire password request control.
246    oidsByName.put("retirepassword", "1.3.6.1.4.1.30221.2.5.31");
247    oidsByName.put("retire-password", "1.3.6.1.4.1.30221.2.5.31");
248
249    // The return conflict entries request control.
250    oidsByName.put("returnconflictentries", "1.3.6.1.4.1.30221.2.5.13");
251    oidsByName.put("return-conflict-entries", "1.3.6.1.4.1.30221.2.5.13");
252
253    // The soft delete request control.
254    oidsByName.put("softdelete", "1.3.6.1.4.1.30221.2.5.20");
255    oidsByName.put("soft-delete", "1.3.6.1.4.1.30221.2.5.20");
256
257    // The soft-deleted entry access request control.
258    oidsByName.put("softdeleteentryaccess", "1.3.6.1.4.1.30221.2.5.24");
259    oidsByName.put("softdeletedentryaccess", "1.3.6.1.4.1.30221.2.5.24");
260    oidsByName.put("soft-delete-entry-access", "1.3.6.1.4.1.30221.2.5.24");
261    oidsByName.put("soft-deleted-entry-access", "1.3.6.1.4.1.30221.2.5.24");
262
263    // The suppress referential integrity updates request control.
264    oidsByName.put("suppressreferentialintegrity", "1.3.6.1.4.1.30221.2.5.30");
265    oidsByName.put("suppressreferentialintegrityupdates",
266         "1.3.6.1.4.1.30221.2.5.30");
267    oidsByName.put("suppress-referential-integrity",
268         "1.3.6.1.4.1.30221.2.5.30");
269    oidsByName.put("suppress-referential-integrity-updates",
270         "1.3.6.1.4.1.30221.2.5.30");
271
272    // The undelete request control.
273    oidsByName.put("undelete", "1.3.6.1.4.1.30221.2.5.23");
274
275    // The virtual attributes only request control.
276    oidsByName.put("virtualattrsonly", "2.16.840.1.113730.3.4.19");
277    oidsByName.put("virtualattributesonly", "2.16.840.1.113730.3.4.19");
278    oidsByName.put("virtual-attrs-only", "2.16.840.1.113730.3.4.19");
279    oidsByName.put("virtual-attributes-only", "2.16.840.1.113730.3.4.19");
280
281    OIDS_BY_NAME = Collections.unmodifiableMap(oidsByName);
282  }
283
284
285
286  /**
287   * The serial version UID for this serializable class.
288   */
289  private static final long serialVersionUID = -1889200072476038957L;
290
291
292
293  // The argument value validators that have been registered for this argument.
294  private final List<ArgumentValueValidator> validators;
295
296  // The list of default values for this argument.
297  private final List<Control> defaultValues;
298
299  // The set of values assigned to this argument.
300  private final List<Control> values;
301
302
303
304  /**
305   * Creates a new control argument with the provided information.  It will not
306   * be required, will be allowed any number of times, will use a default
307   * placeholder, and will not have a default value.
308   *
309   * @param  shortIdentifier   The short identifier for this argument.  It may
310   *                           not be {@code null} if the long identifier is
311   *                           {@code null}.
312   * @param  longIdentifier    The long identifier for this argument.  It may
313   *                           not be {@code null} if the short identifier is
314   *                           {@code null}.
315   * @param  description       A human-readable description for this argument.
316   *                           It must not be {@code null}.
317   *
318   * @throws  ArgumentException  If there is a problem with the definition of
319   *                             this argument.
320   */
321  public ControlArgument(final Character shortIdentifier,
322                         final String longIdentifier, final String description)
323         throws ArgumentException
324  {
325    this(shortIdentifier, longIdentifier, false, 0, null, description);
326  }
327
328
329
330  /**
331   * Creates a new control argument with the provided information.  It will not
332   * have a default value.
333   *
334   * @param  shortIdentifier   The short identifier for this argument.  It may
335   *                           not be {@code null} if the long identifier is
336   *                           {@code null}.
337   * @param  longIdentifier    The long identifier for this argument.  It may
338   *                           not be {@code null} if the short identifier is
339   *                           {@code null}.
340   * @param  isRequired        Indicates whether this argument is required to
341   *                           be provided.
342   * @param  maxOccurrences    The maximum number of times this argument may be
343   *                           provided on the command line.  A value less than
344   *                           or equal to zero indicates that it may be present
345   *                           any number of times.
346   * @param  valuePlaceholder  A placeholder to display in usage information to
347   *                           indicate that a value must be provided.  It may
348   *                           be {@code null} to use a default placeholder that
349   *                           describes the expected syntax for values.
350   * @param  description       A human-readable description for this argument.
351   *                           It must not be {@code null}.
352   *
353   * @throws  ArgumentException  If there is a problem with the definition of
354   *                             this argument.
355   */
356  public ControlArgument(final Character shortIdentifier,
357                         final String longIdentifier, final boolean isRequired,
358                         final int maxOccurrences,
359                         final String valuePlaceholder,
360                         final String description)
361         throws ArgumentException
362  {
363    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
364         valuePlaceholder, description, (List<Control>) null);
365  }
366
367
368
369  /**
370   * Creates a new control argument with the provided information.
371   *
372   * @param  shortIdentifier   The short identifier for this argument.  It may
373   *                           not be {@code null} if the long identifier is
374   *                           {@code null}.
375   * @param  longIdentifier    The long identifier for this argument.  It may
376   *                           not be {@code null} if the short identifier is
377   *                           {@code null}.
378   * @param  isRequired        Indicates whether this argument is required to
379   *                           be provided.
380   * @param  maxOccurrences    The maximum number of times this argument may be
381   *                           provided on the command line.  A value less than
382   *                           or equal to zero indicates that it may be present
383   *                           any number of times.
384   * @param  valuePlaceholder  A placeholder to display in usage information to
385   *                           indicate that a value must be provided.  It may
386   *                           be {@code null} to use a default placeholder that
387   *                           describes the expected syntax for values.
388   * @param  description       A human-readable description for this argument.
389   *                           It must not be {@code null}.
390   * @param  defaultValue      The default value to use for this argument if no
391   *                           values were provided.  It may be {@code null} if
392   *                           there should be no default values.
393   *
394   * @throws  ArgumentException  If there is a problem with the definition of
395   *                             this argument.
396   */
397  public ControlArgument(final Character shortIdentifier,
398                         final String longIdentifier, final boolean isRequired,
399                         final int maxOccurrences,
400                         final String valuePlaceholder,
401                         final String description, final Control defaultValue)
402         throws ArgumentException
403  {
404    this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
405         valuePlaceholder, description,
406         ((defaultValue == null)
407              ? null :
408              Collections.singletonList(defaultValue)));
409  }
410
411
412
413  /**
414   * Creates a new control argument with the provided information.
415   *
416   * @param  shortIdentifier   The short identifier for this argument.  It may
417   *                           not be {@code null} if the long identifier is
418   *                           {@code null}.
419   * @param  longIdentifier    The long identifier for this argument.  It may
420   *                           not be {@code null} if the short identifier is
421   *                           {@code null}.
422   * @param  isRequired        Indicates whether this argument is required to
423   *                           be provided.
424   * @param  maxOccurrences    The maximum number of times this argument may be
425   *                           provided on the command line.  A value less than
426   *                           or equal to zero indicates that it may be present
427   *                           any number of times.
428   * @param  valuePlaceholder  A placeholder to display in usage information to
429   *                           indicate that a value must be provided.  It may
430   *                           be {@code null} to use a default placeholder that
431   *                           describes the expected syntax for values.
432   * @param  description       A human-readable description for this argument.
433   *                           It must not be {@code null}.
434   * @param  defaultValues     The set of default values to use for this
435   *                           argument if no values were provided.
436   *
437   * @throws  ArgumentException  If there is a problem with the definition of
438   *                             this argument.
439   */
440  public ControlArgument(final Character shortIdentifier,
441                         final String longIdentifier, final boolean isRequired,
442                         final int maxOccurrences,
443                         final String valuePlaceholder,
444                         final String description,
445                         final List<Control> defaultValues)
446         throws ArgumentException
447  {
448    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
449         (valuePlaceholder == null)
450              ? INFO_PLACEHOLDER_CONTROL.get()
451              : valuePlaceholder,
452         description);
453
454    if ((defaultValues == null) || defaultValues.isEmpty())
455    {
456      this.defaultValues = null;
457    }
458    else
459    {
460      this.defaultValues = Collections.unmodifiableList(defaultValues);
461    }
462
463    values = new ArrayList<Control>(5);
464    validators = new ArrayList<ArgumentValueValidator>(5);
465  }
466
467
468
469  /**
470   * Creates a new control argument that is a "clean" copy of the provided
471   * source argument.
472   *
473   * @param  source  The source argument to use for this argument.
474   */
475  private ControlArgument(final ControlArgument source)
476  {
477    super(source);
478
479    defaultValues = source.defaultValues;
480    validators    = new ArrayList<ArgumentValueValidator>(source.validators);
481    values        = new ArrayList<Control>(5);
482  }
483
484
485
486  /**
487   * Retrieves the list of default values for this argument, which will be used
488   * if no values were provided.
489   *
490   * @return   The list of default values for this argument, or {@code null} if
491   *           there are no default values.
492   */
493  public List<Control> getDefaultValues()
494  {
495    return defaultValues;
496  }
497
498
499
500  /**
501   * Updates this argument to ensure that the provided validator will be invoked
502   * for any values provided to this argument.  This validator will be invoked
503   * after all other validation has been performed for this argument.
504   *
505   * @param  validator  The argument value validator to be invoked.  It must not
506   *                    be {@code null}.
507   */
508  public void addValueValidator(final ArgumentValueValidator validator)
509  {
510    validators.add(validator);
511  }
512
513
514
515  /**
516   * {@inheritDoc}
517   */
518  @Override()
519  protected void addValue(final String valueString)
520            throws ArgumentException
521  {
522    String oid;
523    boolean isCritical = false;
524    ASN1OctetString value = null;
525
526    final int firstColonPos = valueString.indexOf(':');
527    if (firstColonPos < 0)
528    {
529      oid = valueString;
530    }
531    else
532    {
533      oid = valueString.substring(0, firstColonPos);
534
535      final String criticalityStr;
536      final int secondColonPos = valueString.indexOf(':', (firstColonPos+1));
537      if (secondColonPos < 0)
538      {
539        criticalityStr = valueString.substring(firstColonPos+1);
540      }
541      else
542      {
543        criticalityStr = valueString.substring(firstColonPos+1, secondColonPos);
544
545        final int doubleColonPos = valueString.indexOf("::");
546        if (doubleColonPos == secondColonPos)
547        {
548          try
549          {
550            value = new ASN1OctetString(
551                 Base64.decode(valueString.substring(doubleColonPos+2)));
552          }
553          catch (final Exception e)
554          {
555            Debug.debugException(e);
556            throw new ArgumentException(
557                 ERR_CONTROL_ARG_INVALID_BASE64_VALUE.get(valueString,
558                      getIdentifierString(),
559                      valueString.substring(doubleColonPos+2)),
560                 e);
561          }
562        }
563        else
564        {
565          value = new ASN1OctetString(valueString.substring(secondColonPos+1));
566        }
567      }
568
569      final String lowerCriticalityStr =
570           StaticUtils.toLowerCase(criticalityStr);
571      if (lowerCriticalityStr.equals("true") ||
572          lowerCriticalityStr.equals("t") ||
573          lowerCriticalityStr.equals("yes") ||
574          lowerCriticalityStr.equals("y") ||
575          lowerCriticalityStr.equals("on") ||
576          lowerCriticalityStr.equals("1"))
577      {
578        isCritical = true;
579      }
580      else if (lowerCriticalityStr.equals("false") ||
581               lowerCriticalityStr.equals("f") ||
582               lowerCriticalityStr.equals("no") ||
583               lowerCriticalityStr.equals("n") ||
584               lowerCriticalityStr.equals("off") ||
585               lowerCriticalityStr.equals("0"))
586      {
587        isCritical = false;
588      }
589      else
590      {
591        throw new ArgumentException(ERR_CONTROL_ARG_INVALID_CRITICALITY.get(
592             valueString, getIdentifierString(), criticalityStr));
593      }
594    }
595
596    if (! StaticUtils.isNumericOID(oid))
597    {
598      final String providedOID = oid;
599      oid = OIDS_BY_NAME.get(StaticUtils.toLowerCase(providedOID));
600      if (oid == null)
601      {
602        throw new ArgumentException(ERR_CONTROL_ARG_INVALID_OID.get(
603             valueString, getIdentifierString(), providedOID));
604      }
605    }
606
607    if (values.size() >= getMaxOccurrences())
608    {
609      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
610                                       getIdentifierString()));
611    }
612
613    for (final ArgumentValueValidator v : validators)
614    {
615      v.validateArgumentValue(this, valueString);
616    }
617
618    values.add(new Control(oid, isCritical, value));
619  }
620
621
622
623  /**
624   * Retrieves the value for this argument, or the default value if none was
625   * provided.  If there are multiple values, then the first will be returned.
626   *
627   * @return  The value for this argument, or the default value if none was
628   *          provided, or {@code null} if there is no value and no default
629   *          value.
630   */
631  public Control getValue()
632  {
633    if (values.isEmpty())
634    {
635      if ((defaultValues == null) || defaultValues.isEmpty())
636      {
637        return null;
638      }
639      else
640      {
641        return defaultValues.get(0);
642      }
643    }
644    else
645    {
646      return values.get(0);
647    }
648  }
649
650
651
652  /**
653   * Retrieves the set of values for this argument, or the default values if
654   * none were provided.
655   *
656   * @return  The set of values for this argument, or the default values if none
657   *          were provided.
658   */
659  public List<Control> getValues()
660  {
661    if (values.isEmpty() && (defaultValues != null))
662    {
663      return defaultValues;
664    }
665
666    return Collections.unmodifiableList(values);
667  }
668
669
670
671  /**
672   * {@inheritDoc}
673   */
674  @Override()
675  public List<String> getValueStringRepresentations(final boolean useDefault)
676  {
677    final List<Control> controls;
678    if (values.isEmpty())
679    {
680      if (useDefault)
681      {
682        controls = defaultValues;
683      }
684      else
685      {
686        return Collections.emptyList();
687      }
688    }
689    else
690    {
691      controls = values;
692    }
693
694    if ((controls == null) || controls.isEmpty())
695    {
696      return Collections.emptyList();
697    }
698
699    final StringBuilder buffer = new StringBuilder();
700    final ArrayList<String> valueStrings =
701         new ArrayList<String>(controls.size());
702    for (final Control c : controls)
703    {
704      buffer.setLength(0);
705      buffer.append(c.getOID());
706      buffer.append(':');
707      buffer.append(c.isCritical());
708
709      if (c.hasValue())
710      {
711        final byte[] valueBytes = c.getValue().getValue();
712        if (StaticUtils.isPrintableString(valueBytes))
713        {
714          buffer.append(':');
715          buffer.append(c.getValue().stringValue());
716        }
717        else
718        {
719          buffer.append("::");
720          Base64.encode(valueBytes, buffer);
721        }
722      }
723
724      valueStrings.add(buffer.toString());
725    }
726
727    return Collections.unmodifiableList(valueStrings);
728  }
729
730
731
732  /**
733   * {@inheritDoc}
734   */
735  @Override()
736  protected boolean hasDefaultValue()
737  {
738    return ((defaultValues != null) && (! defaultValues.isEmpty()));
739  }
740
741
742
743  /**
744   * {@inheritDoc}
745   */
746  @Override()
747  public String getDataTypeName()
748  {
749    return INFO_CONTROL_TYPE_NAME.get();
750  }
751
752
753
754  /**
755   * {@inheritDoc}
756   */
757  @Override()
758  public String getValueConstraints()
759  {
760    return INFO_CONTROL_CONSTRAINTS.get();
761  }
762
763
764
765  /**
766   * {@inheritDoc}
767   */
768  @Override()
769  protected void reset()
770  {
771    super.reset();
772    values.clear();
773  }
774
775
776
777  /**
778   * {@inheritDoc}
779   */
780  @Override()
781  public ControlArgument getCleanCopy()
782  {
783    return new ControlArgument(this);
784  }
785
786
787
788  /**
789   * {@inheritDoc}
790   */
791  @Override()
792  protected void addToCommandLine(final List<String> argStrings)
793  {
794    if (values != null)
795    {
796      final StringBuilder buffer = new StringBuilder();
797      for (final Control c : values)
798      {
799        argStrings.add(getIdentifierString());
800
801        if (isSensitive())
802        {
803          argStrings.add("***REDACTED***");
804          continue;
805        }
806
807        buffer.setLength(0);
808        buffer.append(c.getOID());
809        buffer.append(':');
810        buffer.append(c.isCritical());
811
812        if (c.hasValue())
813        {
814          final byte[] valueBytes = c.getValue().getValue();
815          if (StaticUtils.isPrintableString(valueBytes))
816          {
817            buffer.append(':');
818            buffer.append(c.getValue().stringValue());
819          }
820          else
821          {
822            buffer.append("::");
823            Base64.encode(valueBytes, buffer);
824          }
825        }
826
827        argStrings.add(buffer.toString());
828      }
829    }
830  }
831
832
833
834  /**
835   * {@inheritDoc}
836   */
837  @Override()
838  public void toString(final StringBuilder buffer)
839  {
840    buffer.append("ControlArgument(");
841    appendBasicToStringInfo(buffer);
842
843    if ((defaultValues != null) && (! defaultValues.isEmpty()))
844    {
845      if (defaultValues.size() == 1)
846      {
847        buffer.append(", defaultValue='");
848        buffer.append(defaultValues.get(0).toString());
849      }
850      else
851      {
852        buffer.append(", defaultValues={");
853
854        final Iterator<Control> iterator = defaultValues.iterator();
855        while (iterator.hasNext())
856        {
857          buffer.append('\'');
858          buffer.append(iterator.next().toString());
859          buffer.append('\'');
860
861          if (iterator.hasNext())
862          {
863            buffer.append(", ");
864          }
865        }
866
867        buffer.append('}');
868      }
869    }
870
871    buffer.append(')');
872  }
873}