You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by je...@apache.org on 2012/02/07 20:26:18 UTC

svn commit: r1241563 [1/2] - in /chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src: main/java/org/apache/chemistry/opencmis/util/content/ main/java/org/apache/chemistry/opencmis/util/content/fractal/ main/java/org/apache...

Author: jens
Date: Tue Feb  7 19:26:17 2012
New Revision: 1241563

URL: http://svn.apache.org/viewvc?rev=1241563&view=rev
Log:
Add more content types to the ObjectGenerator tool: Html, JPEG-Image

Added:
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexPoint.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexRectangle.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalCalculator.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalGenerator.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsum.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/test/java/org/apache/chemistry/opencmis/util/content/loremipsum/
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/test/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsumTest.java
Removed:
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/LoreIpsum.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/test/java/org/apache/chemistry/opencmis/util/content/LoreIpsumTest.java
Modified:
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/repository/MultiThreadedObjectGenerator.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/repository/ObjGenApp.java
    chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/repository/ObjectGenerator.java

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexPoint.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexPoint.java?rev=1241563&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexPoint.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexPoint.java Tue Feb  7 19:26:17 2012
@@ -0,0 +1,54 @@
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.util.content.fractal;
+
+public class ComplexPoint {
+    private double real;
+    private double imaginary;
+
+    public ComplexPoint(double real, double imaginary) {
+        this.real = real;
+        this.imaginary = imaginary;
+    }
+
+    public ComplexPoint() {
+        real = 0.0;
+        imaginary = 0.0;
+    }
+
+    public double getImaginary() {
+        return imaginary;
+    }
+
+    public double getReal() {
+        return real;
+    }
+
+    public void set(ComplexPoint cp) {
+        real = cp.getReal();
+        imaginary = cp.getImaginary();
+    }
+
+    public void set(double cr, double ci) {
+        real = cr;
+        imaginary = ci;
+    }
+}
\ No newline at end of file

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexRectangle.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexRectangle.java?rev=1241563&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexRectangle.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/ComplexRectangle.java Tue Feb  7 19:26:17 2012
@@ -0,0 +1,91 @@
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.util.content.fractal;
+
+
+public class ComplexRectangle {
+    private double iMin; // imaginary
+    private double iMax;
+    private double rMin; // real
+    private double rMax;
+
+    public ComplexRectangle(double r1, double r2, double i1, double i2) {
+        set(r1, r2, i1, i2);
+    }
+
+    public ComplexRectangle() {
+        set(0.0, 0.0, 0.0, 0.0);
+    }
+
+    public ComplexRectangle(ComplexRectangle cr) {
+        set(cr);
+    }
+
+    public double getIMin() {
+        return iMin;
+    }
+
+    public double getIMax() {
+        return iMax;
+    }
+
+    public double getRMin() {
+        return rMin;
+    }
+
+    public double getRMax() {
+        return rMax;
+    }
+
+    public double getHeight() {
+        return iMax - iMin;
+    }
+
+    public double getWidth() {
+        return rMax - rMin;
+    }
+
+    public void set(ComplexRectangle cr) {
+        set(cr.getRMin(), cr.getRMax(), cr.getIMin(), cr.getIMax());
+    }
+
+    public void set(ComplexPoint p1, ComplexPoint p2) {
+        set(p1.getReal(), p2.getReal(), p1.getImaginary(), p2.getImaginary());
+    }
+
+    public void set(double r1, double r2, double i1, double i2) {
+        if (r1 > r2) {
+            rMin = r2;
+            rMax = r1;
+        } else {
+            rMin = r1;
+            rMax = r2;
+        }
+        if (i1 > i2) {
+            iMin = i2;
+            iMax = i1;
+        } else {
+            iMin = i1;
+            iMax = i2;
+        }
+    }
+}
\ No newline at end of file

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalCalculator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalCalculator.java?rev=1241563&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalCalculator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalCalculator.java Tue Feb  7 19:26:17 2012
@@ -0,0 +1,152 @@
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.util.content.fractal;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+
+final class FractalCalculator {
+    private int[] colorMap;
+    private double delta;
+    private double iRangeMax;
+    private double iRangeMin;
+    private int maxIterations;
+    private ComplexRectangle newRect;
+    private int numColors;
+    private int imageHeight;
+    private int imageWidth;
+    private double rRangeMax;
+    private double rRangeMin;
+    // For Julia set:
+    private double cJuliaPointR = 0.0; // Real
+    private double cJuliaPointI = 0.0; // Imaginary
+    boolean useJulia = false;
+
+    public FractalCalculator(ComplexRectangle complRect, int maxIters, int imgWidth, int imgHeight, int[] colMap,
+            ComplexPoint juliaPoint) {
+        maxIterations = maxIters;
+        newRect = complRect;
+        imageWidth = imgWidth;
+        imageHeight = imgHeight;
+        colorMap = colMap;
+        numColors = colorMap.length;
+        rRangeMin = newRect.getRMin();
+        rRangeMax = newRect.getRMax();
+        iRangeMin = newRect.getIMin();
+        iRangeMax = newRect.getIMax();
+        delta = (rRangeMax - rRangeMin) / imageWidth;
+        if (null != juliaPoint) {
+            cJuliaPointR = juliaPoint.getReal();
+            cJuliaPointI = juliaPoint.getImaginary();
+            useJulia = true;
+        }
+    }
+
+    public BufferedImage calcFractal() {
+
+        // Assign a color to every pixel ( x , y ) in the Image, corresponding
+        // to
+        // one point, z, in the imaginary plane ( zr, zi ).
+        BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_3BYTE_BGR);
+
+        // For each pixel...
+        for (int x = 0; x < imageWidth; x++) {
+            for (int y = 0; y < imageHeight; y++) {
+                int color = getColor(x, y);
+                image.setRGB(x, y, color);
+            }
+        }
+        return image;
+    }
+
+    private int getColor(int x, int y) {
+        int c = Color.black.getRGB();
+        double zR = rRangeMin + x * delta;
+        double zI = iRangeMin + (imageHeight - y) * delta;
+
+        // Is the point inside the set?
+        int numIterations;
+        if (useJulia)
+            numIterations = testPointJuliaSet(zR, zI, maxIterations);
+        else
+            numIterations = testPointMandelbrot(zR, zI, maxIterations);
+
+        if (numIterations != 0) {
+            // The point is outside the set. It gets a color based on the number
+            // of iterations it took to know this.
+            int colorNum = (int) (numColors * (1.0 - (float) numIterations / (float) maxIterations));
+            colorNum = (colorNum == numColors) ? 0 : colorNum;
+
+            c = colorMap[colorNum];
+        }
+        return c;
+    }
+
+    private int testPointMandelbrot(double cR, double cI, int maxIterations) {
+        // Is the given complex point, (cR, cI), in the Mandelbrot set?
+        // Use the formula: z <= z*z + c, where z is initially equal to c.
+        // If |z| >= 2, then the point is not in the set.
+        // Return 0 if the point is in the set; else return the number of
+        // iterations it took to decide that the point is not in the set.
+        double zR = cR;
+        double zI = cI;
+
+        for (int i = 1; i <= maxIterations; i++) {
+            // To square a complex number: (a+bi)(a+bi) = a*a - b*b + 2abi
+            double zROld = zR;
+            zR = zR * zR - zI * zI + cR;
+            zI = 2 * zROld * zI + cI;
+
+            // We know that if the distance from z to the origin is >= 2
+            // then the point is out of the set. To avoid a square root,
+            // we'll instead check if the distance squared >= 4.
+            double distSquared = zR * zR + zI * zI;
+            if (distSquared >= 4) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    private int testPointJuliaSet(double zR, double zI, int maxIterations) {
+        // Is the given complex point, (zR, zI), in the Julia set?
+        // Use the formula: z <= z*z + c, where z is the point being tested,
+        // and c is the Julia Set constant.
+        // If |z| >= 2, then the point is not in the set.
+        // Return 0 if the point is in the set; else return the number of
+        // iterations it took to decide that the point is not in the set.
+        for (int i = 1; i <= maxIterations; i++) {
+            double zROld = zR;
+            // To square a complex number: (a+bi)(a+bi) = a*a - b*b + 2abi
+            zR = zR * zR - zI * zI + cJuliaPointR;
+            zI = 2 * zROld * zI + cJuliaPointI;
+            // We know that if the distance from z to the origin is >= 2
+            // then the point is out of the set. To avoid a square root,
+            // we'll instead check if the distance squared >= 4.
+            double distSquared = zR * zR + zI * zI;
+            if (distSquared >= 4) {
+                return i;
+            }
+        }
+        return 0;
+    }
+}
\ No newline at end of file

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalGenerator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalGenerator.java?rev=1241563&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalGenerator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/fractal/FractalGenerator.java Tue Feb  7 19:26:17 2012
@@ -0,0 +1,507 @@
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ */
+
+/*
+ * Original code inspired by work from David Lebernight 
+ * see: http://www.gui.net/fractal.html
+ * email as requested in original source has been sent,
+ * to david@leberknight.com, but address is invalid (2012-02-07)
+ */
+
+package org.apache.chemistry.opencmis.util.content.fractal;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+public class FractalGenerator {
+    private static final Log LOG = LogFactory.getLog(FractalGenerator.class);
+
+    private final static int ZOOM_STEPS_PER_BATCH = 10;
+    private final static int DEFAULT_MAX_ITERATIONS = 33;
+    private final static ComplexRectangle INITIAL_RECT = new ComplexRectangle(-2.1, 1.1, -1.3, 1.3);
+    private final static ComplexRectangle INITIAL_JULIA_RECT = new ComplexRectangle(-2.0, 2.0, -2.0, 2.0);
+    private final static int INITIAL_ITERATIONS = 33;
+
+    // Color:
+    private Map<String, int[]> colorTable;
+    private final String COLORS_BLACK_AND_WHITE = "black & white";
+    private final String COLORS_BLUE_ICE = "blue ice";
+    private final String COLORS_FUNKY = "funky";
+    private final String COLORS_PASTEL = "pastel";
+    private final String COLORS_PSYCHEDELIC = "psychedelic";
+    private final String COLORS_PURPLE_HAZE = "purple haze";
+    private final String COLORS_RADICAL = "radical";
+    private final String COLORS_RAINBOW = "rainbow";
+    private final String COLORS_RAINBOWS = "rainbows";
+    private final String COLORS_SCINTILLATION = "scintillation";
+    private final String COLORS_WARPED = "warped";
+    private final String COLORS_WILD = "wild";
+    private final String COLORS_ZEBRA = "zebra";
+    private final String[] colorSchemes = {COLORS_BLACK_AND_WHITE, COLORS_BLUE_ICE, COLORS_FUNKY, COLORS_PASTEL,
+        COLORS_PSYCHEDELIC, COLORS_PURPLE_HAZE, COLORS_RADICAL, COLORS_RAINBOW, COLORS_RAINBOWS,
+        COLORS_SCINTILLATION, COLORS_WARPED, COLORS_WILD, COLORS_ZEBRA};
+    private final int imageHeight = 512; // default
+    private final int imageWidth = 512; // default
+    private final int numColors = 512; // colors per colormap
+    private FractalCalculator calculator;
+    private int previousIterations = 1;
+    private int maxIterations;
+    String color;
+    int counter = 0;
+    int newRowTile, newColTile;
+    int parts = 16;
+    private int stepInBatch = 0;
+    ComplexRectangle rect;
+    ComplexPoint juliaPoint;
+    
+    public FractalGenerator() {
+        reset();
+    }
+    
+    private void reset() {
+        rect = new ComplexRectangle(-1.6, -1.2, -0.1, 0.1);
+        juliaPoint = null; // new ComplexPoint();
+        maxIterations = DEFAULT_MAX_ITERATIONS;
+       
+        Random ran = new Random();
+        color = colorSchemes[ran.nextInt(colorSchemes.length)];
+        parts = ran.nextInt(30)+3;
+        LOG.debug("Parts: " + parts);
+        maxIterations = DEFAULT_MAX_ITERATIONS;
+        LOG.debug("Original rect " + ": (" + rect.getRMin() + "r," + rect.getRMax() +
+                "r, " + rect.getIMin() + "i, " + rect.getIMax() + "i)");
+        randomizeRect(rect);
+    }
+    
+    public ByteArrayOutputStream generateFractal() throws IOException {
+        ByteArrayOutputStream bos = null;
+        
+        if (stepInBatch == ZOOM_STEPS_PER_BATCH) {
+            stepInBatch = 0;
+            reset();
+        }
+
+        ++stepInBatch;
+        LOG.debug("Generating rect no " + stepInBatch + ": (" + rect.getRMin() + "r," + 
+                rect.getRMax() +  "r, " + rect.getIMin() + "i, " + rect.getIMax() + "i)");
+        LOG.debug("   width: " + rect.getWidth() + " height: " + rect.getHeight());
+        bos = genFractal(rect, juliaPoint);
+
+        double r1New = rect.getWidth() * newColTile / parts +  rect.getRMin();
+        double r2New = rect.getWidth() * (newColTile+1) / parts +  rect.getRMin();
+        double i1New =  rect.getIMax() - (rect.getHeight() * newRowTile / parts);
+        double i2New =  rect.getIMax() - (rect.getHeight() * (newRowTile+1) / parts);
+        rect.set(r1New, r2New, i1New, i2New);
+        LOG.debug("Done generating fractals.");
+        
+        return bos;
+    }
+
+    private void randomizeRect( ComplexRectangle rect) {
+        double jitterFactor = 0.15; // +/- 15%
+        double ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
+        double width = rect.getWidth() * ran;
+        ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
+        double height = rect.getHeight() * ran;
+        ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
+        double r1 = (rect.getWidth() - width) * ran + rect.getRMin();
+        ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
+        double i1 = (rect.getHeight() - height) * ran + rect.getIMin();
+        rect.set(r1, r1+width, i1, i1+height);
+    }
+
+    /**
+     * Create a fractal image as JPEG in memory and return it  
+     * @param rect
+     *      rectangle of mandelbrot or julia set
+     * @param juliaPoint
+     *      point in Julia set or null
+     * @return
+     *      byte array with JPEG stream
+     * @throws IOException 
+     */
+    public ByteArrayOutputStream genFractal(ComplexRectangle rect, ComplexPoint juliaPoint) throws IOException {
+
+        boolean isJulia = null != juliaPoint;
+        expandRectToFitImage(rect);
+        initializeColors();
+
+        maxIterations = maybeGuessMaxIterations(maxIterations, rect, isJulia);
+        LOG.debug("using " + maxIterations + " iterations.");
+        detectDeepZoom(rect);
+
+        calculator = new FractalCalculator(rect, maxIterations, imageWidth, imageHeight, getCurrentColorMap(),
+                juliaPoint);
+        BufferedImage image = calculator.calcFractal();
+
+        findNewRect(image);
+
+        // fast method to write to a file with default options
+        // ImageIO.write((BufferedImage)(image), "jpg", new File("fractal-" + counter++ + ".jpg"));
+
+        // create image in memory
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(200*1024);
+        ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
+        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName( "jpg" );
+        ImageWriter imageWriter = writers.next();
+
+        JPEGImageWriteParam params = new JPEGImageWriteParam( Locale.getDefault() );
+        params.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
+        params.setCompressionQuality( 0.9f );
+
+        imageWriter.setOutput( ios );
+        imageWriter.write( null, new IIOImage( image, null, null ), params );
+        ios.close();
+
+        // write memory block to a file
+        // String fileName = String.format(pattern, counter++);
+        // FileOutputStream outputStream = new FileOutputStream (fileName);
+        // bos.writeTo(outputStream);
+        // bos.close();
+        // outputStream.close();
+
+        return bos;
+    }
+
+    protected int[] getCurrentColorMap() {
+        return colorTable.get(getColor());
+    }
+
+    protected String getColor() {
+        return color;
+    }
+
+    protected void expandRectToFitImage(ComplexRectangle complexRect) {
+        // The complex rectangle must be scaled to fit the pixel image view.
+        // Method: compare the width/height ratios of the two rectangles.
+        double imageWHRatio = 1.0;
+        double complexWHRatio = 1.0;
+        double iMin = complexRect.getIMin();
+        double iMax = complexRect.getIMax();
+        double rMin = complexRect.getRMin();
+        double rMax = complexRect.getRMax();
+        double complexWidth = rMax - rMin;
+        double complexHeight = iMax - iMin;
+
+        if ((imageWidth != 0) && (imageHeight != 0)) {
+            imageWHRatio = ((double) imageWidth / (double) imageHeight);
+        } else
+            return;
+
+        if ((complexWidth != 0) && (complexHeight != 0)) {
+            complexWHRatio = complexWidth / complexHeight;
+        } else
+            return;
+
+        if (imageWHRatio == complexWHRatio)
+            return;
+
+        if (imageWHRatio < complexWHRatio) {
+            // Expand vertically
+            double newHeight = complexWidth / imageWHRatio;
+            double heightDifference = Math.abs(newHeight - complexHeight);
+            iMin = iMin - heightDifference / 2;
+            iMax = iMax + heightDifference / 2;
+        } else {
+            // Expand horizontally
+            double newWidth = complexHeight * imageWHRatio;
+            double widthDifference = Math.abs(newWidth - complexWidth);
+            rMin = rMin - widthDifference / 2;
+            rMax = rMax + widthDifference / 2;
+        }
+        complexRect.set(rMin, rMax, iMin, iMax);
+    }
+
+    private int guessNewMaxIterations(ComplexRectangle cr, boolean isJulia) {
+        // The higher the zoom factor, the more iterations that are needed to
+        // see
+        // the detail. Guess at a number to produce a cool looking fractal:
+        double zoom = INITIAL_RECT.getWidth() / cr.getWidth();
+        if (zoom < 1.0) {
+            zoom = 1.0; // forces logZoom >= 0
+        }
+        double logZoom = Math.log(zoom);
+        double magnitude = (logZoom / 2.3) - 2.0; // just a guess.
+        if (magnitude < 1.0) {
+            magnitude = 1.0;
+        }
+        double iterations = INITIAL_ITERATIONS * (magnitude * logZoom + 1.0);
+        if (isJulia)
+            iterations *= 2.0; // Julia sets tend to need more iterations.
+        return (int) iterations;
+    }
+
+    private int maybeGuessMaxIterations(int maxIterations, ComplexRectangle cr, boolean isJulia) {
+        // If the user did not change the number of iterations, make a guess...
+        if (previousIterations == maxIterations) {
+            maxIterations = guessNewMaxIterations(cr, isJulia);
+        }
+        previousIterations = maxIterations;
+        return maxIterations;
+    }
+
+    private boolean detectDeepZoom(ComplexRectangle cr) {
+        // "Deep Zoom" occurs when the precision provided by the Java type
+        // double
+        // runs out of resolution. The use of BigDecimal is required to fix
+        // this.
+        double deltaDiv2 = cr.getWidth() / ((imageWidth) * 2.0);
+        String min = "" + (cr.getRMin());
+        String minPlus = "" + (cr.getRMin() + deltaDiv2);
+
+        if (Double.valueOf(min).doubleValue() == Double.valueOf(minPlus).doubleValue()) {
+            LOG.warn("Deep Zoom...  Drawing resolution will be degraded ;-(");
+            return true;
+        }
+        return false;
+    }
+
+    private void initializeColors() {
+        colorTable = new HashMap<String, int[]>();
+
+        int red = 255;
+        int green = 255;
+        int blue = 255;
+
+        float hue = (float) 1.0;
+        float saturation = (float) 1.0;
+        float brightness = (float) 1.0;
+
+        // COLORS_BLACK_AND_WHITE:
+        int[] colorMap = new int[numColors];
+        for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
+            colorMap[colorNum] = Color.white.getRGB();
+        }
+        colorTable.put(COLORS_BLACK_AND_WHITE, colorMap);
+
+        // COLORS_BLUE_ICE:
+        blue = 255;
+        colorMap = new int[numColors];
+        for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((255 * (float) colorNum / numColors)) % 255;
+            green = (int) ((255 * (float) colorNum / numColors)) % 255;
+            colorMap[colorNum] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_BLUE_ICE, colorMap);
+
+        // COLORS_FUNKY:
+        colorMap = new int[numColors];
+        for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((1024 * (float) colorNum / numColors)) % 255;
+            green = (int) ((512 * (float) colorNum / numColors)) % 255;
+            blue = (int) ((256 * (float) colorNum / numColors)) % 255;
+            colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_FUNKY, colorMap);
+
+        // COLORS_PASTEL
+        brightness = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 4) / (float) numColors) % numColors;
+            saturation = ((float) (colorNum * 2) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_PASTEL, colorMap);
+
+        // COLORS_PSYCHEDELIC:
+        saturation = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 5) / (float) numColors) % numColors;
+            brightness = ((float) (colorNum * 20) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_PSYCHEDELIC, colorMap);
+
+        // COLORS_PURPLE_HAZE:
+        red = 255;
+        blue = 255;
+        colorMap = new int[numColors];
+        for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
+            green = (int) ((255 * (float) colorNum / numColors)) % 255;
+            colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_PURPLE_HAZE, colorMap);
+
+        // COLORS_RADICAL:
+        saturation = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 7) / (float) numColors) % numColors;
+            brightness = ((float) (colorNum * 49) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_RADICAL, colorMap);
+
+        // COLORS_RAINBOW:
+        saturation = (float) 1.0;
+        brightness = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = (float) colorNum / (float) numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_RAINBOW, colorMap);
+
+        // COLORS_RAINBOWS:
+        saturation = (float) 1.0;
+        brightness = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 5) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_RAINBOWS, colorMap);
+
+        // COLORS_SCINTILLATION
+        brightness = (float) 1.0;
+        saturation = (float) 1.0;
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 2) / (float) numColors) % numColors;
+            brightness = ((float) (colorNum * 5) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_SCINTILLATION, colorMap);
+
+        // COLORS_WARPED:
+        colorMap = new int[numColors];
+        for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((1024 * (float) colorNum / numColors)) % 255;
+            green = (int) ((256 * (float) colorNum / numColors)) % 255;
+            blue = (int) ((512 * (float) colorNum / numColors)) % 255;
+            colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_WARPED, colorMap);
+
+        // COLORS_WILD:
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            hue = ((float) (colorNum * 1) / (float) numColors) % numColors;
+            saturation = ((float) (colorNum * 2) / (float) numColors) % numColors;
+            brightness = ((float) (colorNum * 4) / (float) numColors) % numColors;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_WILD, colorMap);
+
+        // COLORS_ZEBRA:
+        colorMap = new int[numColors];
+        for (int colorNum = 0; colorNum < numColors; colorNum++) {
+            if (colorNum % 2 == 0) {
+                colorMap[colorNum] = Color.white.getRGB();
+                ;
+            } else {
+                colorMap[colorNum] = Color.black.getRGB();
+                ;
+            }
+        }
+        colorTable.put(COLORS_ZEBRA, colorMap);
+    }
+
+
+    private void findNewRect(BufferedImage image) {
+
+        int newWidth = image.getWidth() / parts;
+        int newHeight = image.getHeight() / parts;
+        int i=0, j=0;
+        int noTiles = (image.getWidth() / newWidth) * (image.getHeight() / newHeight); // equals parts but be aware of rounding errors!;
+        double[][] stdDev = new double [noTiles] [3];
+
+        for (int y = 0; y+newHeight <= image.getHeight(); y+=newHeight) {
+            for (int x = 0; x+newWidth <= image.getWidth(); x+=newWidth) {
+                Rectangle subRect = new Rectangle(x, y, newWidth, newHeight);
+                calcStdDev(image, subRect, stdDev[i*parts+j]);
+                ++j;
+            }
+            ++i;
+            j=0;
+        }
+
+        // find tile with greatest std deviation:
+        double max = 0;
+        int index = 0;
+        for (i=0; i<noTiles; i++) {
+            double avg = (stdDev[i][0] + stdDev[i][1] +stdDev[i][2]) / 3;
+            if (avg > max) {
+                index = i;
+                max = avg;
+            }
+        }
+        newRowTile = index / parts;
+        newColTile = index % parts;
+    }
+
+    private void calcStdDev(BufferedImage image, Rectangle rect, double[] stdDev) {
+
+        int sumR = 0, sumG = 0, sumB = 0;
+        long sumSR = 0, sumSG = 0, sumSB = 0;
+
+        for (int x = rect.x; x < rect.x+rect.width; x+=1) {
+            for (int y = rect.y; y < rect.y+rect.height; y+=1) {
+                int pixel = image.getRGB(x, y);
+                byte r, g, b, alpha;
+
+                alpha = (byte) (pixel >>> 24);
+                r = (byte) (pixel >>> 16);
+                g = (byte) (pixel >>> 8);
+                b = (byte) pixel;
+                int red = r & 0xFF;
+                int green = g & 0xFF;
+                int blue = b & 0xFF;
+                sumR +=red;
+                sumG += green;
+                sumB += blue;
+                sumSR +=red*red;
+                sumSG += green*green;
+                sumSB += blue*blue;
+            }
+        }
+        int count = rect.width * rect.height;
+        double mean = 0.0;
+
+        mean = sumR / count;
+        stdDev[0] = Math.sqrt(sumSR/count - (mean * mean));
+        mean = sumG / count;
+        stdDev[1] = Math.sqrt(Math.sqrt(sumSG/count - (mean * mean)));
+        mean = sumB / count;
+        stdDev[2] = Math.sqrt(Math.sqrt(sumSB/count - (mean * mean)));
+    }
+
+}

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsum.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsum.java?rev=1241563&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsum.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-util/src/main/java/org/apache/chemistry/opencmis/util/content/loremipsum/LoremIpsum.java Tue Feb  7 19:26:17 2012
@@ -0,0 +1,1158 @@
+/*
+ * 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.util.content.loremipsum;
+
+import java.io.IOException;
+import java.io.StringWriter;
+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.Random;
+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;
+        }
+
+        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() != WordLengthPair.class)
+                return false;
+            if (len1 == ((WordLengthPair) other).len1 && len2 == ((WordLengthPair) other).len2)
+                return true;
+            else
+                return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return len1 ^ len2;
+        }
+    }
+
+    /**
+     * Delimiters that end sentences.
+     * 
+     * @type {Array.<string>}
+     * @private
+     */
+    private static final String DELIMITERS_SENTENCES[] = { ".", "?", "!" };
+
+    /**
+     * Regular expression for splitting a text into sentences.
+     * 
+     * @type {RegExp}
+     * @private
+     */
+    private static final String SENTENCE_SPLIT_REGEX = "[\\.\\?\\!]";
+
+    /**
+     * Delimiters that end words.
+     * 
+     * @type {Array.<string>}
+     * @private
+     */
+    private static final String DELIMITERS_WORDS[] = { ".", ",", "?", "!" };
+
+    /**
+     * Regular expression for splitting text into words.
+     * 
+     * @type {RegExp}
+     * @private
+     */
+    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.
+     * 
+     * @type {goog.structs.Map}
+     * @private
+     */
+    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.).
+     * 
+     * @type {goog.structs.Map}
+     * @private
+     */
+    private Map<WordLengthPair, List<WordInfo>> chains;
+
+    /**
+     * Pairs of word-lengths that can appear at the beginning of sentences.
+     * 
+     * @type {Array}
+     */
+    private List<WordLengthPair> starts;
+
+    /**
+     * Average sentence length in words.
+     * 
+     * @type {number}
+     * @private
+     */
+    private double sentenceMean;
+
+    /**
+     * Sigma (sqrt of Objectiance) for the sentence length in words.
+     * 
+     * @type {number}
+     * @private
+     */
+    private double sentenceSigma;
+
+    /**
+     * Average paragraph length in sentences.
+     * 
+     * @type {number}
+     * @private
+     */
+    private double paragraphMean;
+
+    /**
+     * Sigma (sqrt of variance) for the paragraph length in sentences.
+     * 
+     * @type {number}
+     * @private
+     */
+    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} 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 Random randomGenerator = new Random();
+
+    /**
+     * Generates random strings of "lorem ipsum" text, based on the word
+     * distribution of a sample text, using the words in a dictionary.
+     * 
+     * @constructor
+     */
+    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[] dictionary) {
+        this.sample = sample;
+        this.dictionary = null;
+        generateChains(this.sample);
+        generateStatistics(this.sample);
+        initializeDictionary(dictionary);
+    };
+
+    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 {boolean} opt_startWithLorem Whether to start the sentence with
+     *        the standard "Lorem ipsum..." first sentence.
+     * @return {string} The generated sentence.
+     */
+    public String generateParagraph(boolean opt_startWithLorem) {
+        // 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 = opt_startWithLorem;
+        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 {boolean} opt_startWithLorem Whether to start the setnence with
+     *        the standard "Lorem ipsum..." first sentence.
+     * @return {string} The generated sentence.
+     */
+    public String generateSentence(boolean opt_startWithLorem) {
+        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 (opt_startWithLorem) {
+            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 {string} sample The same text.
+     * @private
+     */
+    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.size() > 0) {
+            this.chains = chains;
+            this.starts = starts;
+        } else {
+            throw new RuntimeException("Invalid sample text.");
+        }
+
+    }
+
+    /**
+     * Calculates the mean and standard deviation of sentence and paragraph
+     * lengths.
+     * 
+     * @param {string} sample The same text.
+     * @private
+     */
+    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 {string} sample The same text.
+     * @private
+     */
+    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 {string} sample The same text.
+     * @private
+     */
+    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 {string} 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
+     */
+    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 {string} text The text to split.
+     * @return {Array.<string>} An array of paragraphs.
+     * @private
+     */
+
+    static String[] splitParagraphs(String text) {
+        return filterNotEmptyOrWhiteSpace(text.split("\n")).toArray(new String[0]);
+    }
+
+    /**
+     * Splits a piece of text into sentences.
+     * 
+     * @param {string} text The text to split.
+     * @return {Array.<string>} An array of sentences.
+     * @private
+     */
+    static String[] splitSentences(String text) {
+        return filterNotEmptyOrWhiteSpace(text.split(SENTENCE_SPLIT_REGEX)).toArray(new String[0]);
+    }
+
+    /**
+     * Splits a piece of text into words..
+     * 
+     * @param {string} text The text to split.
+     * @return {Array.<string>} An array of words.
+     * @private
+     */
+    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 {Array.<number>} values The values.
+     * @param {number} target The target value.
+     * @return {number} 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 {string} word The word to check.
+     * @return {Array} 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 private 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;
+    }
+
+    static private 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_}.
+     * 
+     * @type {number}
+     * @private
+     */
+    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 {number} mu The mean of the distribution.
+     * @param {number} sigma The sigma of the distribution.
+     * @private
+     */
+    private static double randomNormal(double mu, double sigma) {
+        double z = 0.0d;
+        while (true) {
+            double u1 = Math.random();
+            double u2 = 1.0d - Math.random();
+            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 {string} text the text to check.
+     * @return {boolean} Whether the text is neither empty nor whitespace.
+     * @private
+     */
+    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) / ((double) (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] = (double) 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.
+     * 
+     * @type {string}
+     * @private
+     */
+    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.
+     * 
+     * @type {string}
+     * @private
+     */
+    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
+     * pararaph tags.
+     * 
+     * @param quantity
+     * @param startWithLorem
+     * @return
+     */
+    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
+     * pararaph tags.
+     * 
+     * @param writer
+     * @param quantity
+     * @param startWithLorem
+     * @throws IOException 
+     */
+    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 pararaph tags.
+     * 
+     * @param quantity
+     * @param startWithLorem
+     * @return
+     */
+    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.
+     * 
+     * @param quantity
+     * @param startWithLorem
+     * @return
+     */
+    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.
+     * 
+     * @param writer
+     * @param quantity
+     * @param startWithLorem
+     * @throws IOException 
+     */
+    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.
+     * 
+     * @param quantity
+     * @param startWithLorem
+     * @return
+     */
+    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.
+     * 
+     * @param writer
+     * @param quantity
+     * @param startWithLorem
+     * @throws IOException 
+     */
+    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
+     * 
+     * @param quantity
+     * @param startWithLorem
+     * @return
+     */
+    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
+     * 
+     * @param writer
+     * @param quantity
+     * @param startWithLorem
+     * @throws IOException 
+     */
+    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;
+            }
+        }
+    }
+
+}