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