code: /**
   * Copyright (c) 2016 YCSB contributors. All rights reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you
   * may not use this file except in compliance with the License. You
   * may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   * implied. See the License for the specific language governing
   * permissions and limitations under the License. See accompanying
   * LICENSE file.
   */
  package com.yahoo.ycsb.generator;
  
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  import java.util.NoSuchElementException;
  import java.util.Set;
  
  /**
   * A generator that produces strings of {@link #length} using a set of code points
   * from {@link #characterSet}. Each time {@link #nextValue()} is executed, the string
   * is incremented by one character. Eventually the string may rollover to the beginning
   * and the user may choose to have the generator throw a NoSuchElementException at that 
   * point or continue incrementing. (By default the generator will continue incrementing).
   * <p>
   * For example, if we set a length of 2 characters and the character set includes
   * [A, B] then the generator output will be:
   * <ul>
   * <li>AA</li>
   * <li>AB</li>
   * <li>BA</li>
   * <li>BB</li>
   * <li>AA <-- rolled over</li>
   * </ul>
   * <p>
   * This class includes some default character sets to choose from including ASCII
   * and plane 0 UTF. 
   */
  public class IncrementingPrintableStringGenerator extends Generator<String> {
  
    /** Default string length for the generator. */
    public static final int DEFAULTSTRINGLENGTH = 8;
    
    /**
     * Set of all character types that include every symbol other than non-printable
     * control characters.
     */
    public static final Set<Integer> CHAR_TYPES_ALL_BUT_CONTROL;
    static {
      CHAR_TYPES_ALL_BUT_CONTROL = new HashSet<Integer>(24);
      // numbers
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.DECIMAL_DIGIT_NUMBER);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.LETTER_NUMBER);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.OTHER_NUMBER);
      
      // letters
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.UPPERCASE_LETTER);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.LOWERCASE_LETTER);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.TITLECASE_LETTER);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.OTHER_LETTER);
      
      // marks
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.COMBINING_SPACING_MARK);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.NON_SPACING_MARK);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.ENCLOSING_MARK);
      
      // punctuation
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.CONNECTOR_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.DASH_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.START_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.END_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.INITIAL_QUOTE_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.FINAL_QUOTE_PUNCTUATION);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.OTHER_PUNCTUATION);
      
      // symbols
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.MATH_SYMBOL);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.CURRENCY_SYMBOL);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.MODIFIER_SYMBOL);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.OTHER_SYMBOL);
      
      // separators
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.SPACE_SEPARATOR);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.LINE_SEPARATOR);
      CHAR_TYPES_ALL_BUT_CONTROL.add((int)Character.PARAGRAPH_SEPARATOR);
    }
    
    /**
     * Set of character types including only decimals, upper and lower case letters.
     */
    public static final Set<Integer> CHAR_TYPES_BASIC_ALPHA;
    static {
      CHAR_TYPES_BASIC_ALPHA = new HashSet<Integer>(2);
      CHAR_TYPES_BASIC_ALPHA.add((int)Character.UPPERCASE_LETTER);
      CHAR_TYPES_BASIC_ALPHA.add((int)Character.LOWERCASE_LETTER);
    }
    
    /**
     * Set of character types including only  decimals, upper and lower case letters.
     */
    public static final Set<Integer> CHAR_TYPES_BASIC_ALPHANUMERICS;
    static {
      CHAR_TYPES_BASIC_ALPHANUMERICS = new HashSet<Integer>(3);
      CHAR_TYPES_BASIC_ALPHANUMERICS.add((int)Character.DECIMAL_DIGIT_NUMBER);
      CHAR_TYPES_BASIC_ALPHANUMERICS.add((int)Character.UPPERCASE_LETTER);
      CHAR_TYPES_BASIC_ALPHANUMERICS.add((int)Character.LOWERCASE_LETTER);
    }
    
    /**
     * Set of character types including only decimals, letter numbers, 
     * other numbers, upper, lower, title case as well as letter modifiers 
     * and other letters.
     */
    public static final Set<Integer> CHAR_TYPE_EXTENDED_ALPHANUMERICS;
    static {
      CHAR_TYPE_EXTENDED_ALPHANUMERICS = new HashSet<Integer>(8);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.DECIMAL_DIGIT_NUMBER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.LETTER_NUMBER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.OTHER_NUMBER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.UPPERCASE_LETTER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.LOWERCASE_LETTER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.TITLECASE_LETTER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.MODIFIER_LETTER);
      CHAR_TYPE_EXTENDED_ALPHANUMERICS.add((int)Character.OTHER_LETTER);
    }
    
    /** The character set to iterate over. */
    private final int[] characterSet;
    
    /** An array indices matching a position in the output string. */
    private int[] indices; 
    
    /** The length of the output string in characters. */
    private final int length;
    
    /** The last value returned by the generator. Should be null if {@link #nextValue()} 
     * has not been called.*/
    private String lastValue;
    
    /** Whether or not to throw an exception when the string rolls over. */
    private boolean throwExceptionOnRollover;
    
    /** Whether or not the generator has rolled over. */
    private boolean hasRolledOver;
    
    /**
     * Generates strings of 8 characters using only the upper and lower case alphabetical
     * characters from the ASCII set. 
     */
    public IncrementingPrintableStringGenerator() {
      this(DEFAULTSTRINGLENGTH, printableBasicAlphaASCIISet());
    }
    
    /**
     * Generates strings of {@link #length} characters using only the upper and lower 
     * case alphabetical characters from the ASCII set. 
     * @param length The length of string to return from the generator.
     * @throws IllegalArgumentException if the length is less than one.
     */
    public IncrementingPrintableStringGenerator(final int length) {
      this(length, printableBasicAlphaASCIISet());
    }
    
    /**
     * Generates strings of {@link #length} characters using the code points in
     * {@link #characterSet}.
     * @param length The length of string to return from the generator.
     * @param characterSet A set of code points to choose from. Code points in the 
     * set can be in any order, not necessarily lexical.
     * @throws IllegalArgumentException if the length is less than one or the character
     * set has fewer than one code points.
     */
    public IncrementingPrintableStringGenerator(final int length, final int[] characterSet) {
      if (length < 1) {
        throw new IllegalArgumentException("Length must be greater than or equal to 1");
      }
      if (characterSet == null || characterSet.length < 1) {
        throw new IllegalArgumentException("Character set must have at least one character");
      }
      this.length = length;
      this.characterSet = characterSet;
      indices = new int[length];
    }
    
    @Override
    public String nextValue() {
      if (hasRolledOver && throwExceptionOnRollover) {
        throw new NoSuchElementException("The generator has rolled over to the beginning");
      }
      
      final StringBuilder buffer = new StringBuilder(length);
      for (int i = 0; i < length; i++) {
        buffer.append(Character.toChars(characterSet[indices[i]]));
      }
      
      // increment the indices;
      for (int i = length - 1; i >= 0; --i) {
        if (indices[i] >= characterSet.length - 1) {
          indices[i] = 0;
          if (i == 0 || characterSet.length == 1 && lastValue != null) {
            hasRolledOver = true;
          }
        } else {
          ++indices[i];
          break;
        }
      }
      
      lastValue = buffer.toString();
      return lastValue;
    }
  
    @Override
    public String lastValue() {
      return lastValue;
    }
  
    /** @param exceptionOnRollover Whether or not to throw an exception on rollover. */
    public void setThrowExceptionOnRollover(final boolean exceptionOnRollover) {
      this.throwExceptionOnRollover = exceptionOnRollover;
    }
    
    /** @return Whether or not to throw an exception on rollover. */
    public boolean getThrowExceptionOnRollover() {
      return throwExceptionOnRollover;
    }
    
    /**
     * Returns an array of printable code points with only the upper and lower
     * case alphabetical characters from the basic ASCII set.
     * @return An array of code points
     */
    public static int[] printableBasicAlphaASCIISet() {
      final List<Integer> validCharacters = 
          generatePrintableCharacterSet(0, 127, null, false, CHAR_TYPES_BASIC_ALPHA);
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Returns an array of printable code points with the upper and lower case 
     * alphabetical characters as well as the numeric values from the basic 
     * ASCII set.
     * @return An array of code points
     */
    public static int[] printableBasicAlphaNumericASCIISet() {
      final List<Integer> validCharacters = 
          generatePrintableCharacterSet(0, 127, null, false, CHAR_TYPES_BASIC_ALPHANUMERICS);
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Returns an array of printable code points with the entire basic ASCII table,
     * including spaces. Excludes new lines.
     * @return An array of code points
     */
    public static int[] fullPrintableBasicASCIISet() {
      final List<Integer> validCharacters = 
          generatePrintableCharacterSet(32, 127, null, false, null);
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Returns an array of printable code points with the entire basic ASCII table,
     * including spaces and new lines.
     * @return An array of code points
     */
    public static int[] fullPrintableBasicASCIISetWithNewlines() {
      final List<Integer> validCharacters =new ArrayList<Integer>();
      validCharacters.add(10); // newline
      validCharacters.addAll(generatePrintableCharacterSet(32, 127, null, false, null));
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Returns an array of printable code points the first plane of Unicode characters
     * including only the alpha-numeric values.
     * @return An array of code points
     */
    public static int[] printableAlphaNumericPlaneZeroSet() {
      final List<Integer> validCharacters = 
          generatePrintableCharacterSet(0, 65535, null, false, CHAR_TYPES_BASIC_ALPHANUMERICS);
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Returns an array of printable code points the first plane of Unicode characters
     * including all printable characters.
     * @return An array of code points
     */
    public static int[] fullPrintablePlaneZeroSet() {
      final List<Integer> validCharacters = 
          generatePrintableCharacterSet(0, 65535, null, false, CHAR_TYPES_ALL_BUT_CONTROL);
      final int[] characterSet = new int[validCharacters.size()];
      for (int i = 0; i < validCharacters.size(); i++) {
        characterSet[i] = validCharacters.get(i);
      }
      return characterSet;
    }
    
    /**
     * Generates a list of code points based on a range and filters.
     * These can be used for generating strings with various ASCII and/or
     * Unicode printable character sets for use with DBs that may have 
     * character limitations.
     * <p>
     * Note that control, surrogate, format, private use and unassigned 
     * code points are skipped.
     * @param startCodePoint The starting code point, inclusive.
     * @param lastCodePoint The final code point, inclusive.
     * @param characterTypesFilter An optional set of allowable character
     * types. See {@link Character} for types.
     * @param isFilterAllowableList Determines whether the {@code allowableTypes}
     * set is inclusive or exclusive. When true, only those code points that
     * appear in the list will be included in the resulting set. Otherwise
     * matching code points are excluded.
     * @param allowableTypes An optional list of code points for inclusion or
     * exclusion.
     * @return A list of code points matching the given range and filters. The
     * list may be empty but is guaranteed not to be null.
     */
    public static List<Integer> generatePrintableCharacterSet(
        final int startCodePoint,
        final int lastCodePoint, 
        final Set<Integer> characterTypesFilter,
        final boolean isFilterAllowableList,
        final Set<Integer> allowableTypes) {
      
      // since we don't know the final size of the allowable character list we
      // start with a list then we'll flatten it to an array.
      final List<Integer> validCharacters = new ArrayList<Integer>(lastCodePoint);
      
      for (int codePoint = startCodePoint; codePoint <= lastCodePoint; ++codePoint) {
        if (allowableTypes != null && 
            !allowableTypes.contains(Character.getType(codePoint))) {
          continue;
        } else {
          // skip control points, formats, surrogates, etc
          final int type = Character.getType(codePoint);
          if (type == Character.CONTROL ||
              type == Character.SURROGATE ||
              type == Character.FORMAT ||
              type == Character.PRIVATE_USE ||
              type == Character.UNASSIGNED) {
            continue;
          }
        }
        
        if (characterTypesFilter != null) {
          // if the filter is enabled then we need to make sure the code point 
          // is in the allowable list if it's a whitelist or that the code point
          // is NOT in the list if it's a blacklist.
          if ((isFilterAllowableList && !characterTypesFilter.contains(codePoint)) ||
              (characterTypesFilter.contains(codePoint))) {
            continue;
          }
        }
        
        validCharacters.add(codePoint);
      }
      return validCharacters;
    }
    
  }
  