You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2013/11/02 01:53:10 UTC

[1/2] git commit: Support aggregation of modules into a JavaScript Stack - move RequireJS into the core stack - include the core stack on all pages - rewrite modules to add the module name to the JavaScript define() function invocation

Updated Branches:
  refs/heads/master 9c574e7a5 -> 65d31852d


Support aggregation of modules into a JavaScript Stack
- move RequireJS into the core stack
- include the core stack on all pages
- rewrite modules to add the module name to the JavaScript define() function invocation


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/65d31852
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/65d31852
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/65d31852

Branch: refs/heads/master
Commit: 65d31852d8b4a0691a19736c672fedea9e3179e0
Parents: 1ff0ae9
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri Nov 1 17:51:17 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri Nov 1 17:51:35 2013 -0700

----------------------------------------------------------------------
 .../internal/services/DocumentLinker.java       |   8 ++
 .../internal/services/DocumentLinkerImpl.java   |  18 +++
 .../services/PartialMarkupDocumentLinker.java   |  15 +-
 .../services/ajax/JavaScriptSupportImpl.java    |  30 ++--
 .../assets/JavaScriptStackAssemblerImpl.java    | 136 ++++++++++++++-----
 .../JavaScriptStackPathConstructorImpl.java     |  16 ++-
 .../services/javascript/ModuleManagerImpl.java  |  31 ++---
 .../tapestry5/modules/JavaScriptModule.java     |  34 ++++-
 .../services/javascript/StackExtensionType.java |  10 +-
 .../ajax/JavaScriptSupportAutofocusTests.groovy |   5 +-
 .../ajax/JavaScriptSupportImplTest.groovy       |  74 ++++------
 .../app1/pages/MultiZoneUpdateDemo.java         |  13 +-
 .../META-INF/modules/app/multi-zone-update.js   |   5 +
 .../app1/pages/MultiZoneUpdateDemo.js           |   5 -
 14 files changed, 263 insertions(+), 137 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinker.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinker.java
index a66b828..e380160 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinker.java
@@ -37,6 +37,14 @@ public interface DocumentLinker
     void addLibrary(String libraryURL);
 
     /**
+     * A special case used only for the libraries that are part of the core stack, which itself contains RequireJS
+     * and is used to bootstrap up to adding non-core libraries.
+     *
+     * @since 5.4
+     */
+    void addCoreLibrary(String libraryURL);
+
+    /**
      * Adds a link to load a CSS stylesheet.
      */
     void addStylesheetLink(StylesheetLink stylesheet);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
index 5263be6..0c41e82 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
@@ -27,6 +27,8 @@ import java.util.List;
 
 public class DocumentLinkerImpl implements DocumentLinker
 {
+    private final List<String> coreLibraryURLs = CollectionFactory.newList();
+
     private final List<String> libraryURLs = CollectionFactory.newList();
 
     private final ModuleInitsManager initsManager = new ModuleInitsManager();
@@ -64,6 +66,13 @@ public class DocumentLinkerImpl implements DocumentLinker
     }
 
 
+    public void addCoreLibrary(String libraryURL)
+    {
+        coreLibraryURLs.add(libraryURL);
+
+        hasScriptsOrInitializations = true;
+    }
+
     public void addLibrary(String libraryURL)
     {
         libraryURLs.add(libraryURL);
@@ -153,7 +162,9 @@ public class DocumentLinkerImpl implements DocumentLinker
         // use stylesheets?
 
         if (!rootElementName.equals("html"))
+        {
             throw new RuntimeException(String.format("The root element of the rendered document was <%s>, not <html>. A root element of <html> is needed when linking JavaScript and stylesheet resources.", rootElementName));
+        }
 
         // TAPESTRY-2364
 
@@ -196,6 +207,13 @@ public class DocumentLinkerImpl implements DocumentLinker
      */
     protected void addScriptsToEndOfBody(Element body)
     {
+        for (String url : coreLibraryURLs)
+        {
+            body.element("script",
+                    "type", "text/javascript",
+                    "src", url);
+        }
+
         // In prior releases of Tapestry, we've vacillated about where the <script> tags go
         // (in <head> or at bottom of <body>). Switching to a module approach gives us a new chance to fix this.
         // Eventually, (nearly) everything will be loaded as modules.

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
index ddcde16..7927f20 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
@@ -1,4 +1,4 @@
-// Copyright 2008, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2008-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -30,6 +30,11 @@ public class PartialMarkupDocumentLinker implements DocumentLinker
 
     private final ModuleInitsManager initsManager = new ModuleInitsManager();
 
+    public void addCoreLibrary(String libraryURL)
+    {
+        notImplemented("addCoreLibrary");
+    }
+
     public void addLibrary(String libraryURL)
     {
         libraryURLs.put(libraryURL);
@@ -46,10 +51,14 @@ public class PartialMarkupDocumentLinker implements DocumentLinker
         stylesheets.put(object);
     }
 
+    private void notImplemented(String methodName)
+    {
+        throw new UnsupportedOperationException(String.format("DocumentLinker.%s() is not implemented for partial page renders.", methodName));
+    }
+
     public void addScript(InitializationPriority priority, String script)
     {
-        throw new UnsupportedOperationException(
-                "DocumentLinker.addScript() is not implemented for partial page renders.");
+        notImplemented("addScript");
     }
 
     public void addInitialization(InitializationPriority priority, String moduleName, String functionName, JSONArray arguments)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
index 2d207b8..691872e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
@@ -198,6 +198,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
         assert parameter != null;
         assert InternalUtils.isNonBlank(functionName);
 
+        addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
+
         require("t5/core/init").priority(priority).with(functionName, parameter);
     }
 
@@ -221,6 +223,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
         assert priority != null;
         assert InternalUtils.isNonBlank(format);
 
+        addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
+
         String newScript = arguments.length == 0 ? format : String.format(format, arguments);
 
         if (partialMode)
@@ -257,6 +261,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     public JavaScriptSupport importJavaScriptLibrary(String libraryURL)
     {
 
+        addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
+
         String stackName = findStackForLibrary(libraryURL);
 
         if (stackName != null)
@@ -322,24 +328,27 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
             addAssetsFromStack(dependentStackname);
         }
 
+        addedStacks.put(stackName, true);
+
+        boolean addAsCoreLibrary = stackName.equals(InternalConstants.CORE_STACK_NAME);
+
         List<String> libraryURLs = stackPathConstructor.constructPathsForJavaScriptStack(stackName);
 
         for (String libraryURL : libraryURLs)
         {
-            linker.addLibrary(libraryURL);
+            if (addAsCoreLibrary)
+            {
+                linker.addCoreLibrary(libraryURL);
+            } else
+            {
+                linker.addLibrary(libraryURL);
+            }
         }
 
         // TAP5-2197: to avoid @Import'ed stylesheets to appear after the core ones,
         // the latter ones are now always added in the head of stylesheetLinks, not the tail.
         stylesheetLinks.addAll(0, stack.getStylesheets());
 
-        for (String moduleName : stack.getModules())
-        {
-            require(moduleName);
-        }
-
-        addedStacks.put(stackName, true);
-
         String initialization = stack.getInitialization();
 
         if (initialization != null)
@@ -376,6 +385,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     {
         assert InternalUtils.isNonBlank(stackName);
 
+        addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
+
         addAssetsFromStack(stackName);
 
         return this;
@@ -399,10 +410,13 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     {
         assert InternalUtils.isNonBlank(moduleName);
 
+        addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
+
         InitializationImpl init = new InitializationImpl(moduleName);
 
         inits.add(init);
 
         return init;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
index b52ab09..5cce91b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
@@ -14,26 +14,20 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.services.ThreadLocale;
-import org.apache.tapestry5.json.JSONArray;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
-import org.apache.tapestry5.services.assets.CompressionStatus;
-import org.apache.tapestry5.services.assets.StreamableResource;
-import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
-import org.apache.tapestry5.services.assets.StreamableResourceSource;
+import org.apache.tapestry5.services.assets.*;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
+import org.apache.tapestry5.services.javascript.ModuleManager;
+
+import java.io.*;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
 
 public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 {
@@ -49,18 +43,20 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
     private final AssetChecksumGenerator checksumGenerator;
 
+    private final ModuleManager moduleManager;
+
     private final Map<String, StreamableResource> cache = CollectionFactory.newCaseInsensitiveMap();
 
-    // TODO: Support for minimization
     // TODO: Support for aggregated CSS as well as aggregated JavaScript
 
-    public JavaScriptStackAssemblerImpl(ThreadLocale threadLocale, ResourceChangeTracker resourceChangeTracker, StreamableResourceSource streamableResourceSource, JavaScriptStackSource stackSource, AssetChecksumGenerator checksumGenerator)
+    public JavaScriptStackAssemblerImpl(ThreadLocale threadLocale, ResourceChangeTracker resourceChangeTracker, StreamableResourceSource streamableResourceSource, JavaScriptStackSource stackSource, AssetChecksumGenerator checksumGenerator, ModuleManager moduleManager)
     {
         this.threadLocale = threadLocale;
         this.resourceChangeTracker = resourceChangeTracker;
         this.streamableResourceSource = streamableResourceSource;
         this.stackSource = stackSource;
         this.checksumGenerator = checksumGenerator;
+        this.moduleManager = moduleManager;
 
         resourceChangeTracker.clearOnInvalidation(cache);
     }
@@ -102,31 +98,72 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
         JavaScriptStack stack = stackSource.getStack(stackName);
 
-        return assemble(locale.toString(), stackName, stack.getJavaScriptLibraries());
+        return assembleStreamableForStack(locale.toString(), stackName, stack.getJavaScriptLibraries(), stack.getModules());
     }
 
+    interface StreamableReader
+    {
+        /**
+         * Reads the content of a StreamableResource as a UTF-8 string, and optionally transforms it in some way.
+         */
+        String read(StreamableResource resource) throws IOException;
+    }
 
-    private StreamableResource assemble(String localeName, String stackName, List<Asset> libraries) throws IOException
+    static String getContent(StreamableResource resource) throws IOException
     {
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
-        PrintWriter writer = new PrintWriter(osw, true);
-        long lastModified = 0;
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream(resource.getSize());
+        resource.streamTo(bos);
 
-        StringBuilder description = new StringBuilder(String.format("'%s' JavaScript stack, for locale %s, resources=", stackName, localeName));
-        String sep = "";
+        return new String(bos.toByteArray(), "UTF-8");
+    }
 
-        JSONArray paths = new JSONArray();
 
-        for (Asset library : libraries)
+    final StreamableReader libraryReader = new StreamableReader()
+    {
+        public String read(StreamableResource resource) throws IOException
         {
-            String path = library.getResource().toString();
+            return getContent(resource);
+        }
+    };
 
-            paths.put(path);
+    private final static Pattern DEFINE = Pattern.compile("\\bdefine\\s*\\(");
 
-            writer.format("\n/* %s */;\n", path);
+    private class ModuleReader implements StreamableReader
+    {
+        final String moduleName;
 
-            Resource resource = library.getResource();
+        private ModuleReader(String moduleName)
+        {
+            this.moduleName = moduleName;
+        }
+
+        public String read(StreamableResource resource) throws IOException
+        {
+            String content = getContent(resource);
+
+            return DEFINE.matcher(content).replaceFirst("define(\"" + moduleName + "\",");
+        }
+    }
+
+
+    private class Assembly
+    {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2000);
+        final PrintWriter writer;
+        long lastModified = 0;
+        final StringBuilder description;
+        private String sep = "";
+
+        private Assembly(String description) throws UnsupportedEncodingException
+        {
+            writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"));
+
+            this.description = new StringBuilder(description);
+        }
+
+        void add(Resource resource, StreamableReader reader) throws IOException
+        {
+            writer.format("\n/* %s */;\n", resource.toString());
 
             description.append(sep).append(resource.toString());
             sep = ", ";
@@ -134,18 +171,45 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
             StreamableResource streamable = streamableResourceSource.getStreamableResource(resource,
                     StreamableResourceProcessing.FOR_AGGREGATION, resourceChangeTracker);
 
-            streamable.streamTo(stream);
+            writer.print(reader.read(streamable));
 
             lastModified = Math.max(lastModified, streamable.getLastModified());
         }
 
-        writer.close();
+        StreamableResource finish()
+        {
+            writer.close();
 
-        return new StreamableResourceImpl(
-                description.toString(),
-                JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSABLE, lastModified,
-                new BytestreamCache(stream), checksumGenerator);
+            return new StreamableResourceImpl(
+                    description.toString(),
+                    JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSABLE, lastModified,
+                    new BytestreamCache(outputStream), checksumGenerator);
+        }
     }
 
+    private StreamableResource assembleStreamableForStack(String localeName, String stackName, List<Asset> libraries, List<String> moduleNames) throws IOException
+    {
+        Assembly assembly = new Assembly(String.format("'%s' JavaScript stack, for locale %s, resources=", stackName, localeName));
 
+        for (Asset library : libraries)
+        {
+            Resource resource = library.getResource();
+
+            assembly.add(resource, libraryReader);
+        }
+
+        for (String moduleName : moduleNames)
+        {
+            Resource resource = moduleManager.findResourceForModule(moduleName);
+
+            if (resource == null)
+            {
+                throw new IllegalArgumentException(String.format("Could not identify a resource for module name '%s'.", moduleName));
+            }
+
+            assembly.add(resource, new ModuleReader(moduleName));
+        }
+
+        return assembly.finish();
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index 25c4288..ebb7af0 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -58,8 +58,9 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
                                               JavaScriptStackSource javascriptStackSource,
                                               JavaScriptStackAssembler assembler,
-                                              ResponseCompressionAnalyzer compressionAnalyzer, @Symbol(SymbolConstants.COMBINE_SCRIPTS)
-    boolean combineScripts)
+                                              ResponseCompressionAnalyzer compressionAnalyzer,
+                                              @Symbol(SymbolConstants.COMBINE_SCRIPTS)
+                                              boolean combineScripts)
     {
         this.threadLocale = threadLocale;
         this.assetPathConstructor = assetPathConstructor;
@@ -75,9 +76,16 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
 
         List<Asset> assets = stack.getJavaScriptLibraries();
 
-        if (assets.size() > 1 && combineScripts)
+        // When combine scripts is true, we want to build the virtual aggregated JavaScript ... but only
+        // if there is more than one library asset, or any modules.
+        if (combineScripts)
         {
-            return combinedStackURL(stackName);
+            boolean needsVirtual = (assets.size() > 1) || (!stack.getModules().isEmpty());
+
+            if (needsVirtual)
+            {
+                return combinedStackURL(stackName);
+            }
         }
 
         return toPaths(assets);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
index 95642ce..99ca34d 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
@@ -14,9 +14,7 @@
 
 package org.apache.tapestry5.internal.services.javascript;
 
-import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Messages;
@@ -43,10 +41,6 @@ public class ModuleManagerImpl implements ModuleManager
 
     private final ResponseCompressionAnalyzer compressionAnalyzer;
 
-    private final Asset requireJS;
-
-    private final Map<String, JavaScriptModuleConfiguration> configuration;
-
     private final Messages globalMessages;
 
     private final boolean compactJSON;
@@ -60,14 +54,12 @@ public class ModuleManagerImpl implements ModuleManager
     // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null.
     private final Map<String, Resource> cache = CollectionFactory.newConcurrentMap();
 
-    private final boolean devMode;
+    private final JSONObject baseConfig;
 
     private final AssetPathConstructor assetPathConstructor;
 
     public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer,
                              AssetSource assetSource,
-                             @Path("${" + SymbolConstants.REQUIRE_JS + "}")
-                             Asset requireJS,
                              Map<String, JavaScriptModuleConfiguration> configuration,
                              Messages globalMessages,
                              StreamableResourceSource streamableResourceSource,
@@ -78,25 +70,29 @@ public class ModuleManagerImpl implements ModuleManager
                              AssetPathConstructor assetPathConstructor)
     {
         this.compressionAnalyzer = compressionAnalyzer;
-        this.requireJS = requireJS;
-        this.configuration = configuration;
         this.globalMessages = globalMessages;
         this.compactJSON = compactJSON;
         this.assetPathConstructor = assetPathConstructor;
 
-        this.devMode = !productionMode;
-
         classpathRoot = assetSource.resourceForPath("");
         extensions = CollectionFactory.newSet("js");
 
         extensions.addAll(streamableResourceSource.fileExtensionsForContentType("text/javascript"));
+
+        baseConfig = buildBaseConfig(configuration, !productionMode);
     }
 
     private String buildRequireJSConfig()
     {
-        String baseURL = getBaseURL();
+        // This is the part that can vary from one request to another, based on the capabilities of the client.
+        JSONObject config =  baseConfig.copy().put("baseUrl", getBaseURL());
 
-        JSONObject config = new JSONObject("baseUrl", baseURL);
+        return String.format("requirejs.config(%s);\n", config.toString(compactJSON));
+    }
+
+    private JSONObject buildBaseConfig(Map<String, JavaScriptModuleConfiguration> configuration, boolean devMode)
+    {
+        JSONObject config = new JSONObject();
 
         // In DevMode, wait up to five minutes for a script, as the developer may be using the debugger.
         if (devMode)
@@ -119,8 +115,7 @@ public class ModuleManagerImpl implements ModuleManager
                 addModuleToConfig(config, name, module);
             }
         }
-
-        return String.format("requirejs.config(%s);\n", config.toString(compactJSON));
+        return config;
     }
 
     private String getBaseURL()
@@ -173,8 +168,6 @@ public class ModuleManagerImpl implements ModuleManager
 
     public void writeInitialization(Element body, List<String> libraryURLs, List<?> inits)
     {
-        body.element("script", "src", requireJS.toClientURL());
-
         Element element = body.element("script", "type", "text/javascript");
 
         // Build it each time because we don't know if the client supports GZip or not, and

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index 5bf0782..25e2f77 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -77,10 +77,35 @@ public class JavaScriptModule
         configuration.add(InternalConstants.CORE_STACK_NAME, coreStack);
     }
 
+    private static final String[] bundledModules = new String[]{
+            "ajax", "dom", "events", "console", "exception-frame", "pageinit", "messages", "utils"
+    };
+
+    /**
+     * The core JavaScriptStack has a number of entries:
+     * <dl>
+     * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library</dd>
+     * <dt>scriptaculous.js, effects.js</dt> <dd>Optional JavaScript libraries in compatibility mode (see {@link Trait#SCRIPTACULOUS})</dd>
+     * <dt>t53-compatibility.js</dt> <dd>Optional JavaScript library (see {@link Trait#INITIALIZERS})</dd>
+     * <dt>t5/core/init</dt> <dd>Optional module related to t53-compatibility.js</dd>
+     * <dt>bootstrap.css, tapestry.css, exception-frame.css, tapestry-console.css, tree.css</dt>
+     * <dd>CSS files</dd>
+     * <dt>t5/core/ajax, dom, events, console, exception-frame, pageinit, messages, utils</dt>
+     * <dd>Additional JavaScript modules</dd>
+     * </dl>
+     * <p/>
+     * User modules may replace or extend this list.
+     */
     @Contribute(JavaScriptStack.class)
     @Core
-    public static void setupCoreJavaScriptStack(OrderedConfiguration<StackExtension> configuration, Compatibility compatibility, @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) String provider)
+    public static void setupCoreJavaScriptStack(OrderedConfiguration<StackExtension> configuration, Compatibility compatibility,
+                                                @Symbol(SymbolConstants.REQUIRE_JS)
+                                                String requireJS,
+                                                @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
+                                                String provider)
     {
+        configuration.add("requirejs", new StackExtension(StackExtensionType.LIBRARY, requireJS));
+
         final String ROOT = "${tapestry.asset.root}";
 
         if (provider.equals("prototype") && compatibility.enabled(Trait.SCRIPTACULOUS))
@@ -95,6 +120,7 @@ public class JavaScriptModule
             add(configuration, StackExtensionType.LIBRARY,
                     ROOT + "/t53-compatibility.js"
             );
+            configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init"));
         }
 
         add(configuration, StackExtensionType.STYLESHEET,
@@ -107,6 +133,12 @@ public class JavaScriptModule
                 ROOT + "/tapestry-console.css",
 
                 ROOT + "/tree.css");
+
+        for (String name : bundledModules)
+        {
+            String full = "t5/core/" + name;
+            configuration.add(full, new StackExtension(StackExtensionType.MODULE, full));
+        }
     }
 
     private static void add(OrderedConfiguration<StackExtension> configuration, StackExtensionType type, String... paths)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
index 2848817..50594ae 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
@@ -55,7 +55,15 @@ public enum StackExtensionType
     STYLESHEET,
 
     /**
-     * A module to load with the stack.
+     * A module to aggregate with the stack. The module's JavaScript is included after any libraries.
+     * In development mode (with aggregation disabled), the library will be included individually.
+     * Unlike the RequireJS {@code r.js} tool, this does not process
+     * dependencies and is based on a simple regular expression parser.
+     * <p/>
+     * Note that this only loads the module's <em>code</em> and defines the module as available;
+     * the module's function will not be invoked unless {@link JavaScriptSupport#require(String)} is invoked to establish
+     * a dependency.
+     *
      * @since 5.4
      */
     MODULE,

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
index a0b7e00..ba023f8 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
@@ -1,4 +1,4 @@
-// Copyright 2010, 2012 The Apache Software Foundation
+// Copyright 2010-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -39,7 +39,8 @@ class JavaScriptSupportAutofocusTests extends InternalBaseTestCase {
 
         replay()
 
-        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor)
+        // Test in partial mode, to bypass the logic about importing the "core' stack.
+        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor, null, true)
 
         cls jss
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
index 5796285..2b8abd1 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
@@ -2,6 +2,7 @@ package org.apache.tapestry5.internal.services.ajax
 
 import org.apache.tapestry5.Asset
 import org.apache.tapestry5.ComponentResources
+import org.apache.tapestry5.internal.InternalConstants
 import org.apache.tapestry5.internal.services.DocumentLinker
 import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor
 import org.apache.tapestry5.internal.test.InternalBaseTestCase
@@ -66,12 +67,27 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
         return newMock(JavaScriptStackSource.class)
     }
 
+    protected final train_for_empty_core_stack(stackSource, pathConstructor) {
+        JavaScriptStack stack = mockJavaScriptStack()
+
+        expect(stackSource.getStack(InternalConstants.CORE_STACK_NAME)).andReturn(stack)
+
+        expect(stack.initialization).andReturn(null)
+
+        expect(stack.stacks).andReturn([])
+        expect(stack.stylesheets).andReturn([])
+
+        expect(pathConstructor.constructPathsForJavaScriptStack(InternalConstants.CORE_STACK_NAME)).andReturn([])
+    }
+
     @Test
     void add_script_passes_thru_to_document_linker() {
         DocumentLinker linker = mockDocumentLinker()
         JavaScriptStackSource stackSource = mockJavaScriptStackSource()
         JavaScriptStackPathConstructor pathConstructor = mockJavaScriptStackPathConstructor()
 
+        train_for_empty_core_stack stackSource, pathConstructor
+
         linker.addScript(InitializationPriority.IMMEDIATE, "doSomething();")
 
         replay()
@@ -97,7 +113,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.importJavaScriptLibrary(library)
 
@@ -121,7 +137,6 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
         expect(stackSource.getStack("mystack")).andReturn(mystack).atLeastOnce()
 
         expect(mystack.stacks).andReturn([])
-        expect(mystack.modules).andReturn([])
         expect(mystack.javaScriptLibraries).andReturn([library1, library2])
 
         expect(pathConstructor.constructPathsForJavaScriptStack("mystack")).andReturn(["stacks/mystack.js"])
@@ -134,7 +149,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.importJavaScriptLibrary(library1)
 
@@ -155,6 +170,8 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
         JavaScriptStackSource stackSource = mockJavaScriptStackSource()
         JavaScriptStackPathConstructor pathConstructor = mockJavaScriptStackPathConstructor()
 
+        train_for_empty_core_stack stackSource, pathConstructor
+
         JavaScriptStack stack = mockJavaScriptStack()
 
         StylesheetLink stylesheetLink = new StylesheetLink("stack.css")
@@ -162,7 +179,6 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
         expect(stackSource.getStack("custom")).andReturn(stack)
         expect(pathConstructor.constructPathsForJavaScriptStack("custom")).andReturn(["stack.js"])
         expect(stack.stylesheets).andReturn([stylesheetLink])
-        expect(stack.modules).andReturn([])
         expect(stack.initialization).andReturn "customInit();"
 
         expect(stack.stacks).andReturn([])
@@ -187,47 +203,13 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
     }
 
     @Test
-    void import_stack_with_modules() {
-        DocumentLinker linker = mockDocumentLinker()
-        JavaScriptStackSource stackSource = mockJavaScriptStackSource()
-        JavaScriptStackPathConstructor pathConstructor = mockJavaScriptStackPathConstructor()
-        JavaScriptStack mystack = mockJavaScriptStack()
-
-        expect(stackSource.getStack("mystack")).andReturn(mystack).atLeastOnce()
-
-        expect(mystack.stacks).andReturn([])
-        expect(mystack.modules).andReturn(["foo/bar", "gnip/gnop"])
-
-        expect(pathConstructor.constructPathsForJavaScriptStack("mystack")).andReturn(["stacks/mystack.js"])
-
-        expect(mystack.stylesheets).andReturn([])
-
-        expect(mystack.initialization).andReturn null
-
-        linker.addLibrary("stacks/mystack.js")
-        linker.addInitialization(InitializationPriority.NORMAL, "foo/bar", null, null)
-        linker.addInitialization(InitializationPriority.NORMAL, "gnip/gnop", null, null)
-
-        replay()
-
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
-
-        jss.importStack("mystack")
-
-
-
-        jss.commit()
-
-        verify()
-
-    }
-
-    @Test
     void import_stack_with_dependencies() {
         DocumentLinker linker = mockDocumentLinker()
         JavaScriptStackSource stackSource = mockJavaScriptStackSource()
         JavaScriptStackPathConstructor pathConstructor = mockJavaScriptStackPathConstructor()
 
+        train_for_empty_core_stack stackSource, pathConstructor
+
         JavaScriptStack child = mockJavaScriptStack()
         JavaScriptStack parent = mockJavaScriptStack()
 
@@ -243,13 +225,11 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         expect(pathConstructor.constructPathsForJavaScriptStack("parent")).andReturn(["parent.js"])
         expect(parent.stylesheets).andReturn([parentStylesheetLink])
-        expect(parent.modules).andReturn([])
 
         expect(parent.initialization).andReturn("parentInit();")
 
         expect(pathConstructor.constructPathsForJavaScriptStack("child")).andReturn(["child.js"])
         expect(child.stylesheets).andReturn([childStylesheetLink])
-        expect(child.modules).andReturn([])
 
         expect(child.getInitialization()).andReturn("childInit();")
 
@@ -291,7 +271,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.importJavaScriptLibrary(library1)
         jss.importJavaScriptLibrary(library2)
@@ -313,7 +293,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "chuck")
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "charley")
@@ -337,7 +317,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", chuck)
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", buzz)
@@ -365,7 +345,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.addInitializerCall("setup", "chuck")
 
@@ -386,7 +366,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
 
         jss.addInitializerCall("setup", chuck)
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
index 3aaa1cb..7af7bcc 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010 The Apache Software Foundation
+// Copyright 2009-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,17 +14,14 @@
 
 package org.apache.tapestry5.integration.app1.pages;
 
-import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.Block;
 import org.apache.tapestry5.ValueEncoder;
 import org.apache.tapestry5.ajax.MultiZoneUpdate;
 import org.apache.tapestry5.annotations.InjectComponent;
-import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.corelib.components.Zone;
 import org.apache.tapestry5.internal.services.StringValueEncoder;
 import org.apache.tapestry5.ioc.annotations.Inject;
-import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
 import org.apache.tapestry5.services.ajax.JavaScriptCallback;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -45,10 +42,6 @@ public class MultiZoneUpdateDemo
     @Inject
     private AjaxResponseRenderer ajaxResponseRenderer;
 
-    @Inject
-    @Path("MultiZoneUpdateDemo.js")
-    private Asset library;
-
     public Date getNow()
     {
         return new Date();
@@ -65,9 +58,7 @@ public class MultiZoneUpdateDemo
         {
             public void run(JavaScriptSupport javascriptSupport)
             {
-                javascriptSupport.importJavaScriptLibrary(library);
-                javascriptSupport.addInitializerCall("writeMessageTo", new JSONObject("id", "message", "message",
-                        "Updated"));
+                javascriptSupport.require("app/multi-zone-update").with("message", "Updated");
             }
         });
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/test/resources/META-INF/modules/app/multi-zone-update.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/resources/META-INF/modules/app/multi-zone-update.js b/tapestry-core/src/test/resources/META-INF/modules/app/multi-zone-update.js
new file mode 100644
index 0000000..fe7f694
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/modules/app/multi-zone-update.js
@@ -0,0 +1,5 @@
+define(["t5/core/dom"], function (dom) {
+    return function (id, message) {
+        dom(id).update(message);
+    };
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/65d31852/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.js b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.js
deleted file mode 100644
index cbf379b..0000000
--- a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.js
+++ /dev/null
@@ -1,5 +0,0 @@
-T5.extendInitializers({
-    writeMessageTo : function(spec) {
-        $(spec.id).update(spec.message);
-    }
-})
\ No newline at end of file


[2/2] git commit: Add a copy() method to JSONObject

Posted by hl...@apache.org.
Add a copy() method to JSONObject


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/1ff0ae9e
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/1ff0ae9e
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/1ff0ae9e

Branch: refs/heads/master
Commit: 1ff0ae9e21dd978e9df0e6854d009b0a6a032247
Parents: 9c574e7
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Oct 16 18:09:28 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri Nov 1 17:51:35 2013 -0700

----------------------------------------------------------------------
 .../org/apache/tapestry5/json/JSONObject.java   | 20 +++++++++++++++---
 .../groovy/json/specs/JSONObjectSpec.groovy     | 22 ++++++++++++++++++++
 2 files changed, 39 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1ff0ae9e/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
----------------------------------------------------------------------
diff --git a/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java b/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
index 8b78ad8..575d1ea 100644
--- a/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
+++ b/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2007-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -150,10 +150,23 @@ public final class JSONObject extends JSONCollection
     }
 
     /**
+     * Returns a new JSONObject that is a shallow copy of this JSONObject.
+     *
+     * @since 5.4
+     */
+    public JSONObject copy()
+    {
+        JSONObject dupe = new JSONObject();
+        dupe.properties.putAll(properties);
+
+        return dupe;
+    }
+
+    /**
      * Constructs a new JSONObject using a series of String keys and object values.
      * Object values sholuld be compatible with {@link #put(String, Object)}. Keys must be strings
      * (toString() will be invoked on each key).
-     *
+     * <p/>
      * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes
      * it much easier to initialize a JSONObject in a single statement, which is more readable.
      *
@@ -999,7 +1012,8 @@ public final class JSONObject extends JSONCollection
             throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key)));
         }
 
-        if (nested == null) {
+        if (nested == null)
+        {
             nested = new JSONObject();
             properties.put(key, nested);
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1ff0ae9e/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy b/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
index 9500d33..4cdd50b 100644
--- a/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
+++ b/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
@@ -38,6 +38,28 @@ class JSONObjectSpec extends Specification {
         fullCopy.toCompactString() == /{"fred":"flintstone","barney":"rubble"}/
     }
 
+    def "copy all properties of JSONObject"() {
+        def master = new JSONObject("fred", "flintstone", "barney", "rubble")
+
+        when:
+
+        def fullCopy = master.copy()
+
+        then:
+
+        master == fullCopy
+
+        // And they are independent:
+
+        when:
+
+        master.put("wilma", "flintstone")
+
+        then:
+
+        master != fullCopy
+    }
+
     def "unknown keys when copying a JSONObject are ignored"() {
         def master = new JSONObject("fred", "flintstone", "barney", "rubble")
 


[2/2] git commit: Add a copy() method to JSONObject

Posted by hl...@apache.org.
Add a copy() method to JSONObject


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/1ff0ae9e
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/1ff0ae9e
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/1ff0ae9e

Branch: refs/heads/master
Commit: 1ff0ae9e21dd978e9df0e6854d009b0a6a032247
Parents: 9c574e7
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Oct 16 18:09:28 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri Nov 1 17:51:35 2013 -0700

----------------------------------------------------------------------
 .../org/apache/tapestry5/json/JSONObject.java   | 20 +++++++++++++++---
 .../groovy/json/specs/JSONObjectSpec.groovy     | 22 ++++++++++++++++++++
 2 files changed, 39 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1ff0ae9e/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
----------------------------------------------------------------------
diff --git a/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java b/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
index 8b78ad8..575d1ea 100644
--- a/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
+++ b/tapestry-json/src/main/java/org/apache/tapestry5/json/JSONObject.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2007-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -150,10 +150,23 @@ public final class JSONObject extends JSONCollection
     }
 
     /**
+     * Returns a new JSONObject that is a shallow copy of this JSONObject.
+     *
+     * @since 5.4
+     */
+    public JSONObject copy()
+    {
+        JSONObject dupe = new JSONObject();
+        dupe.properties.putAll(properties);
+
+        return dupe;
+    }
+
+    /**
      * Constructs a new JSONObject using a series of String keys and object values.
      * Object values sholuld be compatible with {@link #put(String, Object)}. Keys must be strings
      * (toString() will be invoked on each key).
-     *
+     * <p/>
      * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes
      * it much easier to initialize a JSONObject in a single statement, which is more readable.
      *
@@ -999,7 +1012,8 @@ public final class JSONObject extends JSONCollection
             throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key)));
         }
 
-        if (nested == null) {
+        if (nested == null)
+        {
             nested = new JSONObject();
             properties.put(key, nested);
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1ff0ae9e/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy b/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
index 9500d33..4cdd50b 100644
--- a/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
+++ b/tapestry-json/src/test/groovy/json/specs/JSONObjectSpec.groovy
@@ -38,6 +38,28 @@ class JSONObjectSpec extends Specification {
         fullCopy.toCompactString() == /{"fred":"flintstone","barney":"rubble"}/
     }
 
+    def "copy all properties of JSONObject"() {
+        def master = new JSONObject("fred", "flintstone", "barney", "rubble")
+
+        when:
+
+        def fullCopy = master.copy()
+
+        then:
+
+        master == fullCopy
+
+        // And they are independent:
+
+        when:
+
+        master.put("wilma", "flintstone")
+
+        then:
+
+        master != fullCopy
+    }
+
     def "unknown keys when copying a JSONObject are ignored"() {
         def master = new JSONObject("fred", "flintstone", "barney", "rubble")