chemistry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j...@apache.org
Subject svn commit: r1757477 [1/2] - in /chemistry/opencmis/trunk: ./ chemistry-opencmis-server/chemistry-opencmis-server-inmemory/ chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ chemi...
Date Wed, 24 Aug 2016 08:59:22 GMT
Author: jens
Date: Wed Aug 24 08:59:21 2016
New Revision: 1757477

URL: http://svn.apache.org/viewvc?rev=1757477&view=rev
Log:
Add content generating code to InMemory sources to make test-util module obsolete

Added:
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ObjectGenerator.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexPoint.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexRectangle.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalCalculator.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalGenerator.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/loremipsum/
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/loremipsum/LoremIpsum.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/content/
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/content/loremipsum/
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/content/loremipsum/LoremIpsumTest.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/resources/HaenselUndGretel.txt
Modified:
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/pom.xml
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/server/InMemoryServiceFactoryImpl.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/DiscoveryServiceTest.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/NavigationServiceTest.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/test/java/org/apache/chemistry/opencmis/inmemory/ObjectServiceTest.java
    chemistry/opencmis/trunk/pom.xml

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/pom.xml
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/pom.xml?rev=1757477&r1=1757476&r2=1757477&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/pom.xml (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/pom.xml Wed Aug 24 08:59:21 2016
@@ -93,11 +93,6 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>chemistry-opencmis-test-util</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>${project.groupId}</groupId>
             <artifactId>chemistry-opencmis-server-support</artifactId>
             <version>${project.version}</version>
         </dependency>

Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ObjectGenerator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ObjectGenerator.java?rev=1757477&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ObjectGenerator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/ObjectGenerator.java Wed Aug 24 08:59:21 2016
@@ -0,0 +1,667 @@
+/*
+ * 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.chemistry.opencmis.commons.PropertyIds;
+import org.apache.chemistry.opencmis.commons.data.Acl;
+import org.apache.chemistry.opencmis.commons.data.ContentStream;
+import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
+import org.apache.chemistry.opencmis.commons.data.ObjectData;
+import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
+import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
+import org.apache.chemistry.opencmis.commons.data.Properties;
+import org.apache.chemistry.opencmis.commons.data.PropertyData;
+import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
+import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
+import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
+import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
+import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
+import org.apache.chemistry.opencmis.commons.enums.VersioningState;
+import org.apache.chemistry.opencmis.commons.impl.IOUtils;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
+import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory;
+import org.apache.chemistry.opencmis.commons.spi.NavigationService;
+import org.apache.chemistry.opencmis.commons.spi.ObjectService;
+import org.apache.chemistry.opencmis.commons.spi.RepositoryService;
+import org.apache.chemistry.opencmis.inmemory.content.fractal.FractalGenerator;
+import org.apache.chemistry.opencmis.inmemory.content.loremipsum.LoremIpsum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple helper class for the tests that generates a sample folder hierarchy
+ * and optionally documents in it.
+ */
+public class ObjectGenerator {
+
+    private static final int KILO = 1024;
+    private static final Logger LOG = LoggerFactory.getLogger(ObjectGenerator.class);
+    private final BindingsObjectFactory fFactory;
+    NavigationService fNavSvc;
+    ObjectService fObjSvc;
+    RepositoryService fRepSvc;
+    private final String fRepositoryId;
+    private boolean fCleanup;
+    List<String> fTopLevelDocsCreated; // list of ids created on first level
+    List<String> fTopLevelFoldersCreated; // list of ids created on first level
+
+    /**
+     * supported kinds of content
+     * 
+     */
+    public enum ContentKind {
+        STATIC_TEXT, LOREM_IPSUM_TEXT, LOREM_IPSUM_HTML, IMAGE_FRACTAL_JPEG
+    }
+
+    /**
+     * Indicates if / how many documents are created in each folder
+     */
+    private int fNoDocumentsToCreate;
+
+    /**
+     * The type id of the document id that is created.
+     */
+    private String fDocTypeId = BaseTypeId.CMIS_DOCUMENT.value();
+
+    /**
+     * The type id of the folder that is created.
+     */
+    private String fFolderTypeId = BaseTypeId.CMIS_FOLDER.value();
+
+    /**
+     * A list of property ids. For each element in this list a String property
+     * value is created for each creation of a document. All ids must be valid
+     * string property id of the type fDocTypeId
+     */
+    private List<String> fStringPropertyIdsToSetForDocument;
+
+    /**
+     * A list of property ids. For each element in this list a String property
+     * value is created for each creation of a folder. All ids must be valid
+     * string property id of the type fFolderTypeId
+     */
+    private List<String> fStringPropertyIdsToSetForFolder;
+
+    /**
+     * number of documents created in total
+     */
+    private int fDocumentsInTotalCount = 0;
+
+    /**
+     * number of folders created in total
+     */
+    private int fFoldersInTotalCount = 0;
+
+    /**
+     * size of content in KB, if 0 create documents without content
+     */
+    private int fContentSizeInK = 0;
+
+    /**
+     * Kind of content to create
+     */
+    private ContentKind fContentKind;
+
+    private static final String NAMEPROPVALPREFIXDOC = "My_Document-";
+    private static final String NAMEPROPVALPREFIXFOLDER = "My_Folder-";
+    private static final String STRINGPROPVALPREFIXDOC = "My Doc StringProperty ";
+    private static final String STRINGPROPVALPREFIXFOLDER = "My Folder StringProperty ";
+    private int propValCounterDocString = 0;
+    private int propValCounterFolderString = 0;
+    /**
+     * use UUIDs to generate folder and document names
+     */
+    private boolean fUseUuids;
+
+    /**
+     * generator for images
+     */
+    private FractalGenerator fractalGenerator = null;
+
+    public ObjectGenerator(BindingsObjectFactory factory, NavigationService navSvc, ObjectService objSvc,
+            RepositoryService repSvc, String repositoryId, ContentKind contentKind) {
+        super();
+        fFactory = factory;
+        fNavSvc = navSvc;
+        fObjSvc = objSvc;
+        fRepSvc = repSvc;
+        fRepositoryId = repositoryId;
+        // create an empty list of properties to generate by default for folder
+        // and document
+        fStringPropertyIdsToSetForDocument = new ArrayList<String>();
+        fStringPropertyIdsToSetForFolder = new ArrayList<String>();
+        fNoDocumentsToCreate = 0;
+        fUseUuids = false;
+        fCleanup = false;
+        fTopLevelDocsCreated = new ArrayList<String>();
+        fTopLevelFoldersCreated = new ArrayList<String>();
+        fContentKind = contentKind;
+    }
+
+    public void setNumberOfDocumentsToCreatePerFolder(int noDocumentsToCreate) {
+        fNoDocumentsToCreate = noDocumentsToCreate;
+    }
+
+    public void setFolderTypeId(String folderTypeId) {
+        fFolderTypeId = folderTypeId;
+    }
+
+    public void setDocumentTypeId(String docTypeId) {
+        fDocTypeId = docTypeId;
+    }
+
+    public void setDocumentPropertiesToGenerate(List<String> propertyIds) {
+        fStringPropertyIdsToSetForDocument = propertyIds;
+    }
+
+    public void setFolderPropertiesToGenerate(List<String> propertyIds) {
+        fStringPropertyIdsToSetForFolder = propertyIds;
+    }
+
+    public void setContentSizeInKB(int sizeInK) {
+        fContentSizeInK = sizeInK;
+    }
+
+    public ContentKind getContentKind() {
+        return fContentKind;
+    }
+
+    public void setLoreIpsumGenerator(ContentKind contentKind) {
+        fContentKind = contentKind;
+    }
+
+    public void setCleanUpAfterCreate(boolean doCleanup) {
+        fCleanup = doCleanup;
+    }
+
+    public void createFolderHierachy(int levels, int childrenPerLevel, String rootFolderId) {
+        resetCounters();
+        fTopLevelFoldersCreated.clear();
+        fTopLevelDocsCreated.clear();
+        createFolderHierachy(rootFolderId, 0, levels, childrenPerLevel);
+        if (fCleanup) {
+            deleteTree();
+        }
+    }
+
+    public void setUseUuidsForNames(boolean useUuids) {
+        /**
+         * use UUIDs to generate folder and document names
+         */
+        fUseUuids = useUuids;
+    }
+
+    /**
+     * retrieve the index-th folder from given level of the hierarchy starting
+     * at rootId
+     * 
+     * @param rootId
+     * @param level
+     * @param index
+     * @return
+     */
+    public String getFolderId(String rootId, int level, int index) {
+        String objectId = rootId;
+        final String requiredProperties = PropertyIds.OBJECT_ID + "," + PropertyIds.OBJECT_TYPE_ID + ","
+                + PropertyIds.BASE_TYPE_ID;
+        // Note: This works because first folders are created then documents
+        for (int i = 0; i < level; i++) {
+            ObjectInFolderList result = fNavSvc.getChildren(fRepositoryId, objectId, requiredProperties,
+                    PropertyIds.OBJECT_TYPE_ID, false, IncludeRelationships.NONE, null, true, BigInteger.valueOf(-1),
+                    BigInteger.valueOf(-1), null);
+            List<ObjectInFolderData> children = result.getObjects();
+            ObjectData child = children.get(index).getObject();
+            objectId = (String) child.getProperties().getProperties().get(PropertyIds.OBJECT_ID).getFirstValue();
+        }
+        return objectId;
+    }
+
+    /**
+     * retrieve the index-th document from given folder
+     * 
+     * @param folderId
+     *            folder to retrieve document from
+     * @param index
+     *            index of document to retrieve from this folder
+     * @return
+     */
+    public String getDocumentId(String folderId, int index) {
+        String docId = null;
+        final String requiredProperties = PropertyIds.OBJECT_ID + "," + PropertyIds.OBJECT_TYPE_ID + ","
+                + PropertyIds.BASE_TYPE_ID;
+        ObjectInFolderList result = fNavSvc.getChildren(fRepositoryId, folderId, requiredProperties,
+                PropertyIds.OBJECT_TYPE_ID, false, IncludeRelationships.NONE, null, true, BigInteger.valueOf(-1),
+                BigInteger.valueOf(-1), null);
+        List<ObjectInFolderData> children = result.getObjects();
+        int numDocsFound = 0;
+        for (int i = 0; i < children.size(); i++) {
+            ObjectData child = children.get(i).getObject();
+            docId = (String) child.getProperties().getProperties().get(PropertyIds.OBJECT_ID).getFirstValue();
+            if (child.getBaseTypeId().equals(BaseTypeId.CMIS_DOCUMENT)) {
+                if (numDocsFound == index) {
+                    return docId;
+                } else {
+                    numDocsFound++;
+                }
+            }
+        }
+        return docId;
+    }
+
+    /**
+     * return the total number of documents created
+     * 
+     * @return
+     */
+    public int getDocumentsInTotal() {
+        return fDocumentsInTotalCount;
+    }
+
+    /**
+     * return the total number of folders created
+     * 
+     * @return
+     */
+    public int getFoldersInTotal() {
+        return fFoldersInTotalCount;
+    }
+
+    /**
+     * return the total number of objects created
+     */
+    public int getObjectsInTotal() {
+        return fDocumentsInTotalCount + fFoldersInTotalCount;
+    }
+
+    public String createSingleDocument(String folderId) {
+        String objectId = createDocument(folderId, 0, 0);
+        if (fCleanup) {
+            deleteObject(objectId);
+        }
+        return objectId;
+    }
+
+    public String[] createDocuments(String folderId, int count) {
+        String[] result;
+
+        for (int i = 0; i < count; i++) {
+            String id = createDocument(folderId, 0, 0);
+            fTopLevelDocsCreated.add(id);
+        }
+        if (fCleanup) {
+            deleteTree();
+            result = null;
+        } else {
+            result = new String[count];
+            for (int i = 0; i < fTopLevelDocsCreated.size(); i++) {
+                result[i] = fTopLevelDocsCreated.get(i);
+            }
+        }
+        return result;
+    }
+
+    public String[] createFolders(String folderId, int count) {
+        String[] result;
+
+        for (int i = 0; i < count; i++) {
+            createFolder(folderId);
+        }
+        if (fCleanup) {
+            deleteTree();
+            result = null;
+        } else {
+            result = new String[count];
+            for (int i = 0; i < fTopLevelFoldersCreated.size(); i++) {
+                result[i] = fTopLevelFoldersCreated.get(i);
+            }
+        }
+        return result;
+    }
+
+    public void resetCounters() {
+        fDocumentsInTotalCount = 0;
+        fFoldersInTotalCount = 0;
+    }
+
+    private void createFolderHierachy(String parentId, int level, int levels, int childrenPerLevel) {
+        String id = null;
+
+        if (level >= levels) {
+            return;
+        }
+
+        LOG.debug(" create folder for parent id: " + parentId + ", in level " + level + ", max levels " + levels);
+
+        for (int i = 0; i < childrenPerLevel; i++) {
+            Properties props = createFolderProperties(i, level);
+            id = fObjSvc.createFolder(fRepositoryId, props, parentId, null, null, null, null);
+            if (level == 0) {
+            	fTopLevelFoldersCreated.add(id);
+            }
+
+            if (id != null) {
+                ++fFoldersInTotalCount;
+                createFolderHierachy(id, level + 1, levels, childrenPerLevel);
+            }
+        }
+        for (int j = 0; j < fNoDocumentsToCreate; j++) {
+            id = createDocument(parentId, j, level);
+            if (level == 0) {
+                fTopLevelDocsCreated.add(id);
+            }
+        }
+    }
+
+    private String createFolder(String parentId) {
+        Properties props = createFolderProperties(0, 0);
+        String id = null;
+        id = fObjSvc.createFolder(fRepositoryId, props, parentId, null, null, null, null);
+        fTopLevelFoldersCreated.add(id);
+        return id;
+    }
+
+    private String createDocument(String folderId, int no, int level) {
+        ContentStream contentStream = null;
+        VersioningState versioningState = VersioningState.NONE;
+        List<String> policies = null;
+        Acl addACEs = null;
+        Acl removeACEs = null;
+        ExtensionsData extension = null;
+
+        LOG.debug("create document in folder " + folderId);
+        Properties props = createDocumentProperties(no, level);
+        String id = null;
+
+        if (fContentSizeInK > 0) {
+            switch (fContentKind) {
+            case STATIC_TEXT:
+                contentStream = createContentStaticText();
+                break;
+            case LOREM_IPSUM_TEXT:
+                contentStream = createContentLoremIpsumText();
+                break;
+            case LOREM_IPSUM_HTML:
+                contentStream = createContentLoremIpsumHtml();
+                break;
+            case IMAGE_FRACTAL_JPEG:
+                contentStream = createContentFractalimageJpeg();
+                break;
+            }
+        }
+
+        id = fObjSvc.createDocument(fRepositoryId, props, folderId, contentStream, versioningState, policies,
+        		addACEs, removeACEs, extension);
+
+        if (null == id) {
+            LOG.error("createDocument failed.");
+        }
+        ++fDocumentsInTotalCount;
+        return id;
+    }
+
+    private void deleteTree() {
+
+        // delete all documents from first level
+        for (String id : fTopLevelDocsCreated) {
+            deleteObject(id);
+        }
+
+        // delete recursively all folders from first level
+        for (String id : fTopLevelFoldersCreated) {
+        	fObjSvc.deleteTree(fRepositoryId, id, true, UnfileObject.DELETE, true, null);
+        }
+    }
+
+    private void deleteObject(String objectId) {
+    	fObjSvc.deleteObject(fRepositoryId, objectId, true, null);
+    }
+
+    public ContentStream createContentLoremIpsumHtml() {
+        ContentStreamImpl content = new ContentStreamImpl();
+        content.setFileName("data.html");
+        content.setMimeType("text/html");
+        int len = fContentSizeInK * KILO; // size of document in K
+
+        LoremIpsum ipsum = new LoremIpsum();
+        String text = ipsum.generateParagraphsFullHtml(len, true);
+
+        content.setStream(new ByteArrayInputStream(IOUtils.toUTF8Bytes(text)));
+        return content;
+    }
+
+    public ContentStream createContentLoremIpsumText() {
+        ContentStreamImpl content = new ContentStreamImpl();
+        content.setFileName("data.txt");
+        content.setMimeType("text/plain");
+        int len = fContentSizeInK * 1024; // size of document in K
+
+        LoremIpsum ipsum = new LoremIpsum();
+        String text = ipsum.generateParagraphsPlainText(len, 80, true);
+        content.setStream(new ByteArrayInputStream(IOUtils.toUTF8Bytes(text)));
+        return content;
+    }
+
+    public ContentStream createContentStaticText() {
+        ContentStreamImpl content = new ContentStreamImpl();
+        content.setFileName("data.txt");
+        content.setMimeType("text/plain");
+        int len = fContentSizeInK * 1024; // size of document in K
+        byte[] b = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x0c, 0x0a,
+                0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x0c, 0x0a }; // 32
+        // Bytes
+        ByteArrayOutputStream ba = new ByteArrayOutputStream(len);
+        try {
+            for (int j = 0; j < fContentSizeInK; j++) {
+                // write 1K of data
+                for (int i = 0; i < 32; i++) {
+                    ba.write(b);
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to fill content stream with data", e);
+        }
+        content.setStream(new ByteArrayInputStream(ba.toByteArray()));
+        return content;
+    }
+
+    public ContentStream createContentFractalimageJpeg() {
+        if (null == fractalGenerator) {
+            fractalGenerator = new FractalGenerator();
+        }
+
+        ContentStreamImpl content = null;
+
+        try {
+            ByteArrayOutputStream bos = fractalGenerator.generateFractal();
+            content = new ContentStreamImpl();
+            content.setFileName("image.jpg");
+            content.setMimeType("image/jpeg");
+            content.setStream(new ByteArrayInputStream(bos.toByteArray()));
+            bos.close();
+        } catch (IOException e) {
+            System.err.println("Error when generating fractal image: " + e);
+            e.printStackTrace();
+        }
+
+        return content;
+    }
+
+    private Properties createFolderProperties(int no, int level) {
+        List<PropertyData<?>> properties = new ArrayList<PropertyData<?>>();
+        properties.add(fFactory.createPropertyStringData(PropertyIds.NAME, generateFolderNameValue(no, level)));
+        properties.add(fFactory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, fFolderTypeId));
+        // Generate some property values for custom attributes
+        for (String stringPropId : fStringPropertyIdsToSetForFolder) {
+            properties.add(fFactory.createPropertyStringData(stringPropId, generateStringPropValueFolder()));
+        }
+        Properties props = fFactory.createPropertiesData(properties);
+        return props;
+    }
+
+    private Properties createDocumentProperties(int no, int level) {
+        List<PropertyData<?>> properties = new ArrayList<PropertyData<?>>();
+        properties.add(fFactory.createPropertyStringData(PropertyIds.NAME, generateDocNameValue(no, level)));
+        properties.add(fFactory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, fDocTypeId));
+        // Generate some property values for custom attributes
+        for (String stringPropId : fStringPropertyIdsToSetForDocument) {
+            properties.add(fFactory.createPropertyStringData(stringPropId, generateStringPropValueDoc()));
+        }
+        Properties props = fFactory.createPropertiesData(properties);
+        return props;
+    }
+
+    private synchronized int incrementPropCounterDocStringProp() {
+        return propValCounterDocString++;
+    }
+
+    private synchronized int incrementPropCounterFolderStringProp() {
+        return propValCounterFolderString++;
+    }
+
+    private String generateDocNameValue(int no, int level) {
+        if (fUseUuids) {
+            return UUID.randomUUID().toString();
+        } else {
+            return NAMEPROPVALPREFIXDOC + level + "-" + no;
+        }
+    }
+
+    private String generateFolderNameValue(int no, int level) {
+        if (fUseUuids) {
+            return UUID.randomUUID().toString();
+        } else {
+            return NAMEPROPVALPREFIXFOLDER + level + "-" + no;
+        }
+    }
+
+    private String generateStringPropValueDoc() {
+        return STRINGPROPVALPREFIXDOC + incrementPropCounterDocStringProp();
+    }
+
+    private String generateStringPropValueFolder() {
+        return STRINGPROPVALPREFIXFOLDER + incrementPropCounterFolderStringProp();
+    }
+
+    public void dumpFolder(String folderId, String propertyFilter) {
+        LOG.debug("starting dumpFolder() id " + folderId + " ...");
+        boolean allRequiredPropertiesArePresent = propertyFilter != null && propertyFilter.equals("*"); // can
+        // be
+        // optimized
+        final String requiredProperties = allRequiredPropertiesArePresent ? propertyFilter : PropertyIds.OBJECT_ID
+                + "," + PropertyIds.NAME + "," + PropertyIds.OBJECT_TYPE_ID + "," + PropertyIds.BASE_TYPE_ID;
+        // if all required properties are contained in the filter use we use the
+        // filter otherwise
+        // we use our own set and get those from the filter later in an extra
+        // call
+        String propertyFilterIntern = allRequiredPropertiesArePresent ? propertyFilter : requiredProperties;
+        dumpFolder(folderId, propertyFilterIntern, 0);
+    }
+
+    private void dumpFolder(String folderId, String propertyFilter, int depth) {
+        boolean allRequiredPropertiesArePresent = propertyFilter.equals("*"); // can
+        // be
+        // optimized
+        StringBuilder prefix = new StringBuilder();
+        for (int i = 0; i < depth; i++) {
+            prefix.append("   ");
+        }
+
+        ObjectInFolderList result = fNavSvc.getChildren(fRepositoryId, folderId, propertyFilter, null, false,
+                IncludeRelationships.NONE, null, true, BigInteger.valueOf(-1), BigInteger.valueOf(-1), null);
+        List<ObjectInFolderData> folders = result.getObjects();
+        if (null != folders) {
+            LOG.debug(prefix + "found " + folders.size() + " children in folder " + folderId);
+            int no = 0;
+            for (ObjectInFolderData folder : folders) {
+                LOG.debug(prefix.toString() + ++no + ": found object with id: " + folder.getObject().getId()
+                        + " and path segment: " + folder.getPathSegment());
+                dumpObjectProperties(folder.getObject(), depth, propertyFilter, !allRequiredPropertiesArePresent);
+                String objectTypeBaseId = folder.getObject().getBaseTypeId().value();
+                if (objectTypeBaseId.equals(BaseTypeId.CMIS_FOLDER.value())) {
+                    dumpFolder(folder.getObject().getId(), propertyFilter, depth + 1);
+                } else if (objectTypeBaseId.equals(BaseTypeId.CMIS_DOCUMENT.value())) {
+                    dumpObjectProperties(folder.getObject(), depth + 1, propertyFilter,
+                            !allRequiredPropertiesArePresent);
+                }
+            }
+        }
+        LOG.debug(""); // add empty line
+    }
+
+    private void dumpObjectProperties(ObjectData object, int depth, String propertyFilter, boolean mustFetchProperties) {
+        final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+        StringBuilder prefix = new StringBuilder();
+        for (int i = 0; i < depth; i++) {
+            prefix.append("   ");
+        }
+
+        LOG.debug(prefix + "found object id " + object.getId());
+        Map<String, PropertyData<?>> propMap;
+        if (mustFetchProperties) {
+            String objId = (String) object.getProperties().getProperties().get(PropertyIds.OBJECT_ID).getFirstValue();
+            Properties props = fObjSvc.getProperties(fRepositoryId, objId, propertyFilter, null);
+            propMap = props.getProperties();
+        } else {
+            propMap = object.getProperties().getProperties();
+        }
+        StringBuilder valueStr = new StringBuilder("[");
+        for (Map.Entry<String, PropertyData<?>> entry : propMap.entrySet()) {
+            if (entry.getValue().getValues().size() > 1) {
+                if (entry.getValue().getFirstValue() instanceof GregorianCalendar) {
+                    for (Object obj : entry.getValue().getValues()) {
+                        GregorianCalendar cal = (GregorianCalendar) obj;
+                        valueStr.append(df.format(cal.getTime())).append(", ");
+                    }
+                    valueStr.append("]");
+                } else {
+                    valueStr = new StringBuilder(entry.getValue().getValues().toString());
+                }
+            } else {
+                Object value = entry.getValue().getFirstValue();
+                if (null != value) {
+                    valueStr = new StringBuilder(value.toString());
+                    if (value instanceof GregorianCalendar) {
+                        valueStr = new StringBuilder(df.format(((GregorianCalendar) entry.getValue().getFirstValue())
+                                .getTime()));
+                    }
+                }
+            }
+            LOG.debug(prefix + entry.getKey() + ": " + valueStr);
+        }
+        LOG.debug(""); // add empty line
+    }
+
+    public void createTypes(TypeDefinitionList typeDefList) {
+        for (TypeDefinition td : typeDefList.getList()) {
+            // TODO: enable this if available!
+            // fRepSvc.createTypeDefinition(fRepositoryId, td);
+        }
+    }
+}

Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexPoint.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexPoint.java?rev=1757477&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexPoint.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexPoint.java Wed Aug 24 08:59:21 2016
@@ -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.inmemory.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-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexRectangle.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexRectangle.java?rev=1757477&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexRectangle.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/ComplexRectangle.java Wed Aug 24 08:59:21 2016
@@ -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.inmemory.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-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalCalculator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalCalculator.java?rev=1757477&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalCalculator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalCalculator.java Wed Aug 24 08:59:21 2016
@@ -0,0 +1,165 @@
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.fractal;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+
+final class FractalCalculator {
+    private int[] colorMap;
+    protected int[][] noIterations;
+    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 = Arrays.copyOf(colMap, colMap.length);
+        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 int[][] calcFractal() {
+        noIterations = new int[ imageWidth ][ imageHeight ];
+
+        // For each pixel...
+        for (int x = 0; x < imageWidth; x++) {
+            for (int y = 0; y < imageHeight; y++) {
+                double zR = rRangeMin + x * delta;
+                double zI = iRangeMin + (imageHeight - y) * delta;
+
+                // Is the point inside the set?
+                if (useJulia) {
+                    noIterations[x][y] = testPointJuliaSet(zR, zI, maxIterations);
+                } else {
+                    noIterations[x][y] = testPointMandelbrot(zR, zI, maxIterations);
+                }            
+            }
+        }
+        return noIterations;
+    }
+
+    public BufferedImage mapItersToColors(int[][] iterations) {
+
+        // 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(iterations[x][y]);
+                image.setRGB(x, y, color);
+            }
+        }
+        return image;
+    }
+
+    protected int getColor(int numIterations) {
+        int c = Color.black.getRGB();
+
+        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-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalGenerator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalGenerator.java?rev=1757477&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalGenerator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-inmemory/src/main/java/org/apache/chemistry/opencmis/inmemory/content/fractal/FractalGenerator.java Wed Aug 24 08:59:21 2016
@@ -0,0 +1,484 @@
+////////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.inmemory.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.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FractalGenerator {
+    private static final Logger LOG = LoggerFactory.getLogger(FractalGenerator.class);
+
+    private static final int ZOOM_STEPS_PER_BATCH = 10;
+    private static final int DEFAULT_MAX_ITERATIONS = 33;
+    private static final ComplexRectangle INITIAL_RECT = new ComplexRectangle(-2.1, 1.1, -1.3, 1.3);
+    private static final ComplexRectangle INITIAL_JULIA_RECT = new ComplexRectangle(-2.0, 2.0, -2.0, 2.0);
+    private static final int INITIAL_ITERATIONS = 33;
+
+    // Color:
+    private Map<String, int[]> colorTable;
+    private static final String COLORS_BLACK_AND_WHITE = "black & white";
+    private static final String COLORS_BLUE_ICE = "blue ice";
+    private static final String COLORS_FUNKY = "funky";
+    private static final String COLORS_PASTEL = "pastel";
+    private static final String COLORS_PSYCHEDELIC = "psychedelic";
+    private static final String COLORS_PURPLE_HAZE = "purple haze";
+    private static final String COLORS_RADICAL = "radical";
+    private static final String COLORS_RAINBOW = "rainbow";
+    private static final String COLORS_RAINBOWS = "rainbows";
+    private static final String COLORS_SCINTILLATION = "scintillation";
+    private static final String COLORS_WARPED = "warped";
+    private static final String COLORS_WILD = "wild";
+    private static final String COLORS_ZEBRA = "zebra";
+    private static 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 static final int IMAGE_HEIGHT = 512; // default
+    private static final int IMAGE_WIDTH = 512; // default
+    private static final int NUM_COLORS = 512; // colors per colormap
+    private FractalCalculator calculator;
+    private int previousIterations = 1;
+    private int maxIterations;
+    private String color;
+    private int newRowTile, newColTile;
+    private int parts = 16;
+    private int stepInBatch = 0;
+    private ComplexRectangle rect;
+    private 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;
+
+        SecureRandom ran = new SecureRandom();
+        color = colorSchemes[ran.nextInt(colorSchemes.length)];
+        parts = ran.nextInt(13) + 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);
+        randomizeRect(rect);
+        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, IMAGE_WIDTH, IMAGE_HEIGHT, getCurrentColorMap(),
+                juliaPoint);
+        int[][] iterations = calculator.calcFractal();
+        BufferedImage image = calculator.mapItersToColors(iterations);
+        findNewRect(image, iterations);
+
+        // 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();
+
+        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 ((IMAGE_WIDTH > 0) && (IMAGE_HEIGHT > 0)) {
+            imageWHRatio = ((double) IMAGE_WIDTH / (double) IMAGE_HEIGHT);
+        }
+
+        if ((complexWidth > 0) && (complexHeight > 0)) {
+            complexWHRatio = complexWidth / complexHeight;
+        } else {
+            return;
+        }
+
+        if (Double.compare(imageWHRatio, complexWHRatio) == 0) {
+            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() / ((IMAGE_WIDTH) * 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[NUM_COLORS];
+        for (int colorNum = NUM_COLORS - 1; colorNum >= 0; colorNum--) {
+            colorMap[colorNum] = Color.white.getRGB();
+        }
+        colorTable.put(COLORS_BLACK_AND_WHITE, colorMap);
+
+        // COLORS_BLUE_ICE:
+        blue = 255;
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = NUM_COLORS - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((255 * (float) colorNum / NUM_COLORS)) % 255;
+            green = (int) ((255 * (float) colorNum / NUM_COLORS)) % 255;
+            colorMap[colorNum] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_BLUE_ICE, colorMap);
+
+        // COLORS_FUNKY:
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = NUM_COLORS - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((1024 * (float) colorNum / NUM_COLORS)) % 255;
+            green = (int) ((512 * (float) colorNum / NUM_COLORS)) % 255;
+            blue = (int) ((256 * (float) colorNum / NUM_COLORS)) % 255;
+            colorMap[NUM_COLORS - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_FUNKY, colorMap);
+
+        // COLORS_PASTEL
+        brightness = (float) 1.0;
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 4) / (float) NUM_COLORS) % NUM_COLORS;
+            saturation = ((float) (colorNum * 2) / (float) NUM_COLORS) % NUM_COLORS;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_PASTEL, colorMap);
+
+        // COLORS_PSYCHEDELIC:
+        saturation = (float) 1.0;
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 5) / (float) NUM_COLORS) % NUM_COLORS;
+            brightness = ((float) (colorNum * 20) / (float) NUM_COLORS) % NUM_COLORS;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_PSYCHEDELIC, colorMap);
+
+        // COLORS_PURPLE_HAZE:
+        red = 255;
+        blue = 255;
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = NUM_COLORS - 1; colorNum >= 0; colorNum--) {
+            green = (int) ((255 * (float) colorNum / NUM_COLORS)) % 255;
+            colorMap[NUM_COLORS - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_PURPLE_HAZE, colorMap);
+
+        // COLORS_RADICAL:
+        saturation = (float) 1.0;
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 7) / (float) NUM_COLORS) % NUM_COLORS;
+            brightness = ((float) (colorNum * 49) / (float) NUM_COLORS) % NUM_COLORS;
+            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[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = (float) colorNum / (float) NUM_COLORS;
+            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[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 5) / (float) NUM_COLORS) % NUM_COLORS;
+            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[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 2) / (float) NUM_COLORS) % NUM_COLORS;
+            brightness = ((float) (colorNum * 5) / (float) NUM_COLORS) % NUM_COLORS;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_SCINTILLATION, colorMap);
+
+        // COLORS_WARPED:
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = NUM_COLORS - 1; colorNum >= 0; colorNum--) {
+            red = (int) ((1024 * (float) colorNum / NUM_COLORS)) % 255;
+            green = (int) ((256 * (float) colorNum / NUM_COLORS)) % 255;
+            blue = (int) ((512 * (float) colorNum / NUM_COLORS)) % 255;
+            colorMap[NUM_COLORS - colorNum - 1] = new Color(red, green, blue).getRGB();
+        }
+        colorTable.put(COLORS_WARPED, colorMap);
+
+        // COLORS_WILD:
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; colorNum++) {
+            hue = ((float) (colorNum * 1) / (float) NUM_COLORS) % NUM_COLORS;
+            saturation = ((float) (colorNum * 2) / (float) NUM_COLORS) % NUM_COLORS;
+            brightness = ((float) (colorNum * 4) / (float) NUM_COLORS) % NUM_COLORS;
+            colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
+        }
+        colorTable.put(COLORS_WILD, colorMap);
+
+        // COLORS_ZEBRA:
+        colorMap = new int[NUM_COLORS];
+        for (int colorNum = 0; colorNum < NUM_COLORS; 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[][] iterations) {
+
+        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];
+
+        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);
+                stdDev[i * parts + j] = calcStdDev(iterations, subRect);
+                ++j;
+            }
+            ++i;
+            j = 0;
+        }
+
+        // find tile with greatest std deviation:
+        double max = 0;
+        int index = 0;
+        for (i = 0; i < noTiles; i++) {
+            if (stdDev[i] > max) {
+                index = i;
+                max = stdDev[i];
+            }
+        }
+        newRowTile = index / parts;
+        newColTile = index % parts;
+    }
+
+    private double calcStdDev(int[][] iterations, Rectangle rect) {
+
+        int sum = 0;
+        long sumSquare = 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 iters = iterations[x][y];
+                sum += iters;
+                sumSquare += iters * iters;
+            }
+        }
+        int count = rect.width * rect.height;
+        double mean = 0.0;
+
+        mean = (double) sum / count;
+        return Math.sqrt(sumSquare / (count - (mean * mean)));
+    }
+
+}



Mime
View raw message