content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.
   */
  package org.apache.chemistry.opencmis.inmemory.content.loremipsum;
  
  import java.io.IOException;
  import java.io.StringWriter;
  import java.security.SecureRandom;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * A generator of lorem ipsum text ported from the Python implementation at
   * http://code.google.com/p/lorem-ipsum-generator/.
   * 
   * Note: Original code licensed under the BSD license
   */
  public class LoremIpsum {
  
      private static class WordLengthPair {
          public int len1;
          public int len2;
  
          public WordLengthPair(int len1, int len2) {
              this.len1 = len1;
              this.len2 = len2;
          }
  
          @Override
          public String toString() {
              return "WordLengthPair: len1: " + len1 + ", len2: " + len2;
          }
  
          @Override
          public boolean equals(Object other) {
              if (this == null || other == null) {
                  return false;
              }
              if (other.getClass() != getClass()) {
                  return false;
              }
  
              return len1 == ((WordLengthPair) other).len1 && len2 == ((WordLengthPair) other).len2;
          }
  
          @Override
          public int hashCode() {
              return len1 ^ len2;
          }
      }
  
      /**
       * Delimiters that end sentences.
       * 
       * @type {Array.<string>}
       */
      private static final String DELIMITERS_SENTENCES[] = { ".", "?", "!" };
  
      /**
       * Regular expression for splitting a text into sentences.
       * 
       * @type {RegExp}
       */
      private static final String SENTENCE_SPLIT_REGEX = "[\\.\\?\\!]";
  
      /**
       * Delimiters that end words.
       * 
       * @type {Array.<string>}
       */
      private static final String DELIMITERS_WORDS[] = { ".", ",", "?", "!" };
  
      /**
       * Regular expression for splitting text into words.
       */
      private static final String WORD_SPLIT_REGEX = "\\s";
  
      private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  
      /**
       * Words that can be used in the generated output. Maps a word-length to a
       * list of words of that length.
       */
      private Map<Integer, List<String>> words;
  
      /**
       * Chains of three words that appear in the sample text Maps a pair of
       * word-lengths to a third word-length and an optional piece of trailing
       * punctuation (for example, a period, comma, etc.).
       */
      private Map<WordLengthPair, List<WordInfo>> chains;
  
      /**
       * Pairs of word-lengths that can appear at the beginning of sentences.
       */
      private List<WordLengthPair> starts;
  
      /**
       * Average sentence length in words.
       */
  
      private double sentenceMean;
  
      /**
       * Sigma (sqrt of Objectiance) for the sentence length in words.
       */
      private double sentenceSigma;
  
      /**
       * Average paragraph length in sentences.
       */
      private double paragraphMean;
  
      /**
       * Sigma (sqrt of variance) for the paragraph length in sentences.
       * 
       * @type {number}
       */
      private double paragraphSigma;
  
      /**
       * Sample that the generated text is based on .
       * 
       * @type {string}
       */
      private String sample = SAMPLE;
  
      /**
       * Dictionary of words.
       * 
       * @type {string}
       */
      private String dictionary = DICT;
  
      /**
       * Picks a random element of the array.
       * 
       * @param array
       *            the array to pick from
       * @return an element from the array
       */
      private WordInfo randomChoice(WordInfo[] array) {
          return array[randomInt(array.length)];
      }
  
      private String randomChoice(String[] array) {
          return array[randomInt(array.length)];
      }
  
      private int randomInt(int length) {
          return randomGenerator.nextInt(length);
      }
  
      private static class WordInfo {
          int len;
          String delim;
      }
  
      private SecureRandom randomGenerator = new SecureRandom();
  
      /**
       * Generates random strings of "lorem ipsum" text, based on the word
       * distribution of a sample text, using the words in a dictionary.
       */
      public LoremIpsum() {
          generateChains(this.sample);
          generateStatistics(this.sample);
          initializeDictionary(this.dictionary);
      }
  
      public LoremIpsum(String sample, String dictionary) {
          this.sample = sample;
          this.dictionary = dictionary;
          generateChains(this.sample);
          generateStatistics(this.sample);
          initializeDictionary(this.dictionary);
      }
  
      public LoremIpsum(String sample, String[] newDictionary) {
          this.sample = sample;
          this.dictionary = null;
          generateChains(this.sample);
          generateStatistics(this.sample);
          initializeDictionary(newDictionary);
      }
  
      public LoremIpsum(String sample) {
          this.sample = sample;
          String[] dictWords = filterNotEmptyOrWhiteSpace(sample.split("[^\\p{L}]"/* "\\W" */)).toArray(new String[0]);
          Set<String> dict = new HashSet<String>(Arrays.asList(dictWords));
          dictWords = dict.toArray(new String[0]);
          Arrays.sort(dictWords);
  
          generateChains(this.sample);
          generateStatistics(this.sample);
          initializeDictionary(dictWords);
      }
  
      /**
       * Generates a single lorem ipsum paragraph, of random length.
       * 
       * @param optStartWithLorem
       *            Whether to start the sentence with the standard
       *            "Lorem ipsum..." first sentence
       * @return the generated sentence
       */
      public String generateParagraph(boolean optStartWithLorem) {
          // The length of the paragraph is a normally distributed random
          // Objectiable.
          Double paragraphLengthDbl = randomNormal(this.paragraphMean, this.paragraphSigma);
          int paragraphLength = Math.max((int) Math.floor(paragraphLengthDbl), 1);
  
          // Construct a paragraph from a number of sentences.
          List<String> paragraph = new ArrayList<String>();
          boolean startWithLorem = optStartWithLorem;
          while (paragraph.size() < paragraphLength) {
              String sentence = this.generateSentence(startWithLorem);
              paragraph.add(sentence);
              startWithLorem = false;
          }
  
          StringBuffer result = new StringBuffer();
          // Form the paragraph into a string.
          for (String sentence : paragraph) {
              result.append(sentence);
              result.append(" ");
          }
          return result.toString();
      }
  
      /**
       * Generates a single sentence, of random length.
       * 
       * @param optStartWithLorem
       *            Whether to start the sentence with the standard
       *            "Lorem ipsum..." first sentence.
       * @return the generated sentence
       */
      public String generateSentence(boolean optStartWithLorem) {
          if (this.chains.size() == 0 || this.starts.size() == 0) {
              throw new RuntimeException("No chains created (Invalid sample text?)");
          }
  
          if (this.words.size() == 0) {
              throw new RuntimeException("No dictionary");
          }
  
          // The length of the sentence is a normally distributed random
          // Objectiable.
          double sentenceLengthDbl = randomNormal(this.sentenceMean, this.sentenceSigma);
          int sentenceLength = Math.max((int) Math.floor(sentenceLengthDbl), 1);
  
          String wordDelimiter = ""; // Defined here in case while loop doesn't
                                     // run
  
          // Start the sentence with "Lorem ipsum...", if desired
          List<String> sentence;
  
          if (optStartWithLorem) {
              String lorem = "lorem ipsum dolor sit amet, consecteteur adipiscing elit";
              sentence = new ArrayList<String>(Arrays.asList(splitWords(lorem)));
              if (sentence.size() > sentenceLength) {
                  sentence.subList(0, sentenceLength);
              }
              String lastWord = sentence.get(sentence.size() - 1);
              String lastChar = lastWord.substring(lastWord.length() - 1);
              if (contains(DELIMITERS_WORDS, lastChar)) {
                  wordDelimiter = lastChar;
              }
          } else {
              sentence = new ArrayList<String>();
          }
  
          WordLengthPair previous = new WordLengthPair(0, 0);
  
          // Generate a sentence from the "chains"
          while (sentence.size() < sentenceLength) {
              // If the current starting point is invalid, choose another randomly
              if (!this.chains.containsKey(previous)) {
                  previous = this.chooseRandomStart_();
              }
  
              // Choose the next "chain" to go to. This determines the next word
              // length we use, and whether there is e.g. a comma at the end of
              // the word.
              WordInfo chain = randomChoice(this.chains.get(previous).toArray(new WordInfo[0]));
              int wordLength = chain.len;
  
              // If the word delimiter contained in the chain is also a sentence
              // delimiter, then we don"t include it because we don"t want the
              // sentence to end prematurely (we want the length to match the
              // sentence_length value).
              if (contains(DELIMITERS_SENTENCES, chain.delim)) {
                  wordDelimiter = "";
              } else {
                  wordDelimiter = chain.delim;
              }
  
              // Choose a word randomly that matches (or closely matches) the
              // length we're after.
              int closestLength = chooseClosest(this.words.keySet().toArray(new Integer[0]), wordLength);
              String word = randomChoice(this.words.get(closestLength).toArray(new String[0]));
  
              sentence.add(word + wordDelimiter);
              previous = new WordLengthPair(previous.len2, wordLength);
  
          }
  
          // Finish the sentence off with capitalisation, a period and
          // form it into a string
          StringBuffer result = new StringBuffer();
          for (String s : sentence) {
              result.append(s);
              result.append(" ");
          }
          result.deleteCharAt(result.length() - 1);
  
          result.replace(0, 1, result.substring(0, 1).toUpperCase());
          int strLen = result.length() - 1;
          if (wordDelimiter.length() > 0 && wordDelimiter.charAt(0) == result.charAt(strLen)) {
              result.deleteCharAt(strLen);
          }
          result.append(".");
          return result.toString();
      }
  
      public String getSample() {
          return sample;
      }
  
      public void setSample(String sample) {
          this.sample = sample;
          generateChains(this.sample);
          generateStatistics(this.sample);
      }
  
      public String getDictionary() {
          return dictionary;
      }
  
      public void setDictionary(String dictionary) {
          this.dictionary = dictionary;
          initializeDictionary(this.dictionary);
      }
  
      /**
       * Generates multiple paragraphs of text, with begin before the paragraphs,
       * end after the paragraphs, and between between each two paragraphs.
       */
      private String generateMarkupParagraphs(String begin, String end, String between, int quantity,
              boolean startWithLorem) {
  
          StringBuffer text = new StringBuffer();
  
          text.append(begin);
          String para = generateParagraph(startWithLorem);
          text.append(para);
          while (text.length() < quantity) {
              para = generateParagraph(false);
              text.append(para);
              if (text.length() < quantity) {
                  text.append(between);
              }
          }
  
          text.append(end);
          return text.toString();
      }
  
      /**
       * Generates multiple paragraphs of text, with begin before the paragraphs,
       * end after the paragraphs, and between between each two paragraphs.
       * 
       * @throws IOException
       */
      private void generateMarkupParagraphs(Appendable writer, String begin, String end, String between, int quantity,
              boolean startWithLorem) throws IOException {
  
          int len = begin.length();
          writer.append(begin);
          String para = generateParagraph(startWithLorem);
          len += para.length();
          writer.append(para);
          while (len < quantity) {
              para = generateParagraph(false);
              len += para.length();
              writer.append(para);
              if (len < quantity) {
                  writer.append(between);
                  len += para.length();
              }
          }
  
          writer.append(end);
      }
  
      /**
       * Generates multiple sentences of text, with begin before the sentences,
       * end after the sentences, and between between each two sentences.
       */
      private String generateMarkupSentences(String begin, String end, String between, int quantity,
              boolean startWithLorem) {
  
          StringBuffer text = new StringBuffer();
          text.append(begin);
          String sentence = generateSentence(startWithLorem);
          text.append(sentence);
  
          while (text.length() < quantity) {
              sentence = generateSentence(false);
              text.append(sentence);
              if (text.length() < quantity) {
                  text.append(between);
              }
          }
  
          text.append(end);
          return text.toString();
      }
  
      /**
       * Generates the chains and starts values required for sentence generation.
       * 
       * @param sample
       *            The same text.
       */
      private void generateChains(String sample) {
  
          String[] words = splitWords(sample);
          WordInfo[] wordInfos = generateWordInfo(words);
          WordLengthPair previous = new WordLengthPair(0, 0);
          List<WordLengthPair> starts = new ArrayList<WordLengthPair>();
          List<String> delimList = Arrays.asList(DELIMITERS_SENTENCES);
          Map<WordLengthPair, List<WordInfo>> chains = new HashMap<WordLengthPair, List<WordInfo>>();
  
          for (WordInfo wi : wordInfos) {
              if (wi.len == 0) {
                  continue;
              }
  
              List<WordInfo> value = chains.get(previous);
              if (null == value) {
                  chains.put(previous, new ArrayList<WordInfo>());
              } else {
                  chains.get(previous).add(wi);
              }
  
              if (delimList.contains(wi.delim)) {
                  starts.add(previous);
              }
  
              previous.len1 = previous.len2;
              previous.len2 = wi.len;
          }
  
          if (chains.isEmpty()) {
              throw new RuntimeException("Invalid sample text.");
          }
  
          this.chains = chains;
          this.starts = starts;
      }
  
      /**
       * Calculates the mean and standard deviation of sentence and paragraph
       * lengths.
       * 
       * @param sample
       *            The same text.
       */
      private void generateStatistics(String sample) {
          this.generateSentenceStatistics(sample);
          this.generateParagraphStatistics(sample);
      }
  
      /**
       * Calculates the mean and standard deviation of the lengths of sentences
       * (in words) in a sample text.
       * 
       * @param sample
       *            The same text.
       */
      private void generateSentenceStatistics(String sample) {
          List<String> sentences = filterNotEmptyOrWhiteSpace(splitSentences(sample));
          int sentenceLengths[] = new int[sentences.size()];
          for (int i = 0; i < sentences.size(); i++) {
              String[] words = splitWords(sentences.get(i));
              sentenceLengths[i] = words.length;
          }
          this.sentenceMean = mean(sentenceLengths);
          this.sentenceSigma = sigma(sentenceLengths);
      }
  
      /**
       * Calculates the mean and standard deviation of the lengths of paragraphs
       * (in sentences) in a sample text.
       * 
       * @param sample
       *            The same text.
       */
      private void generateParagraphStatistics(String sample) {
          List<String> paragraphs = filterNotEmptyOrWhiteSpace(splitParagraphs(sample));
  
          int paragraphLengths[] = new int[paragraphs.size()];
          for (int i = 0; i < paragraphs.size(); i++) {
              String[] sentences = splitSentences(paragraphs.get(i));
              paragraphLengths[i] = sentences.length;
          }
  
          this.paragraphMean = mean(paragraphLengths);
          this.paragraphSigma = sigma(paragraphLengths);
      }
  
      /**
       * Sets the generator to use a given selection of words for generating
       * sentences with.
       * 
       * @param dictionary
       *            The dictionary to use.
       */
      private void initializeDictionary(String dictionary) {
          String[] dictionaryWords = splitWords(dictionary);
          initializeDictionary(dictionaryWords);
      }
  
      private void initializeDictionary(String[] dictionaryWords) {
          words = new HashMap<Integer, List<String>>();
          for (String word : dictionaryWords) {
              List<String> wordWithLen = words.get(word.length());
              if (null == wordWithLen) {
                  List<String> list = new ArrayList<String>();
                  list.add(word);
                  words.put(word.length(), list);
              } else {
                  wordWithLen.add(word);
              }
          }
  
          if (words.size() == 0) {
              throw new RuntimeException("Invalid dictionary.");
          }
      }
  
      /**
       * Picks a random starting chain.
       * 
       * @return {string} The starting key.
       */
      private WordLengthPair chooseRandomStart_() {
          Set<WordLengthPair> keys = chains.keySet();
          Set<WordLengthPair> validStarts = new HashSet<WordLengthPair>(starts);
          validStarts.retainAll(keys);
          int index = randomInt(validStarts.size());
          WordLengthPair wlPair = validStarts.toArray(new WordLengthPair[0])[index];
          return wlPair;
      }
  
      /**
       * Splits a piece of text into paragraphs.
       * 
       * @param text
       *            The text to split.
       * @return An array of paragraphs.
       */
  
      static String[] splitParagraphs(String text) {
          return filterNotEmptyOrWhiteSpace(text.split("\n")).toArray(new String[0]);
      }
  
      /**
       * Splits a piece of text into sentences.
       * 
       * @param text
       *            The text to split.
       * @return An array of sentences.
       */
      static String[] splitSentences(String text) {
          return filterNotEmptyOrWhiteSpace(text.split(SENTENCE_SPLIT_REGEX)).toArray(new String[0]);
      }
  
      /**
       * Splits a piece of text into words..
       * 
       * @param text
       *            The text to split.
       * @return An array of words.
       */
      static String[] splitWords(String text) {
          return filterNotEmptyOrWhiteSpace(text.split(WORD_SPLIT_REGEX)).toArray(new String[0]);
      }
  
      /**
       * Find the number in the list of values that is closest to the target.
       * 
       * @param values
       *            The values.
       * @param target
       *            The target value.
       * @return The closest value.
       */
      static int chooseClosest(Integer[] values, int target) {
          int closest = values[0];
          for (int value : values) {
              if (Math.abs(target - value) < Math.abs(target - closest)) {
                  closest = value;
              }
          }
  
          return closest;
      }
  
      /**
       * Gets info about a word used as part of the lorem ipsum algorithm.
       * 
       * @param word
       *            The word to check.
       * @return A two element array. The first element is the size of the word.
       *         The second element is the delimiter used in the word.
       */
      private static WordInfo getWordInfo(String word) {
          WordInfo ret = new WordInfo();
          for (String delim : DELIMITERS_WORDS) {
              if (word.endsWith(delim)) {
                  ret.len = word.length() - delim.length();
                  ret.delim = delim;
                  return ret;
              }
          }
          ret.len = word.length();
          ret.delim = "";
          return ret;
      }
  
      private static WordInfo[] generateWordInfo(String[] words) {
          WordInfo[] result = new WordInfo[words.length];
          int i = 0;
          for (String word : words) {
              result[i++] = getWordInfo(word);
          }
          return result;
      }
  
      /**
       * Constant used for {@link #randomNormal_}.
       */
      private static final double NV_MAGICCONST_ = 4 * Math.exp(-0.5) / Math.sqrt(2.0);
  
      /**
       * Generates a random number for a normal distribution with the specified
       * mean and sigma.
       * 
       * @param mu
       *            The mean of the distribution.
       * @param sigma
       *            The sigma of the distribution.
       */
      private static double randomNormal(double mu, double sigma) {
          SecureRandom rnd = new SecureRandom();
  
          double z = 0.0d;
          while (true) {
              double u1 = rnd.nextDouble();
              double u2 = 1.0d - rnd.nextDouble();
              z = NV_MAGICCONST_ * (u1 - 0.5d) / u2;
              double zz = z * z / 4.0d;
              if (zz <= -Math.log(u2)) {
                  break;
              }
          }
          return mu + z * sigma;
      }
  
      /**
       * Returns the text if it is not empty or just whitespace.
       * 
       * @param text
       *            the text to check.
       * @return Whether the text is neither empty nor whitespace.
       */
      private static List<String> filterNotEmptyOrWhiteSpace(String[] arr) {
          List<String> result = new ArrayList<String>();
          for (String s : arr) {
              String trims = s.trim();
              if (trims.length() > 0) {
                  result.add(trims);
              }
          }
          return result;
      }
  
      public static double mean(int[] values) {
          return ((double) sum(values)) / ((double) (Math.max(values.length, 1)));
      }
  
      public static double mean(double[] values) {
          return sum(values) / ((Math.max(values.length, 1)));
      }
  
      public static double variance(double[] values) {
          double[] squared = new double[values.length];
          for (int i = 0; i < values.length; i++) {
              squared[i] = values[i] * values[i];
          }
  
          double meanVal = mean(values);
          return mean(squared) - (meanVal * meanVal);
      }
  
      public static double sigma(int[] values) {
          double[] d = new double[values.length];
          for (int i = 0; i < values.length; i++) {
              d[i] = values[i];
          }
  
          return sigma(d);
      }
  
      public static double sigma(double[] values) {
          return Math.sqrt(variance(values));
      }
  
      public static int sum(int[] values) {
          int sum = 0;
          for (int val : values) {
              sum += val;
          }
          return sum;
      }
  
      public static double sum(double[] values) {
          double sum = 0.0d;
          for (double val : values) {
              sum += val;
          }
          return sum;
      }
  
      public static boolean contains(String[] array, String val) {
          for (String s : array) {
              if (s.equals(val)) {
                  return true;
              }
          }
          return false;
      }
  
      /* for unit testing */
      double getSentenceMean() {
          return sentenceMean;
      }
  
      double getSentenceSigma() {
          return sentenceSigma;
      }
  
      double getParagraphMean() {
          return paragraphMean;
      }
  
      double getParagraphSigma() {
          return paragraphSigma;
      }
  
      /**
       * Dictionary of words for lorem ipsum.
       */
      private static final String DICT = "a ac accumsan ad adipiscing aenean aliquam aliquet amet ante "
              + "aptent arcu at auctor augue bibendum blandit class commodo "
              + "condimentum congue consectetuer consequat conubia convallis cras "
              + "cubilia cum curabitur curae cursus dapibus diam dictum dictumst "
              + "dignissim dis dolor donec dui duis egestas eget eleifend elementum "
              + "elit eni enim erat eros est et etiam eu euismod facilisi facilisis "
              + "fames faucibus felis fermentum feugiat fringilla fusce gravida "
              + "habitant habitasse hac hendrerit hymenaeos iaculis id imperdiet "
              + "in inceptos integer interdum ipsum justo lacinia lacus laoreet "
              + "lectus leo libero ligula litora lobortis lorem luctus maecenas "
              + "magna magnis malesuada massa mattis mauris metus mi molestie "
              + "mollis montes morbi mus nam nascetur natoque nec neque netus "
              + "nibh nisi nisl non nonummy nostra nulla nullam nunc odio orci "
              + "ornare parturient pede pellentesque penatibus per pharetra "
              + "phasellus placerat platea porta porttitor posuere potenti praesent "
              + "pretium primis proin pulvinar purus quam quis quisque rhoncus "
              + "ridiculus risus rutrum sagittis sapien scelerisque sed sem semper "
              + "senectus sit sociis sociosqu sodales sollicitudin suscipit "
              + "suspendisse taciti tellus tempor tempus tincidunt torquent tortor "
              + "tristique turpis ullamcorper ultrices ultricies urna ut Objectius ve "
              + "vehicula vel velit venenatis vestibulum vitae vivamus viverra " + "volutpat vulputate";
  
      /**
       * A sample to use for generating the distribution of word and sentence
       * lengths in lorem ipsum.
       */
      private static final String SAMPLE = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean "
              + "commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus "
              + "et magnis dis parturient montes, nascetur ridiculus mus. Donec quam "
              + "felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
              + "consequat massa quis enim. Donec pede justo, fringilla vel, aliquet "
              + "nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, "
              + "venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. "
              + "Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean "
              + "vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat "
              + "vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra "
              + "quis, feugiat a, tellus. Phasellus viverra nulla ut metus Objectius "
              + "laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel "
              + "augue. Curabitur ullamcorper ultricies nisi. Nam eget dui.\n\n" +
  
              "Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem "
              + "quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam "
              + "nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec "
              + "odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis "
              + "faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus "
              + "tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales "
              + "sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit "
              + "cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend "
              + "sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, "
              + "metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis "
              + "hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci "
              + "luctus et ultrices posuere cubilia Curae; In ac dui quis mi " + "consectetuer lacinia.\n\n" +
  
              "Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet "
              + "nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer "
              + "ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent "
              + "adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy "
              + "metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros "
              + "et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, "
              + "nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit "
              + "risus. Phasellus nec sem in justo pellentesque facilisis. Etiam "
              + "imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus "
              + "non, auctor et, hendrerit quis, nisi.\n\n" +
  
              "Curabitur ligula sapien, tincidunt non, euismod vitae, posuere "
              + "imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed "
              + "cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus "
              + "accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci "
              + "luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis "
              + "porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis "
              + "orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, "
              + "bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede "
              + "sit amet augue. In turpis. Pellentesque posuere. Praesent turpis.\n\n" +
  
              "Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu "
              + "sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales "
              + "nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse "
              + "pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, "
              + "nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in "
              + "faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id "
              + "purus. Ut Objectius tincidunt libero. Phasellus dolor. Maecenas vestibulum "
              + "mollis diam. Pellentesque ut neque. Pellentesque habitant morbi "
              + "tristique senectus et netus et malesuada fames ac turpis egestas.\n\n" +
  
              "In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac "
              + "felis quis tortor malesuada pretium. Pellentesque auctor neque nec "
              + "urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean "
              + "viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et "
              + "netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis "
              + "pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna.\n\n" +
  
              "In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare "
              + "lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis "
              + "ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. "
              + "Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, "
              + "quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at "
              + "pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo "
              + "quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam "
              + "sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce "
              + "risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis " + "vulputate lorem.\n\n" +
  
              "Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, "
              + "dui et placerat feugiat, eros pede Objectius nisi, condimentum viverra "
              + "felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, "
              + "Objectius ut, felis. In auctor lobortis lacus. Quisque libero metus, "
              + "condimentum nec, tempor a, commodo mollis, magna. Vestibulum "
              + "ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia "
              + "erat. Praesent blandit laoreet nibh.\n\n" +
  
              "Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, "
              + "neque sit amet convallis pulvinar, justo nulla eleifend augue, ac "
              + "auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. "
              + "Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. "
              + "Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In "
              + "hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis "
              + "mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat "
              + "nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, " + "lacus.\n\n" +
  
              "Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, "
              + "dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi "
              + "congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin "
              + "fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit "
              + "amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam "
              + "gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac "
              + "sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus "
              + "blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in "
              + "libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In "
              + "consectetuer turpis ut velit. Nulla sit amet est. Praesent metus "
              + "tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus "
              + "ipsum, faucibus ut, ullamcorper id, Objectius ac, leo. Suspendisse "
              + "feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum "
              + "nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac "
              + "massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, "
              + "iaculis quis, molestie non, velit.\n\n" +
  
              "Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. "
              + "Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus "
              + "at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet "
              + "velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. "
              + "Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, "
              + "sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla "
              + "facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere "
              + "iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. "
              + "Curabitur suscipit suscipit tellus.\n\n" +
  
              "Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id "
              + "nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu "
              + "pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante "
              + "odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque "
              + "suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce "
              + "ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu "
              + "quis ligula mattis placerat. Duis lobortis massa imperdiet quam. " + "Suspendisse potenti.\n\n" +
  
              "Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, "
              + "lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat "
              + "volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam "
              + "eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris "
              + "ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta "
              + "dolor. Class aptent taciti sociosqu ad litora torquent per conubia "
              + "nostra, per inceptos hymenaeos.\n\n" +
  
              "Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. "
              + "Sed a libero. Cras Objectius. Donec vitae orci sed dolor rutrum auctor. "
              + "Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, "
              + "elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum "
              + "sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus "
              + "non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. " + "Vestibulum eu odio.\n\n" +
  
              "Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. "
              + "Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique "
              + "sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse "
              + "faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, "
              + "vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce "
              + "fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae "
              + "odio lacinia tincidunt. Praesent ut ligula non mi Objectius sagittis. "
              + "Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus "
              + "consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna "
              + "cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit "
              + "quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar " + "varius.\n\n";
  
      /**
       * Generates a number of paragraphs, with each paragraph surrounded by HTML
       * paragraph tags.
       */
      public String generateParagraphsHtml(int quantity, boolean startWithLorem) {
  
          return generateMarkupParagraphs("<p>" + LINE_SEPARATOR + "\t", LINE_SEPARATOR + "</p>", LINE_SEPARATOR + "</p>"
                  + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR + "\t", quantity, startWithLorem);
  
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph surrounded by HTML
       * paragraph tags.
       */
      public void generateParagraphsHtml(Appendable writer, int quantity, boolean startWithLorem) throws IOException {
  
          generateMarkupParagraphs(writer, "<p>" + LINE_SEPARATOR + "\t", LINE_SEPARATOR + "</p>", LINE_SEPARATOR
                  + "</p>" + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR + "\t", quantity, startWithLorem);
  
      }
  
      /**
       * Generates one paragraph of HTML, surrounded by HTML paragraph tags.
       */
      public String generateOneParagraphHtml(int quantity, boolean startWithLorem) {
  
          return generateMarkupSentences("<p>" + LINE_SEPARATOR + "\t", LINE_SEPARATOR + "</p>", LINE_SEPARATOR,
                  quantity, startWithLorem);
  
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph surrounded by HTML
       * paragraph tags as a full HTML page.
       */
      public String generateParagraphsFullHtml(int quantity, boolean startWithLorem) {
  
          String prefix = "<html>" + LINE_SEPARATOR + "<header>" + LINE_SEPARATOR + "<title>Lorem Ipsum</title>"
                  + LINE_SEPARATOR + "</header>" + LINE_SEPARATOR + LINE_SEPARATOR + "<body>";
          String postfix = "</body>" + LINE_SEPARATOR + "</html>" + LINE_SEPARATOR;
  
          return generateMarkupParagraphs(prefix + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR + "\t", LINE_SEPARATOR
                  + "</p>" + LINE_SEPARATOR + postfix, LINE_SEPARATOR + "</p>" + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR
                  + "\t", quantity, startWithLorem);
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph surrounded by HTML
       * paragraph tags as a full HTML page.
       */
      public void generateParagraphsFullHtml(Appendable writer, int quantity, boolean startWithLorem) throws IOException {
  
          String prefix = "<html>" + LINE_SEPARATOR + "<header>" + LINE_SEPARATOR + "<title>Lorem Ipsum</title>"
                  + LINE_SEPARATOR + "</header>" + LINE_SEPARATOR + LINE_SEPARATOR + "<body>";
          String postfix = "</body>" + LINE_SEPARATOR + "</html>" + LINE_SEPARATOR;
  
          generateMarkupParagraphs(writer, prefix + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR + "\t", LINE_SEPARATOR
                  + "</p>" + LINE_SEPARATOR + postfix, LINE_SEPARATOR + "</p>" + LINE_SEPARATOR + "<p>" + LINE_SEPARATOR
                  + "\t", quantity, startWithLorem);
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph separated by two
       * newlines.
       */
      public String generateParagraphsPlainText(int quantity, boolean startWithLorem) {
  
          return generateMarkupParagraphs("", "", LINE_SEPARATOR + LINE_SEPARATOR, quantity, startWithLorem);
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph separated by two
       * newlines.
       */
      public void generateParagraphsPlainText(Appendable writer, int quantity, boolean startWithLorem) throws IOException {
  
          generateMarkupParagraphs(writer, "", "", LINE_SEPARATOR + LINE_SEPARATOR, quantity, startWithLorem);
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph separated by two
       * newlines and no line exceeding maxCols columns
       */
      public String generateParagraphsPlainText(int quantity, int maxCols, boolean startWithLorem) {
  
          StringWriter writer = new StringWriter(quantity + 512);
          try {
              generateParagraphsPlainText(writer, quantity, maxCols, startWithLorem);
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
          return writer.toString();
      }
  
      /**
       * Generates a number of paragraphs, with each paragraph separated by two
       * newlines and no line exceeding maxCols columns
       */
      public void generateParagraphsPlainText(Appendable writer, int quantity, int maxCols, boolean startWithLorem)
              throws IOException {
  
          String delims = " .,?!";
          String unformatted = generateMarkupParagraphs("", "", LINE_SEPARATOR + LINE_SEPARATOR, quantity, startWithLorem);
          int len = unformatted.length();
  
          if (maxCols <= 0) {
              writer.append(unformatted);
          } else {
              int startPos = 0;
              while (startPos < len - 1) {
                  int endPos = Math.min(startPos + maxCols, len - 1);
                  boolean shift = true;
                  // check if there is already a line break:
                  for (int i = startPos; i < endPos; i++) {
                      if (unformatted.charAt(i) == '\n') {
                          shift = false;
                          endPos = i;
                      }
                  }
                  char ch = unformatted.charAt(endPos);
                  while (shift) {
                      for (int i = 0; i < delims.length(); i++) {
                          if (ch == delims.charAt(i)) {
                              shift = false;
                              break;
                          }
                      }
                      if (shift) {
                          ch = unformatted.charAt(--endPos);
                          shift = endPos > startPos;
                      }
                  }
                  writer.append(unformatted.substring(startPos, endPos + 1));
                  if (unformatted.charAt(endPos) != '\n') {
                      writer.append(LINE_SEPARATOR);
                  }
                  startPos = endPos + 1;
              }
          }
      }
  }
  