001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.tools.makeldif;
028import org.forgerock.i18n.LocalizableMessage;
029
030
031
032import java.text.DecimalFormat;
033import java.util.List;
034import java.util.Random;
035
036import org.opends.server.types.InitializationException;
037
038import static org.opends.messages.ToolMessages.*;
039
040import static org.opends.server.util.StaticUtils.*;
041
042
043
044/**
045 * This class defines a tag that may be used to generate random values.  It has
046 * a number of subtypes based on the type of information that should be
047 * generated, including:
048 * <UL>
049 *   <LI>alpha:length</LI>
050 *   <LI>alpha:minlength:maxlength</LI>
051 *   <LI>numeric:length</LI>
052 *   <LI>numeric:minvalue:maxvalue</LI>
053 *   <LI>numeric:minvalue:maxvalue:format</LI>
054 *   <LI>alphanumeric:length</LI>
055 *   <LI>alphanumeric:minlength:maxlength</LI>
056 *   <LI>chars:characters:length</LI>
057 *   <LI>chars:characters:minlength:maxlength</LI>
058 *   <LI>hex:length</LI>
059 *   <LI>hex:minlength:maxlength</LI>
060 *   <LI>base64:length</LI>
061 *   <LI>base64:minlength:maxlength</LI>
062 *   <LI>month</LI>
063 *   <LI>month:maxlength</LI>
064 *   <LI>telephone</LI>
065 * </UL>
066 */
067public class RandomTag
068       extends Tag
069{
070  /**
071   * The value that indicates that the value is to be generated from a fixed
072   * number of characters from a given character set.
073   */
074  public static final int RANDOM_TYPE_CHARS_FIXED = 1;
075
076
077
078  /**
079   * The value that indicates that the value is to be generated from a variable
080   * number of characters from a given character set.
081   */
082  public static final int RANDOM_TYPE_CHARS_VARIABLE = 2;
083
084
085
086  /**
087   * The value that indicates that the value should be a random number.
088   */
089  public static final int RANDOM_TYPE_NUMERIC = 3;
090
091
092
093  /**
094   * The value that indicates that the value should be a random month.
095   */
096  public static final int RANDOM_TYPE_MONTH = 4;
097
098
099
100  /**
101   * The value that indicates that the value should be a telephone number.
102   */
103  public static final int RANDOM_TYPE_TELEPHONE = 5;
104
105
106
107  /**
108   * The character set that will be used for alphabetic characters.
109   */
110  public static final char[] ALPHA_CHARS =
111       "abcdefghijklmnopqrstuvwxyz".toCharArray();
112
113
114
115  /**
116   * The character set that will be used for numeric characters.
117   */
118  public static final char[] NUMERIC_CHARS = "01234567890".toCharArray();
119
120
121
122  /**
123   * The character set that will be used for alphanumeric characters.
124   */
125  public static final char[] ALPHANUMERIC_CHARS =
126       "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
127
128
129
130  /**
131   * The character set that will be used for hexadecimal characters.
132   */
133  public static final char[] HEX_CHARS = "01234567890abcdef".toCharArray();
134
135
136
137  /**
138   * The character set that will be used for base64 characters.
139   */
140  public static final char[] BASE64_CHARS =
141       ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
142        "01234567890+/").toCharArray();
143
144
145
146  /**
147   * The set of month names that will be used.
148   */
149  public static final String[] MONTHS =
150  {
151    "January",
152    "February",
153    "March",
154    "April",
155    "May",
156    "June",
157    "July",
158    "August",
159    "September",
160    "October",
161    "November",
162    "December"
163  };
164
165
166
167  /** The character set that should be used to generate the values. */
168  private char[] characterSet;
169
170  /** The decimal format used to format numeric values. */
171  private DecimalFormat decimalFormat;
172
173  /**
174   * The number of characters between the minimum and maximum length
175   * (inclusive).
176   */
177  private int lengthRange;
178
179  /** The maximum number of characters to include in the value. */
180  private int maxLength;
181
182  /** The minimum number of characters to include in the value. */
183  private int minLength;
184
185  /** The type of random value that should be generated. */
186  private int randomType;
187
188  /** The maximum numeric value that should be generated. */
189  private long maxValue;
190
191  /** The minimum numeric value that should be generated. */
192  private long minValue;
193
194  /** The number of values between the minimum and maximum value (inclusive). */
195  private long valueRange;
196
197  /** The random number generator for this tag. */
198  private Random random;
199
200
201
202  /**
203   * Creates a new instance of this random tag.
204   */
205  public RandomTag()
206  {
207    characterSet  = null;
208    decimalFormat = null;
209    lengthRange   = 1;
210    maxLength     = 0;
211    minLength     = 0;
212    randomType    = 0;
213    maxValue      = 0L;
214    minValue      = 0L;
215    valueRange    = 1L;
216  }
217
218
219
220  /**
221   * Retrieves the name for this tag.
222   *
223   * @return  The name for this tag.
224   */
225  public String getName()
226  {
227    return "Random";
228  }
229
230
231
232  /**
233   * Indicates whether this tag is allowed for use in the extra lines for
234   * branches.
235   *
236   * @return  <CODE>true</CODE> if this tag may be used in branch definitions,
237   *          or <CODE>false</CODE> if not.
238   */
239  public boolean allowedInBranch()
240  {
241    return true;
242  }
243
244
245
246  /**
247   * Performs any initialization for this tag that may be needed while parsing
248   * a branch definition.
249   *
250   * @param  templateFile  The template file in which this tag is used.
251   * @param  branch        The branch in which this tag is used.
252   * @param  arguments     The set of arguments provided for this tag.
253   * @param  lineNumber    The line number on which this tag appears in the
254   *                       template file.
255   * @param  warnings      A list into which any appropriate warning messages
256   *                       may be placed.
257   *
258   * @throws  InitializationException  If a problem occurs while initializing
259   *                                   this tag.
260   */
261  public void initializeForBranch(TemplateFile templateFile, Branch branch,
262                                  String[] arguments, int lineNumber,
263                                  List<LocalizableMessage> warnings)
264         throws InitializationException
265  {
266    initializeInternal(templateFile, arguments, lineNumber, warnings);
267  }
268
269
270
271  /**
272   * Performs any initialization for this tag that may be needed while parsing
273   * a template definition.
274   *
275   * @param  templateFile  The template file in which this tag is used.
276   * @param  template      The template in which this tag is used.
277   * @param  arguments     The set of arguments provided for this tag.
278   * @param  lineNumber    The line number on which this tag appears in the
279   *                       template file.
280   * @param  warnings      A list into which any appropriate warning messages
281   *                       may be placed.
282   *
283   * @throws  InitializationException  If a problem occurs while initializing
284   *                                   this tag.
285   */
286  public void initializeForTemplate(TemplateFile templateFile,
287                                    Template template, String[] arguments,
288                                    int lineNumber, List<LocalizableMessage> warnings)
289         throws InitializationException
290  {
291    initializeInternal(templateFile, arguments, lineNumber, warnings);
292  }
293
294
295
296  /**
297   * Performs any initialization for this tag that may be needed while parsing
298   * either a branch or template definition.
299   *
300   * @param  templateFile  The template file in which this tag is used.
301   * @param  arguments     The set of arguments provided for this tag.
302   * @param  lineNumber    The line number on which this tag appears in the
303   *                       template file.
304   * @param  warnings      A list into which any appropriate warning messages
305   *                       may be placed.
306   *
307   * @throws  InitializationException  If a problem occurs while initializing
308   *                                   this tag.
309   */
310  private void initializeInternal(TemplateFile templateFile, String[] arguments,
311                                  int lineNumber, List<LocalizableMessage> warnings)
312          throws InitializationException
313  {
314    random = templateFile.getRandom();
315
316    // There must be at least one argument, to specify the type of random value
317    // to generate.
318    if (arguments == null || arguments.length == 0)
319    {
320      LocalizableMessage message =
321          ERR_MAKELDIF_TAG_NO_RANDOM_TYPE_ARGUMENT.get(lineNumber);
322      throw new InitializationException(message);
323    }
324
325    int numArgs = arguments.length;
326    String randomTypeString = toLowerCase(arguments[0]);
327
328    if (randomTypeString.equals("alpha"))
329    {
330      characterSet = ALPHA_CHARS;
331      decodeLength(arguments, 1, lineNumber, warnings);
332    }
333    else if (randomTypeString.equals("numeric"))
334    {
335      if (numArgs == 2)
336      {
337        randomType   = RANDOM_TYPE_CHARS_FIXED;
338        characterSet = NUMERIC_CHARS;
339
340        try
341        {
342          minLength = Integer.parseInt(arguments[1]);
343
344          if (minLength < 0)
345          {
346            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
347                minLength, 0, getName(), lineNumber);
348            throw new InitializationException(message);
349          }
350          else if (minLength == 0)
351          {
352            LocalizableMessage message = WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(
353                    lineNumber);
354            warnings.add(message);
355          }
356        }
357        catch (NumberFormatException nfe)
358        {
359          LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
360              arguments[1], getName(), lineNumber);
361          throw new InitializationException(message, nfe);
362        }
363      }
364      else if (numArgs == 3 || numArgs == 4)
365      {
366        randomType = RANDOM_TYPE_NUMERIC;
367
368        if (numArgs == 4)
369        {
370          try
371          {
372            decimalFormat = new DecimalFormat(arguments[3]);
373          }
374          catch (Exception e)
375          {
376            LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_FORMAT_STRING.get(
377                arguments[3], getName(), lineNumber);
378            throw new InitializationException(message, e);
379          }
380        }
381        else
382        {
383          decimalFormat = null;
384        }
385
386        try
387        {
388          minValue = Long.parseLong(arguments[1]);
389        }
390        catch (NumberFormatException nfe)
391        {
392          LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
393              arguments[1], getName(), lineNumber);
394          throw new InitializationException(message, nfe);
395        }
396
397        try
398        {
399          maxValue = Long.parseLong(arguments[2]);
400          if (maxValue < minValue)
401          {
402            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
403                maxValue, minValue, getName(), lineNumber);
404            throw new InitializationException(message);
405          }
406
407          valueRange = maxValue - minValue + 1;
408        }
409        catch (NumberFormatException nfe)
410        {
411          LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
412              arguments[2], getName(), lineNumber);
413          throw new InitializationException(message, nfe);
414        }
415      }
416      else
417      {
418        LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
419            getName(), lineNumber, 2, 4, numArgs);
420        throw new InitializationException(message);
421      }
422    }
423    else if (randomTypeString.equals("alphanumeric"))
424    {
425      characterSet = ALPHANUMERIC_CHARS;
426      decodeLength(arguments, 1, lineNumber, warnings);
427    }
428    else if (randomTypeString.equals("chars"))
429    {
430      if (numArgs < 3 || numArgs > 4)
431      {
432        LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
433            getName(), lineNumber, 3, 4, numArgs);
434        throw new InitializationException(message);
435      }
436
437      characterSet = arguments[1].toCharArray();
438      decodeLength(arguments, 2, lineNumber, warnings);
439    }
440    else if (randomTypeString.equals("hex"))
441    {
442      characterSet = HEX_CHARS;
443      decodeLength(arguments, 1, lineNumber, warnings);
444    }
445    else if (randomTypeString.equals("base64"))
446    {
447      characterSet = BASE64_CHARS;
448      decodeLength(arguments, 1, lineNumber, warnings);
449    }
450    else if (randomTypeString.equals("month"))
451    {
452      randomType = RANDOM_TYPE_MONTH;
453
454      if (numArgs == 1)
455      {
456        maxLength = 0;
457      }
458      else if (numArgs == 2)
459      {
460        try
461        {
462          maxLength = Integer.parseInt(arguments[1]);
463          if (maxLength <= 0)
464          {
465            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
466                maxLength, 1, getName(), lineNumber);
467            throw new InitializationException(message);
468          }
469        }
470        catch (NumberFormatException nfe)
471        {
472          LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
473              arguments[1], getName(), lineNumber);
474          throw new InitializationException(message, nfe);
475        }
476      }
477      else
478      {
479        LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
480            getName(), lineNumber, 1, 2, numArgs);
481        throw new InitializationException(message);
482      }
483    }
484    else if (randomTypeString.equals("telephone"))
485    {
486      randomType    = RANDOM_TYPE_TELEPHONE;
487    }
488    else
489    {
490      LocalizableMessage message = ERR_MAKELDIF_TAG_UNKNOWN_RANDOM_TYPE.get(
491          lineNumber, randomTypeString);
492      throw new InitializationException(message);
493    }
494  }
495
496
497
498  /**
499   * Decodes the information in the provided argument list as either a single
500   * integer specifying the number of characters, or two integers specifying the
501   * minimum and maximum number of characters.
502   *
503   * @param  arguments   The set of arguments to be processed.
504   * @param  startPos    The position at which the first legth value should
505   *                     appear in the argument list.
506   * @param  lineNumber  The line number on which the tag appears in the
507   *                     template file.
508   * @param  warnings    A list into which any appropriate warning messages may
509   *                     be placed.
510   */
511  private void decodeLength(String[] arguments, int startPos, int lineNumber,
512                            List<LocalizableMessage> warnings)
513          throws InitializationException
514  {
515    int numArgs = arguments.length - startPos + 1;
516
517    if (numArgs == 2)
518    {
519      // There is a fixed number of characters in the value.
520      randomType = RANDOM_TYPE_CHARS_FIXED;
521
522      try
523      {
524        minLength = Integer.parseInt(arguments[startPos]);
525
526        if (minLength < 0)
527        {
528          LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
529              minLength, 0, getName(), lineNumber);
530          throw new InitializationException(message);
531        }
532        else if (minLength == 0)
533        {
534          LocalizableMessage message = WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(
535                  lineNumber);
536          warnings.add(message);
537        }
538      }
539      catch (NumberFormatException nfe)
540      {
541        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
542            arguments[startPos], getName(), lineNumber);
543        throw new InitializationException(message, nfe);
544      }
545    }
546    else if (numArgs == 3)
547    {
548      // There are minimum and maximum lengths.
549      randomType = RANDOM_TYPE_CHARS_VARIABLE;
550
551      try
552      {
553        minLength = Integer.parseInt(arguments[startPos]);
554
555        if (minLength < 0)
556        {
557          LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
558              minLength, 0, getName(), lineNumber);
559          throw new InitializationException(message);
560        }
561      }
562      catch (NumberFormatException nfe)
563      {
564        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
565            arguments[startPos], getName(), lineNumber);
566        throw new InitializationException(message, nfe);
567      }
568
569      try
570      {
571        maxLength   = Integer.parseInt(arguments[startPos+1]);
572        lengthRange = maxLength - minLength + 1;
573
574        if (maxLength < minLength)
575        {
576          LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(
577              maxLength, minLength, getName(), lineNumber);
578          throw new InitializationException(message);
579        }
580        else if (maxLength == 0)
581        {
582          LocalizableMessage message =
583                  WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(lineNumber);
584          warnings.add(message);
585        }
586      }
587      catch (NumberFormatException nfe)
588      {
589        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(
590            arguments[startPos+1], getName(), lineNumber);
591        throw new InitializationException(message, nfe);
592      }
593    }
594    else
595    {
596      LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
597          getName(), lineNumber, startPos+1, startPos+2, numArgs);
598      throw new InitializationException(message);
599    }
600  }
601
602
603
604  /**
605   * Generates the content for this tag by appending it to the provided tag.
606   *
607   * @param  templateEntry  The entry for which this tag is being generated.
608   * @param  templateValue  The template value to which the generated content
609   *                        should be appended.
610   *
611   * @return  The result of generating content for this tag.
612   */
613  public TagResult generateValue(TemplateEntry templateEntry,
614                                 TemplateValue templateValue)
615  {
616    StringBuilder buffer = templateValue.getValue();
617
618    switch (randomType)
619    {
620      case RANDOM_TYPE_CHARS_FIXED:
621        for (int i=0; i < minLength; i++)
622        {
623          buffer.append(characterSet[random.nextInt(characterSet.length)]);
624        }
625        break;
626
627      case RANDOM_TYPE_CHARS_VARIABLE:
628        int numChars = random.nextInt(lengthRange) + minLength;
629        for (int i=0; i < numChars; i++)
630        {
631          buffer.append(characterSet[random.nextInt(characterSet.length)]);
632        }
633        break;
634
635      case RANDOM_TYPE_NUMERIC:
636        long randomValue =
637          ((random.nextLong() & 0x7FFFFFFFFFFFFFFFL) % valueRange) + minValue;
638        if (decimalFormat == null)
639        {
640          buffer.append(randomValue);
641        }
642        else
643        {
644          buffer.append(decimalFormat.format(randomValue));
645        }
646        break;
647
648      case RANDOM_TYPE_MONTH:
649        String month = MONTHS[random.nextInt(MONTHS.length)];
650        if (maxLength == 0 || month.length() <= maxLength)
651        {
652          buffer.append(month);
653        }
654        else
655        {
656          buffer.append(month, 0, maxLength);
657        }
658        break;
659
660      case RANDOM_TYPE_TELEPHONE:
661        buffer.append("+1 ");
662        for (int i=0; i < 3; i++)
663        {
664          buffer.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
665        }
666        buffer.append(' ');
667        for (int i=0; i < 3; i++)
668        {
669          buffer.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
670        }
671        buffer.append(' ');
672        for (int i=0; i < 4; i++)
673        {
674          buffer.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
675        }
676        break;
677    }
678
679    return TagResult.SUCCESS_RESULT;
680  }
681}
682