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