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