This is an automated email from the ASF dual-hosted git repository.

thiagohp pushed a commit to branch TAP5-2779
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git

commit a573e5d016624901495af25fddd8c3516ce30d38
Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br>
AuthorDate: Mon Jun 24 22:29:11 2024 -0300

    TAP5-2779: fixing attempted duplicate class definition exception
    
    (pass 1, still with a lot of debugging and testing code and needing
    a big cleanup)
---
 .../internal/plastic/PlasticClassLoader.java       | 107 +++++++-
 .../internal/plastic/PlasticClassPool.java         |  20 +-
 .../corelib/pages/ComponentLibraries.java          | 163 +++++++++++-
 .../tapestry5/corelib/pages/ExceptionReport.java   |   6 +
 .../tapestry5/corelib/pages/PageCatalog.java       |   1 +
 .../corelib/pages/PageClassLoaderContexts.java     |  94 +++++++
 .../tapestry5/internal/ThrowawayClassLoader.java   |  92 +++++++
 .../ComponentDependencyGraphvizGeneratorImpl.java  |  28 +-
 .../services/ComponentDependencyRegistry.java      |   7 +
 .../services/ComponentDependencyRegistryImpl.java  |  17 +-
 .../services/ComponentInstantiatorSourceImpl.java  |  63 ++++-
 .../services/ComponentModelSourceImpl.java         |  55 +++-
 .../apache/tapestry5/modules/DashboardModule.java  |  10 +-
 .../apache/tapestry5/modules/PageLoadModule.java   |   9 +-
 .../pageload/PageClassLoaderContextManager.java    |   6 +
 .../PageClassLoaderContextManagerImpl.java         | 117 +++++++--
 .../tapestry5/corelib/pages/ComponentLibraries.tml | 289 +++++++++++++--------
 .../apache/tapestry5/corelib/pages/PageCatalog.tml |   2 +-
 .../corelib/pages/PageClassLoaderContexts.tml      |  17 ++
 tapestry-core/src/test/app1/AtComponentType.tml    |   6 +
 tapestry-core/src/test/app1/GridDemo.tml           |   2 +-
 .../integration/app1/pages/AtComponentType.java    |  24 ++
 .../tapestry5/integration/app1/pages/GridDemo.java |   6 +
 .../ComponentDependencyRegistryImplTest.java       | 101 ++++++-
 tapestry-core/src/test/resources/log4j.properties  |   1 +
 25 files changed, 1040 insertions(+), 203 deletions(-)

diff --git 
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
 
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
index 85a263b75..c0774c13a 100644
--- 
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
+++ 
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2011, 2023, 2024 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,6 +14,14 @@
 
 package org.apache.tapestry5.internal.plastic;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -33,12 +41,21 @@ public class PlasticClassLoader extends ClassLoader
     
     private Function<String, Class<?>> alternativeClassloading;
     
+    private Consumer<String> beforeLoadClass;
+    
+    private Set<String> loadedClasses;
+    
     private String tag;
     
+    private Map<String, Class<?>> cache;
+    
+    private static List<String> log = new ArrayList<>();
+    
     public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate 
delegate) 
     {
         super(parent);
         this.delegate = delegate;
+        loadedClasses = new HashSet<>();
     }
 
     @Override
@@ -50,24 +67,55 @@ public class PlasticClassLoader extends ClassLoader
 
             if (loadedClass != null)
                 return loadedClass;
-
+            
             if (shouldInterceptClassLoading(name))
             {
-                Class<?> c = null;
-                if ((filter != null && filter.test(name)) || (filter == null 
&& delegate.shouldInterceptClassLoading(name)))
-                {
-                    c = delegate.loadAndTransformClass(name);
+                
+                if 
(name.equals("org.apache.tapestry5.integration.app1.pages.GridDemo")) {
+                    System.out.println();
                 }
-                else if (alternativeClassloading != null)
+                
+                Class<?> c = getFromCache(name);
+                
+                if (c == null)
                 {
-                    c = alternativeClassloading.apply(name);
+                
+                    if ((filter != null && filter.test(name)) || (filter == 
null && delegate.shouldInterceptClassLoading(name)))
+                    {
+    
+                        try {
+                            log.add(String.format("Loading %s in %s", name, 
this));
+                            c = delegate.loadAndTransformClass(name);
+                        }
+                        catch (LinkageError e) {
+                            e.printStackTrace();
+                            for (String entry : log) {
+                                System.out.println(entry);
+                            }
+                            System.out.println();
+                            throw e;
+                        }
+                    }
+                    else if (alternativeClassloading != null)
+                    {
+                        c = alternativeClassloading.apply(name);
+                    }
+                    
+                    if (cache != null && c != null)
+                    {
+                        cache.put(name, c);
+                    }
+                    
                 }
                 
                 if (c == null)
                 {
                     return super.loadClass(name, resolve);                    
                 }
-                    
+                
+                
+//                loadedClasses.add(name + " : " + System.currentTimeMillis());
+
                 if (resolve)
                     resolveClass(c);
 
@@ -88,6 +136,10 @@ public class PlasticClassLoader extends ClassLoader
     {
         synchronized(getClassLoadingLock(className))
         {
+            if 
(className.equals("org.apache.tapestry5.integration.app1.pages.GridDemo"))
+            {
+                System.out.println();
+            }
             return defineClass(className, bytecode, 0, bytecode.length);
         }
     }
@@ -110,6 +162,10 @@ public class PlasticClassLoader extends ClassLoader
     public void setTag(String tag) 
     {
         this.tag = tag;
+        if (cache == null)
+        {
+            cache = Collections.synchronizedMap(new HashMap<>());
+        }
     }
     
     /**
@@ -126,9 +182,38 @@ public class PlasticClassLoader extends ClassLoader
     @Override
     public String toString()
     {
-        final String superToString = super.toString();
-        final String id = 
superToString.substring(superToString.indexOf('@')).trim();
+        final String id = getClassLoaderId();
         return String.format("PlasticClassLoader[%s, tag=%s, parent=%s]", id, 
tag, getParent());
     }
 
+    public String getClassLoaderId() {
+        final String superToString = super.toString();
+        return superToString.substring(superToString.indexOf('@')).trim();
+    }
+    
+    private Class<?> getFromCache(String name)
+    {
+        Class<?> c = null;
+        if (cache != null && cache.containsKey(name))
+        {
+            c = cache.get(name);
+        }
+        if (c == null && getParent() instanceof PlasticClassLoader)
+        {
+            c = ((PlasticClassLoader) getParent()).getFromCache(name);
+        }
+        return c;
+    }
+    
+//    /**
+//     * Sets a {@linkplain Consumer} that will be called before a transformed
+//     * class is loaded.
+//     * @param beforeLoadClass the <code>Consumer&lt;String&gt;</code>.
+//     * @since 5.8.7
+//     */
+//    public void setBeforeLoadClass(Consumer<String> beforeLoadClass) 
+//    {
+//        this.beforeLoadClass = beforeLoadClass;
+//    }
+
 }
diff --git 
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
 
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
index 9daa47d51..0bc3bcb7c 100644
--- 
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
+++ 
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
@@ -753,7 +753,18 @@ public class PlasticClassPool implements 
ClassLoaderDelegate, Opcodes, PlasticCl
 
     private FieldInstrumentations getFieldInstrumentations(String 
classInternalName)
     {
-        FieldInstrumentations result = instrumentations.get(classInternalName);
+        
+        // Check whether parent pool already has instrumentations for
+        // that class to avoid a duplicated class definition attempt
+        // when running tapestry-core in multiple classloader mode.
+        PlasticClassPool current = this;
+        FieldInstrumentations result;
+        do 
+        {
+            result = current.instrumentations.get(classInternalName);
+            current = current.parent;
+        }
+        while (current != null && result == null);
 
         if (result != null)
         {
@@ -845,6 +856,13 @@ public class PlasticClassPool implements 
ClassLoaderDelegate, Opcodes, PlasticCl
             throw new RuntimeException(e);
         }
     }
+
+    @Override
+    public String toString() 
+    {
+        return "PlasticClassPool [loader=" + loader + "]";
+    }
+    
 }
 
 
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
index 4ebaad2f1..83e4cfa27 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
@@ -21,11 +21,20 @@ import java.util.List;
 
 import org.apache.tapestry5.Block;
 import org.apache.tapestry5.annotations.Cached;
+import org.apache.tapestry5.annotations.InjectComponent;
 import org.apache.tapestry5.annotations.OnEvent;
+import org.apache.tapestry5.annotations.Persist;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
 import org.apache.tapestry5.annotations.WhitelistAccessOnly;
+import org.apache.tapestry5.corelib.components.Zone;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
+import org.apache.tapestry5.internal.plastic.ClassLoaderDelegate;
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
+import 
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
+import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
+import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
 import org.apache.tapestry5.ioc.annotations.Description;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
@@ -59,6 +68,9 @@ public class ComponentLibraries
     };
 
     private static enum Type { PAGE, COMPONENT, MIXIN }
+    
+    @InjectComponent
+    private Zone zone;
 
     @Inject
     private ComponentClassResolver componentClassResolver;
@@ -97,6 +109,39 @@ public class ComponentLibraries
     @Inject
     private ComponentLibraryInfoSource componentLibraryInfoSource;
     
+    @Inject
+    private ComponentDependencyRegistry componentDependencyRegistry;
+    
+    @Inject
+    private ComponentDependencyGraphvizGenerator 
componentDependencyGraphvizGenerator;
+    
+    @Property
+    private String selectedComponent;
+    
+    @Property
+    private String dependency;
+    
+    @Persist
+    @Property
+    private boolean showEverything;
+    
+//    void onActivate(List<String> context)
+//    {
+//        if (context.size() > 0)
+//        {
+//            selectedComponent = String.join("/", context);
+//        }
+//        else
+//        {
+//            selectedComponent = null;
+//        }
+//    }
+//    
+//    Object[] onPassivate()
+//    {
+//        return selectedComponent.split("/");
+//    }
+    
     @Cached
     public List<LibraryMapping> getLibraryMappings()
     {
@@ -105,7 +150,7 @@ public class ComponentLibraries
         // add all the library mappings, except the "" (empty string) one.
         for (LibraryMapping libraryMapping : 
componentClassResolver.getLibraryMappings())
         {
-            if (!"".equals(libraryMapping.libraryName)) {
+            if (showEverything || !"".equals(libraryMapping.libraryName)) {
                 mappings.add(libraryMapping);
             }
         }
@@ -132,6 +177,7 @@ public class ComponentLibraries
     private List<String> filter(final List<String> allNames)
     {
         List<String> logicalNames = new ArrayList<String>();
+        final List<LibraryMapping> libraryMappings = getLibraryMappings();
         for (String name : allNames)
         {
             
@@ -140,6 +186,25 @@ public class ComponentLibraries
             {
                 logicalNames.add(name);
             }
+            else
+            {
+                if (libraryMapping.libraryName.equals(""))
+                {
+                    boolean isWebappLibrary = true;
+                    for (LibraryMapping otherLibraryMapping : libraryMappings) 
{
+                        if (!libraryMapping.equals(otherLibraryMapping) &&
+                                
name.startsWith(otherLibraryMapping.libraryName + "/"))
+                        {
+                            isWebappLibrary = false;
+                            break;
+                        }
+                    }
+                    if (isWebappLibrary)
+                    {
+                        logicalNames.add(name);
+                    }
+                }
+            }
         }
         
         return logicalNames;
@@ -211,7 +276,12 @@ public class ComponentLibraries
     @Cached(watch = "logicalName")
     public Description getDescription() throws ClassNotFoundException
     {
-        return Class.forName(getClassName()).getAnnotation(Description.class);
+        try {
+            return 
Class.forName(getClassName()).getAnnotation(Description.class);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
     }
 
     public boolean isClassHasTags() throws ClassNotFoundException
@@ -325,5 +395,94 @@ public class ComponentLibraries
             object.put(propertyName, value);
         }
     }
+    
+    public String getGraphvizValue()
+    {
+        return componentDependencyGraphvizGenerator.generate(
+                getClassName(selectedComponent));
+    }
+    
+    public String getClassName(String logicalName)
+    {
+        return componentClassResolver.getClassName(logicalName);
+    }
+    
+    public String getComponentClassName()
+    {
+        return getClassName(selectedComponent);
+    }
+    
+    public List<String> getDependencies()
+    {
+        final String className = 
componentClassResolver.getClassName(selectedComponent);
+        final List<String> dependencies = new ArrayList<>();
+        
dependencies.addAll(componentDependencyRegistry.getDependencies(className, 
DependencyType.INJECT_PAGE));
+        
dependencies.addAll(componentDependencyRegistry.getDependencies(className, 
DependencyType.SUPERCLASS));
+        
dependencies.addAll(componentDependencyRegistry.getDependencies(className, 
DependencyType.USAGE));
+        Collections.sort(dependencies);
+        return dependencies;
+    }
+    
+    public List<String> getDependents()
+    {
+        final String className = 
componentClassResolver.getClassName(selectedComponent);
+        List<String> dependents = new ArrayList<>(
+                componentDependencyRegistry.getDependents(className));
+        Collections.sort(dependents);
+        return dependents;
+    }
+    
+    public String getDisplayLogicalName()
+    {
+        return componentClassResolver.getLogicalName(dependency);
+    }
+    
+    public Object onSelectComponent(String selectedComponent)
+    {
+        this.selectedComponent = selectedComponent;
+        final String className = 
componentClassResolver.getClassName(selectedComponent);
+        if (!componentDependencyRegistry.contains(className)) 
+        {
+            
+            final ClassLoader classLoader = new 
ThrowawayClassLoader(getClass().getClassLoader());
+            
+            try 
+            {
+                
componentDependencyRegistry.register(classLoader.loadClass(className));
+            } catch (ClassNotFoundException e) 
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        return zone.getBody();
+    }
+    
+    public Object getContext()
+    {
+        return logicalName;
+    }
+    
+    public Object onReset()
+    {
+        selectedComponent = null;
+        return zone.getBody();
+    }
+    
+    public Object onShowEverything()
+    {
+        showEverything = true;
+        return zone.getBody();
+    }
+    
+    public Object onShowRestricted()
+    {
+        showEverything = false;
+        return zone.getBody();
+    }
+    
+    public String getLibraryName()
+    {
+        return !libraryMapping.libraryName.isEmpty() ? 
libraryMapping.libraryName : "Webapp's own component library";
+    }
 
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index f11d80548..985b38fa6 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -41,6 +41,7 @@ import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.ExceptionReporter;
 import org.apache.tapestry5.services.PageRenderLinkSource;
 import org.apache.tapestry5.services.URLEncoder;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -123,6 +124,10 @@ public class ExceptionReport extends AbstractInternalPage 
implements ExceptionRe
     @Inject
     @ComponentClasses 
     private InvalidationEventHub classesInvalidationHub;
+    
+    @Inject
+    @Property
+    private PageClassLoaderContextManager pageClassLoaderContextManager;
 
     private String failurePage;
 
@@ -187,6 +192,7 @@ public class ExceptionReport extends AbstractInternalPage 
implements ExceptionRe
 
     public void reportException(Throwable exception)
     {
+        
System.out.print(pageClassLoaderContextManager.getRoot().toRecursiveString());
         rootException = exception;
 
         rootURL = baseURLSource.getBaseURL(request.isSecure());
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
index 974a4a9e4..ba28b0a98 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
@@ -227,6 +227,7 @@ public class PageCatalog
     void onActionFromPreloadPageClassLoaderContexts()
     {
         pageClassLoaderContextManager.preload();
+        alertManager.warn("Component dependency information and page 
classloader contexts preloaded.");
     }
     
     Object onClearPage(String className)
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
new file mode 100644
index 000000000..d8bd3a024
--- /dev/null
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
@@ -0,0 +1,94 @@
+// Copyright 2024 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.
+// 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.tapestry5.corelib.pages;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
+import org.apache.tapestry5.annotations.WhitelistAccessOnly;
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContext;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
+
+/**
+ * Shows information about the page classloader contexts.
+ */
+@UnknownActivationContextCheck(false)
+@WhitelistAccessOnly
+public class PageClassLoaderContexts
+{
+
+    @Inject
+    private PageClassLoaderContextManager pageClassLoaderContextManager;
+    
+    void onRender(MarkupWriter writer)
+    {
+        final PageClassLoaderContext root = 
pageClassLoaderContextManager.getRoot();
+        render(root, writer);
+    }
+
+    private void render(PageClassLoaderContext context, MarkupWriter writer) 
+    {
+        
+        writer.element("li");
+        writer.element("details");
+        writer.element("summary");
+        writer.element("span", "class", "glyphicon glyphicon-zoom-in");
+        writer.end(); // span
+        writer.write(context.getName());
+        writer.write(" (");
+        writer.write(((PlasticClassLoader) 
context.getClassLoader()).getClassLoaderId());
+        writer.write(")");
+        writer.end(); // summary
+
+        if (!context.isRoot() && !context.getClassNames().isEmpty())
+        {
+            final List<String> classNames = new 
ArrayList<>(context.getClassNames());
+            Collections.sort(classNames);
+            writer.element("ul");
+            for (String className : classNames) 
+            {
+                writer.element("li").text(className);
+                writer.end(); // li
+            }
+            writer.end(); // ul
+        }
+        
+        writer.end(); // details
+        
+        final List<PageClassLoaderContext> children = new 
ArrayList<>(context.getChildren());
+        
+        if (!children.isEmpty())
+        {
+            
children.sort(Comparator.comparing(PageClassLoaderContext::getName));
+            writer.element("ul");
+            for (PageClassLoaderContext child : children) 
+            {
+                writer.element("li");
+                render(child, writer);
+                writer.end(); // li
+            }
+            writer.end(); // ul
+        }
+        
+        writer.end(); //li
+    }
+    
+}
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
new file mode 100644
index 000000000..79851bfb0
--- /dev/null
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
@@ -0,0 +1,92 @@
+// Copyright 2024 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.
+// 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.tapestry5.internal;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
+
+public class ThrowawayClassLoader extends ClassLoader
+{
+
+    final private ClassLoader parent;
+    
+    public ThrowawayClassLoader(ClassLoader parent) 
+    {
+        super(parent);
+        this.parent = parent;
+    }
+    
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException 
+    {
+        synchronized (getClassLoadingLock(name)) 
+        
+        {
+            // First, check if the class has already been loaded
+            Class<?> c = findLoadedClass(name);
+            if (c == null) {
+                if (name.contains(".base.") || name.contains(".pages.") ||
+                        name.contains(".components.") || 
name.contains(".mixins."))
+                {
+                    final byte[] bytes = 
PlasticInternalUtils.readBytecodeForClass(parent, name, true);
+                    c = defineClass(name, bytes, 0, bytes.length);
+                    if (resolve) 
+                    {
+                        resolveClass(c);
+                    }
+                }
+                else
+                {
+                    c = parent.loadClass(name);
+                }
+            }
+            return c;
+        }    
+    }
+
+    public static void main(String[] args) throws Exception 
+    {
+        
+        final String className = 
"org.apache.tapestry5.corelib.components.BeanEditor";
+        
+        final ClassLoader parentClassLoader = 
ThrowawayClassLoader.class.getClassLoader();
+        ClassLoader classLoader1 = create(parentClassLoader);
+        ClassLoader classLoader2 = create(parentClassLoader);
+        
+        System.out.println("Parent class loader 1: " + parentClassLoader);
+        System.out.println("Class loader 1       : " + classLoader1);
+        System.out.println("Class loader 2       : " + classLoader2);
+        
+        Class class1 = classLoader1.loadClass(className);
+        Class class2 = classLoader2.loadClass(className);
+        Class class3 = parentClassLoader.loadClass(className);
+
+        System.out.println("Class 1 : " + class1.getClassLoader());
+        System.out.println("Class 2 : " + class2.getClassLoader());
+        System.out.println("Class 3 : " + class3.getClassLoader());        
+        
+        assertNotEquals(class1, class2);
+        assertNotEquals(class1, class3);
+        assertNotEquals(class2, class3);
+        
+    }
+
+    private static ClassLoader create(final ClassLoader parentClassLoader) {
+//        return 
TapestryInternalUtils.createThrowawayClassloader(parentClassLoader);
+        return new ThrowawayClassLoader(parentClassLoader);
+    }
+    
+}
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
index 24671c003..ef9cf1017 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
@@ -59,12 +59,7 @@ public class ComponentDependencyGraphvizGeneratorImpl 
implements ComponentDepend
         for (String className : classNames) 
         {
             createNode(className, nodeMap);
-            for (DependencyType dependencyType : DependencyType.values()) 
-            {
-                addDependencies(className, allClasses, dependencyType, 
nodeMap);
-            }
-            
-
+            addDependencies(className, allClasses, nodeMap);
         }
         
         final List<Node> nodes = new ArrayList<>(nodeMap.values());
@@ -141,16 +136,19 @@ public class ComponentDependencyGraphvizGeneratorImpl 
implements ComponentDepend
         return label.replace('.', '_').replace('/', '_');
     }
 
-    private void addDependencies(String className, Set<String> allClasses, 
DependencyType type, Map<String, Node> nodeMap) 
+    private void addDependencies(String className, Set<String> allClasses, 
Map<String, Node> nodeMap) 
     {
         if (!allClasses.contains(className))
         {
             createNode(className, nodeMap);
-            for (String dependency : 
componentDependencyRegistry.getDependencies(className, type))
+            allClasses.add(className);
+            for (DependencyType type : DependencyType.values()) 
             {
-                addDependencies(dependency, allClasses, type, nodeMap);
+                for (String dependency : 
componentDependencyRegistry.getDependencies(className, type))
+                {
+                    addDependencies(dependency, allClasses, nodeMap);
+                }
             }
-            allClasses.add(className);
         }
     }
 
@@ -188,7 +186,8 @@ public class ComponentDependencyGraphvizGeneratorImpl 
implements ComponentDepend
         }
     }
 
-    private static final class Node {
+    private static final class Node 
+    {
 
         final private String id;
         final private String className;
@@ -204,5 +203,12 @@ public class ComponentDependencyGraphvizGeneratorImpl 
implements ComponentDepend
             this.dependencies.addAll(dependencies);
         }
 
+        @Override
+        public String toString() 
+        {
+            return "Node [id=" + id + ", className=" + className + ", 
dependencies=" + dependencies + ", label=" + label + "]";
+        }
+
     }
+    
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
index ad79630f4..6c02ae75f 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
@@ -67,6 +67,13 @@ public interface ComponentDependencyRegistry {
      */
     void register(Class<?> clasz);
 
+    /**
+     * Register all the dependencies of a given class and uses a given
+     * classloader to load other classes if needed.
+     * @since 5.8.7
+     */
+    void register(Class<?> clasz, ClassLoader classLoader);
+
     /**
      * Register all the dependencies of a given component.
      */
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
index 090d71268..e8099b309 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
@@ -179,9 +179,15 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
             INVALIDATIONS_DISABLED.set(0);
         });
     }
-    
+
     @Override
     public void register(Class<?> component) 
+    {
+        register(component, component.getClassLoader());
+    }
+    
+    @Override
+    public void register(Class<?> component, ClassLoader classLoader) 
     {
         
         final String className = component.getName();
@@ -189,7 +195,7 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
         Consumer<Class<?>> processClass = furtherDependencies::add;
         Consumer<String> processClassName = s -> {
             try {
-                
furtherDependencies.add(component.getClassLoader().loadClass(s));
+                furtherDependencies.add(classLoader.loadClass(s));
             } catch (ClassNotFoundException e) {
                 throw new RuntimeException(e);
             }
@@ -216,6 +222,7 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
             {
                 final Class<?> dependency = field.getType();
                 add(component, dependency, DependencyType.INJECT_PAGE);
+                processClass.accept(dependency);
             }
             
             // @Component
@@ -245,7 +252,7 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
             if (!alreadyProcessed.contains(dependencyClassName)
                     && 
plasticManager.shouldInterceptClassLoading(dependency.getName()))
             {
-                register(dependency);
+                register(dependency, classLoader);
             }
         }
         
@@ -261,14 +268,14 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
     private void registerTemplate(Class<?> component, Consumer<String> 
processClassName) 
     {
         // TODO: implement caching of template dependency information, probably
-        // by listening separaterly to ComponentTemplateSource to invalidate 
caches
+        // by listening separately to ComponentTemplateSource to invalidate 
caches
         // just when template changes.
         
         final String className = component.getName();
         ComponentModel mock = new ComponentModelMock(component, 
isPage(className));
         final Resource templateResource = 
componentTemplateLocator.locateTemplate(mock, Locale.getDefault());
         String dependency;
-        if (templateResource != null)
+        if (templateResource != null && templateResource.exists())
         {
             final ComponentTemplate template = 
templateParser.parseTemplate(templateResource);
             final List<TemplateToken> tokens = new LinkedList<>();
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
index 8a5344c72..4a2b296b5 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
@@ -356,7 +356,23 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
 
     public Instantiator getInstantiator(final String className)
     {
-        return classToInstantiator.computeIfAbsent(className, 
this::createInstantiatorForClass);
+        Instantiator instantiator;
+        if (multipleClassLoaders)
+        {
+            instantiator = classToInstantiator.get(className);
+
+            if (instantiator == null)
+            {
+                instantiator = createInstantiatorForClass(className);
+                classToInstantiator.put(className, instantiator);
+            }
+
+        }
+        else 
+        {
+            instantiator = classToInstantiator.computeIfAbsent(className, 
this::createInstantiatorForClass);
+        }
+        return instantiator;
     }
     
     private static final ThreadLocal<Set<String>> OPEN_INSTANTIATORS = 
@@ -376,6 +392,30 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
                         OPEN_INSTANTIATORS.get().add(className);
                         
                         componentDependencyRegistry.disableInvalidations();
+                        
+                        // Make sure the dependencies have been processed in 
case
+                        // there was some invalidation going on and they're 
not there.
+
+                        final Set<String> dependencies = new HashSet<>();
+                        dependencies.addAll(
+                                
componentDependencyRegistry.getDependencies(className, DependencyType.USAGE));
+//                        dependencies.addAll(
+//                                
componentDependencyRegistry.getDependencies(className, 
DependencyType.SUPERCLASS));
+                        for (String dependency : dependencies)
+                        {
+                            if (!OPEN_INSTANTIATORS.get().contains(dependency))
+                            {
+                                if (multipleClassLoaders)
+                                {
+                                    getInstantiator(dependency);
+                                }
+                                else
+                                {
+                                    createInstantiatorForClass(dependency);
+                                }
+                            }
+                        }
+                        
                         PageClassLoaderContext context;
                         try
                         {
@@ -386,20 +426,21 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
                             componentDependencyRegistry.enableInvalidations();
                         }
                         
-                        // Make sure the dependencies have been processed in 
case
-                        // there was some invalidation going on and they're 
not there.
-
-                        // TODO: maybe we need superclasses here too?
-                        final Set<String> dependencies = 
componentDependencyRegistry.getDependencies(className, DependencyType.USAGE);
-                        for (String dependency : dependencies)
+                        ClassInstantiator<Component> plasticInstantiator;
+                        try 
                         {
-                            if (!OPEN_INSTANTIATORS.get().contains(dependency))
+                            if 
(className.equals("org.apache.tapestry5.integration.app1.pages.GridInLoopDemo"))
 {
+                                System.out.println();
+                            }
+                            plasticInstantiator = 
context.getPlasticManager().getClassInstantiator(className);
+                            if (multipleClassLoaders)
                             {
-                                createInstantiatorForClass(dependency);
+                                
context.getPlasticManager().getClassLoader().loadClass(className);
                             }
+                        } catch (Exception e) {
+                            
System.out.println(pageClassLoaderContextManager.getRoot().toRecursiveString());
+                            throw new RuntimeException(e);
                         }
-                        
-                        ClassInstantiator<Component> plasticInstantiator = 
context.getPlasticManager().getClassInstantiator(className);
                         final ComponentModel model = 
classToModel.get(className);
                         
                         OPEN_INSTANTIATORS.get().remove(className);
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
index 9c01847b3..b167b39ba 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
@@ -14,6 +14,11 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.util.Set;
+
+import org.apache.tapestry5.SymbolConstants;
+import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.services.ComponentClassResolver;
 
@@ -22,15 +27,58 @@ public class ComponentModelSourceImpl implements 
ComponentModelSource
     private final ComponentClassResolver resolver;
 
     private final ComponentInstantiatorSource source;
+    
+    private final ComponentDependencyRegistry componentDependencyRegistry;
+    
+    private final PageSource pageSource;
+    
+    private final boolean multipleClassLoaders;
 
-    public ComponentModelSourceImpl(ComponentClassResolver resolver, 
ComponentInstantiatorSource source)
+    public ComponentModelSourceImpl(ComponentClassResolver resolver, 
ComponentInstantiatorSource source,
+            ComponentDependencyRegistry componentDependencyRegistry,
+            PageSource pageSource,
+            @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+            @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean 
multipleClassLoaders)
     {
         this.resolver = resolver;
         this.source = source;
+        this.componentDependencyRegistry = componentDependencyRegistry;
+        this.pageSource = pageSource;
+        this.multipleClassLoaders = !productionMode && multipleClassLoaders;
     }
 
     public ComponentModel getModel(String componentClassName)
     {
+        if (multipleClassLoaders && isPage(componentClassName))
+        {
+            
+            if (componentClassName.contains("GridDemo") || 
+                    componentClassName.contains("GridInLoopDemo"))
+            {
+                System.out.println();
+            }
+            
+            final Set<String> superclasses = 
componentDependencyRegistry.getDependencies(
+                    componentClassName, DependencyType.SUPERCLASS);
+            
+            if (!superclasses.isEmpty())
+            {
+                final String superclass = superclasses.iterator().next();
+                if (isPage(superclass))
+                {
+                    getModel(superclass);
+                    try
+                    {
+                        
pageSource.getPage(resolver.getLogicalName(componentClassName));
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                        //ignore
+                    }
+                }
+            }
+        }
         return source.getInstantiator(componentClassName).getModel();
     }
 
@@ -38,4 +86,9 @@ public class ComponentModelSourceImpl implements 
ComponentModelSource
     {
         return getModel(resolver.resolvePageNameToClassName(pageName));
     }
+    
+    private boolean isPage(String componentClassName)
+    {
+        return componentClassName.contains(".pages.");
+    }
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
index c4985b510..c2938bf1e 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
@@ -14,12 +14,14 @@
 
 package org.apache.tapestry5.modules;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.OrderedConfiguration;
 import 
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
 import 
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGeneratorImpl;
 import org.apache.tapestry5.internal.services.dashboard.DashboardManagerImpl;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.dashboard.DashboardManager;
 import org.apache.tapestry5.services.dashboard.DashboardTab;
 
@@ -32,11 +34,17 @@ public class DashboardModule
     }
 
     @Contribute(DashboardManager.class)
-    public static void defaultTabs(OrderedConfiguration<DashboardTab> 
configuration)
+    public static void defaultTabs(OrderedConfiguration<DashboardTab> 
configuration,
+            @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode, 
+            @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean 
multipleClassLoaders)
     {
         configuration.add("Pages", new DashboardTab("Pages", 
"core/PageCatalog"));
         configuration.add("Services", new DashboardTab("Services", 
"core/ServiceStatus"));
         configuration.add("Libraries", new DashboardTab("ComponentLibraries", 
"core/ComponentLibraries"));
         configuration.add("PageDependencyGraph", new 
DashboardTab("PageDependencyGraph", "core/PageDependencyGraph"));
+        if (!productionMode && multipleClassLoaders)
+        {
+            configuration.add("PageClassLoaderContexts", new 
DashboardTab("PageClassLoaderContexts", "core/PageClassLoaderContexts"));
+        }
     }
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
index ae6e76a77..b658553bb 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
@@ -97,14 +97,7 @@ public class PageLoadModule
     {
         if (!productionMode && multipleClassLoaders)
         {
-            // Preload the page activation context tree for the already known 
classes
-            for (int i = 0; i < 5; i++)
-            {
-                for (String className : 
componentDependencyRegistry.getClassNames()) 
-                {
-                    pageClassLoaderContextManager.get(className);
-                }
-            }
+            pageClassLoaderContextManager.preloadContexts();
         }
         // Preload the dependency information for all pages 
         // when in production mode. Without that, exceptions during
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
index b37f0a0fc..fa91b70f3 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
@@ -95,5 +95,11 @@ public interface PageClassLoaderContextManager
      * page classloader contexts.
      */
     void preload();
+    
+    /**
+     * Preloads the page classloader contexts.
+     * @since 5.8.7
+     */
+    void preloadContexts();
 
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
index afe8a6746..7a3df12f7 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
@@ -28,6 +28,10 @@ import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.internal.util.TapestryException;
 import org.apache.tapestry5.commons.services.InvalidationEventHub;
 import org.apache.tapestry5.commons.services.PlasticProxyFactory;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
+import org.apache.tapestry5.internal.plastic.ClassLoaderDelegate;
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
 import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
 import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
 import 
org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub;
@@ -66,6 +70,8 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
     
     private static final AtomicInteger MERGED_COUNTER = new AtomicInteger(1);
     
+    private final static ThreadLocal<Boolean> CONTEXTS_CHANGED = 
ThreadLocal.withInitial(() -> false);
+    
     private Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider;
     
     private PageClassLoaderContext root;
@@ -83,7 +89,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
         this.componentClassResolver = componentClassResolver;
         this.invalidationHub = invalidationHub;
         this.componentClassesInvalidationEventHub = 
componentClassesInvalidationEventHub;
-        this.multipleClassLoaders = multipleClassLoaders;
+        this.multipleClassLoaders = multipleClassLoaders && !productionMode;
         this.productionMode = productionMode;
         invalidationHub.addInvalidationCallback(this::listen);
         NESTED_MERGE_COUNT.set(0);
@@ -147,7 +153,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
 
             if (!className.equals(enclosingClassName))
             {
-                loadClass(className, context);
+                loadClass(enclosingClassName, context);
             }
             
         }
@@ -227,7 +233,6 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
         if (context == null)
         {
             
-            LOGGER.debug("Processing class {}", className);
             
             // Class isn't in a controlled package, so it doesn't get 
transformed
             // and should go for the root context, which is never thrown out.
@@ -235,6 +240,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             {
                 context = root;
             } else {
+                LOGGER.debug("Processing class {}", className);
                 if (!productionMode && (
                         !componentDependencyRegistry.contains(className) ||
                         !multipleClassLoaders))
@@ -267,7 +273,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
                     {
                         PageClassLoaderContext dependencyContext = 
root.findByClassName(dependency);
                         // Avoid infinite recursion loops
-                        if (!alreadyProcessed.contains(dependency))
+                        if (multipleClassLoaders || 
!alreadyProcessed.contains(dependency))
                         {
                             if (dependencyContext == null)
                             {
@@ -319,14 +325,31 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
                         context.getParent().addChild(context);
                     }
                     
-                    // Ensure non-page class is initialized in the correct 
context and classloader.
-                    // Pages get their own context and classloader, so this 
initialization
-                    // is both non-needed and a cause for an NPE if it happens.
-                    if (!componentClassResolver.isPage(className)
-                            || 
componentDependencyRegistry.getDependencies(className, 
DependencyType.USAGE).isEmpty())
-                    {
-                        loadClass(className, context);
-                    }
+//                    // Ensure non-page class is initialized in the correct 
context and classloader.
+//                    // Pages get their own context and classloader, so this 
initialization
+//                    // is both non-needed and a cause for an NPE if it 
happens.
+//                    if (!componentClassResolver.isPage(className)
+//                            || 
componentDependencyRegistry.getDependencies(className, 
DependencyType.USAGE).isEmpty())
+//                    {
+//                        // Avoiding "attempted duplicate class definition" 
due to 
+//                        // loading a class into a classloader which already 
loaded
+//                        // that class before.
+//                        if (!context.getClassNames().contains(className))
+//                        {
+//                            list.get().add(className);
+//                            try {
+//                                loadClass(className, context);
+//                            }
+//                            catch (LinkageError e) {
+//                                
System.out.println("-------------------------");
+//                                for (String c : list.get()) {
+//                                    System.out.println(c);
+//                                }
+//                                
System.out.println("-------------------------");
+//                                throw e;
+//                            }
+//                        }
+//                    }
 
                     if (multipleClassLoaders)
                     {
@@ -334,6 +357,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
                     }
                     
                 }
+                CONTEXTS_CHANGED.set(true);
             }
             
         }
@@ -347,6 +371,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
         Set<String> dependencies = new HashSet<>();
         
dependencies.addAll(componentDependencyRegistry.getDependencies(className, 
DependencyType.USAGE));
         
dependencies.addAll(componentDependencyRegistry.getDependencies(className, 
DependencyType.SUPERCLASS));
+        dependencies.remove(className); // Just in case
         return Collections.unmodifiableSet(dependencies);
     }
 
@@ -387,12 +412,19 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             }
             LOGGER.debug(builder.toString().trim());
         }
-        
+
+        final Set<String> classesToReprocess = multipleClassLoaders ? new 
HashSet<>() : Collections.emptySet();
         Set<PageClassLoaderContext> allContextsIncludingDescendents = new 
HashSet<>();
+        
         for (PageClassLoaderContext context : contextDependencies) 
         {
+            final Set<PageClassLoaderContext> descendents = 
context.getDescendents();
             allContextsIncludingDescendents.add(context);
-            allContextsIncludingDescendents.addAll(context.getDescendents());
+            allContextsIncludingDescendents.addAll(descendents);
+            for (PageClassLoaderContext descendent : descendents) 
+            {
+                addClassNames(descendent, classesToReprocess);
+            }
         }
 
         PageClassLoaderContext merged;
@@ -460,6 +492,18 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
         
         parent.addChild(merged);
         
+        // Recreating contexts for classes that got invalidated but
+        // aren't part of the new merged context (i.e. the classes
+        // in contexts are are descendent of the merged contexts).
+//        if (!classesToReprocess.isEmpty())
+//        {
+//            final List<String> sorted = new ArrayList<>(classesToReprocess);
+//            for (String className : sorted) 
+//            {
+//                get(className);
+//            }
+//        }
+        
 //        for (String className : classNames) 
 //        {
 //            loadClass(className, merged);
@@ -647,10 +691,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
     public void preload() 
     {
         
-        final PageClassLoaderContext context = new 
PageClassLoaderContext(PageClassLoaderContext.UNKOWN_CONTEXT_NAME, root, 
-                Collections.emptySet(), 
-                plasticProxyFactoryProvider.apply(root.getClassLoader()),
-                this::get);
+        final ClassLoader classLoader = new 
ThrowawayClassLoader(PageClassLoaderContext.class.getClassLoader());
         
         final List<String> pageNames = componentClassResolver.getPageNames();
         final List<String> classNames = new ArrayList<>(pageNames.size());
@@ -664,7 +705,7 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             try 
             {
                 final String className = 
componentClassResolver.resolvePageNameToClassName(page);
-                
componentDependencyRegistry.register(context.getClassLoader().loadClass(className));
+                
componentDependencyRegistry.register(classLoader.loadClass(className));
                 classNames.add(className);
             } catch (ClassNotFoundException e) 
             {
@@ -683,27 +724,57 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             LOGGER.info(String.format("Dependency information gathered in %.3f 
ms", (finish - start) / 1000.0));
         }
         
-        context.invalidate();
+        preloadContexts();
         
+    }
+
+    @Override
+    public void preloadContexts() 
+    {
+        long start;
+        long finish;
         LOGGER.info("Starting preloading page classloader contexts");
         
         start = System.currentTimeMillis();
         
-        for (int i = 0; i < 10; i++)
+        final List<String> classNames = new ArrayList<>();
+        classNames.addAll(componentClassResolver.getMixinNames().stream()
+                .map(s -> 
componentClassResolver.resolveMixinTypeToClassName(s))
+                .sorted()
+                .collect(Collectors.toList()));
+        
+        classNames.addAll(componentClassResolver.getComponentNames().stream()
+                .map(s -> 
componentClassResolver.resolveComponentTypeToClassName(s))
+                .sorted()
+                .collect(Collectors.toList()));        
+        
+        classNames.addAll(componentClassResolver.getPageNames().stream()
+                .map(s -> componentClassResolver.resolvePageNameToClassName(s))
+                .sorted()
+                .collect(Collectors.toList()));
+        
+        int runs = 0;
+        
+        // The run counter check is to just avoid possible infinite loops,
+        // although that's very unlikely.
+        CONTEXTS_CHANGED.set(true);
+        while (runs < 5000 && CONTEXTS_CHANGED.get() == true)
         {
+            runs++;
+            CONTEXTS_CHANGED.set(false);
             for (String className : classNames) 
             {
                 get(className);
             }
+            System.out.println(CONTEXTS_CHANGED.get());
         }
         
         finish = System.currentTimeMillis();
 
         if (LOGGER.isInfoEnabled())
         {
-            LOGGER.info(String.format("Preloading of page classloadercontexts 
finished in %.3f ms", (finish - start) / 1000.0));
+            LOGGER.info(String.format("Preloading of page classloader contexts 
finished in %.3f ms (%d passes)", (finish - start) / 1000.0, runs));
         }
-
     }
     
 }
diff --git 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
index e5323cb5a..a43b79659 100644
--- 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
+++ 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
@@ -3,125 +3,180 @@
 
     <h2 t:type="If" t:test="productionMode">This page is disabled in 
production mode</h2>
     
-    <t:if t:test="!productionMode">
-           <h1><strong>${libraryMappings.size()}</strong> component libraries 
used</h1>
-           
-           
-           <ul id="libraryList" class="list-group">
-               <li t:type="Loop" t:source="libraryMappings" 
t:value="libraryMapping" class="list-group-item">
-                       <a href="#${libraryClientId}">
-                               <code>${libraryMapping.libraryName}</code> 
<t:if test="info">: ${info.name}</t:if>
-                       </a>
-                       <p t:type="If" t:test="info?.description">
-                               ${info.description}
-                       </p>
-                       <p class="tags" t:type="If" t:test="!info?.tags.empty">
-                               Tags: 
-                                       <span t:type="Loop" 
t:source="info.tags" t:value="var:tag" class="badge" 
-                                               style="margin-right: 0.3em; 
font-size: 0.75em">
-                                               ${var:tag}
-                                       </span>
-                               </p>
-               </li>
-           </ul>
-           
-       
-               <div id="libraries">
-               
-                       <div class="libraryInfo" t:type="Loop" 
t:source="libraryMappings" t:value="libraryMapping" id="${libraryClientId}">
-               
-                               <h2><code>${libraryMapping.libraryName}</code> 
<t:if test="info">: ${info.name}</t:if></h2>
-                               
-                               <t:if test="info">
+    <t:If t:test="!productionMode">
+               <t:zone>
                        
-                                   <dl class="dl-horizontal">
-                                   
-                                       <dt>Homepage</dt>
-                                       <dd class="homepage">
-                                               <t:if test="info.homepageUrl" 
else="message:not-informed">
-                                                       <a 
href="${info.homepageUrl}">${info.homepageUrl}</a>
-                                               </t:if>
-                                       </dd>
-                                       
-                                       <dt>Version</dt>
-                                       <dd class="version">
-                                               <t:if test="info.version" 
else="message:not-informed">
-                                                       ${info.version}
-                                               </t:if>
-                                       </dd>
-
-                                       <dt>Tapestry version</dt>
-                                       <dd class="tapestryVersion">
-                                               <t:if 
test="info.tapestryVersion" else="message:not-informed">
-                                                       ${info.tapestryVersion}
-                                               </t:if>
-                                       </dd>
+                       <p>
+                               <t:if test="showEverything">
+                                       <p:then>
+                                               <t:eventLink 
event="showRestricted" zone="^">
+                                                       Don't show webapp 
library
+                                               </t:eventLink>
+                                       </p:then>                               
+                                       <p:else>
+                                               <t:eventLink 
event="showEverything" zone="^">
+                                                       Show webapp library
+                                               </t:eventLink>
+                                       </p:else>       
+                               </t:if>
+                       </p>
+                       
+                       <t:If test="!selectedComponent">
+                       
+                           <h1><strong>${libraryMappings.size()}</strong> 
component libraries used</h1>
+                           
+                           <ul id="libraryList" class="list-group">
+                               <li t:type="Loop" t:source="libraryMappings" 
t:value="libraryMapping" class="list-group-item">
+                                       <a href="#${libraryClientId}">
+                                               <code>${libraryName}</code> 
<t:if test="info">: ${info.name}</t:if>
+                                       </a>
+                                       <p t:type="If" 
t:test="info?.description">
+                                               ${info.description}
+                                       </p>
+                                       <p class="tags" t:type="If" 
t:test="!info?.tags.empty">
+                                               Tags: 
+                                                       <span t:type="Loop" 
t:source="info.tags" t:value="var:tag" class="badge" 
+                                                               
style="margin-right: 0.3em; font-size: 0.75em">
+                                                               ${var:tag}
+                                                       </span>
+                                               </p>
+                               </li>
+                           </ul>
+                           
+                       
+                               <div id="libraries">
                                
-                                       <dt>Documentation URL</dt>
-                                       <dd class="documentationUrl">
-                                               <t:if 
test="info.documentationUrl" else="message:not-informed">
-                                                       <a 
href="${info.documentationUrl}">${info.documentationUrl}</a>
-                                               </t:if>
-                                       </dd>
-                                       
-                                       <dt>JavaDoc URL</dt>
-                                       <dd class="javadocUrl">
-                                               <t:if test="info.javadocUrl" 
else="message:not-informed">
-                                                       <a 
href="${info.javadocUrl}">${info.javadocUrl}</a>
-                                               </t:if>
-                                       </dd>
-                                       
-                                       <t:if 
test="info.dependencyManagementInfoPresent">
-                                               <dt>Dependency information</dt>
-                                               <dd 
class="dependencyInformation">
-                                                       Group id <code 
class="groupId">${info.groupId}</code>,
-                                                       artifact id <code 
class="groupId">${info.artifactId}</code>,
-                                                       version <code 
class="groupId">${info.version}</code>
-                                                       <br/>
-                                                       <a 
href="${info.dependencyManagementInfoUrl}" 
-                                                               target="_blank">
-                                                               <em>More 
information at Maven Central Respository</em>
-                                                       </a>
-                                               </dd>
-                                       </t:if>
-                                       
-                                       <dt>Source browse URL</dt>
-                                       <dd class="sourceBrowseUrl">
-                                               <t:if 
test="info.sourceBrowseUrl" else="message:not-informed">
-                                                       <a 
href="${info.sourceBrowseUrl}">${info.sourceBrowseUrl}</a>
-                                               </t:if>
-                                       </dd>
-                                       
-                                       <dt>Issue tracker URL</dt>
-                                       <dd class="issueTrackerUrl">
-                                               <t:if 
test="info.issueTrackerUrl" else="message:not-informed">
-                                                       <a 
href="${info.issueTrackerUrl}">${info.issueTrackerUrl}</a>
-                                               </t:if>
-                                       </dd>
-                                       
-                                       <dt></dt>
-                                       <dd class="jsonDescription">
-                                               <a t:type="EventLink" 
t:event="json" t:context="libraryMapping.libraryName">Generate JSON 
description</a>
-                                       </dd>
+                                       <div class="libraryInfo" t:type="Loop" 
t:source="libraryMappings" t:value="libraryMapping" id="${libraryClientId}">
                                
-                                   </dl>
-                           
-                               </t:if>
+                                               <h2><code>${libraryName}</code> 
<t:if test="info">: ${info.name}</t:if></h2>
+                                               
+                                               <t:if test="info">
+                                       
+                                                   <dl class="dl-horizontal">
+                                                   
+                                                       <dt>Homepage</dt>
+                                                       <dd class="homepage">
+                                                               <t:if 
test="info.homepageUrl" else="message:not-informed">
+                                                                       <a 
href="${info.homepageUrl}">${info.homepageUrl}</a>
+                                                               </t:if>
+                                                       </dd>
+                                                       
+                                                       <dt>Version</dt>
+                                                       <dd class="version">
+                                                               <t:if 
test="info.version" else="message:not-informed">
+                                                                       
${info.version}
+                                                               </t:if>
+                                                       </dd>
+               
+                                                       <dt>Tapestry 
version</dt>
+                                                       <dd 
class="tapestryVersion">
+                                                               <t:if 
test="info.tapestryVersion" else="message:not-informed">
+                                                                       
${info.tapestryVersion}
+                                                               </t:if>
+                                                       </dd>
+                                               
+                                                       <dt>Documentation 
URL</dt>
+                                                       <dd 
class="documentationUrl">
+                                                               <t:if 
test="info.documentationUrl" else="message:not-informed">
+                                                                       <a 
href="${info.documentationUrl}">${info.documentationUrl}</a>
+                                                               </t:if>
+                                                       </dd>
+                                                       
+                                                       <dt>JavaDoc URL</dt>
+                                                       <dd class="javadocUrl">
+                                                               <t:if 
test="info.javadocUrl" else="message:not-informed">
+                                                                       <a 
href="${info.javadocUrl}">${info.javadocUrl}</a>
+                                                               </t:if>
+                                                       </dd>
+                                                       
+                                                       <t:if 
test="info.dependencyManagementInfoPresent">
+                                                               <dt>Dependency 
information</dt>
+                                                               <dd 
class="dependencyInformation">
+                                                                       Group 
id <code class="groupId">${info.groupId}</code>,
+                                                                       
artifact id <code class="groupId">${info.artifactId}</code>,
+                                                                       version 
<code class="groupId">${info.version}</code>
+                                                                       <br/>
+                                                                       <a 
href="${info.dependencyManagementInfoUrl}" 
+                                                                               
target="_blank">
+                                                                               
<em>More information at Maven Central Respository</em>
+                                                                       </a>
+                                                               </dd>
+                                                       </t:if>
+                                                       
+                                                       <dt>Source browse 
URL</dt>
+                                                       <dd 
class="sourceBrowseUrl">
+                                                               <t:if 
test="info.sourceBrowseUrl" else="message:not-informed">
+                                                                       <a 
href="${info.sourceBrowseUrl}">${info.sourceBrowseUrl}</a>
+                                                               </t:if>
+                                                       </dd>
+                                                       
+                                                       <dt>Issue tracker 
URL</dt>
+                                                       <dd 
class="issueTrackerUrl">
+                                                               <t:if 
test="info.issueTrackerUrl" else="message:not-informed">
+                                                                       <a 
href="${info.issueTrackerUrl}">${info.issueTrackerUrl}</a>
+                                                               </t:if>
+                                                       </dd>
+                                                       
+                                                       <dt></dt>
+                                                       <dd 
class="jsonDescription">
+                                                               <a 
t:type="EventLink" t:event="json" 
t:context="libraryMapping.libraryName">Generate JSON description</a>
+                                                       </dd>
+                                               
+                                                   </dl>
+                                           
+                                               </t:if>
+                       
+                                               <p t:type="If" t:test="!info" 
class="noInformation">No additional information provided for 
<code>${libraryMapping.libraryName}</code>.</p>
+                                               
+                       <!--                    <div t:type="Zone" t:id="pages" 
id="prop:libraryClientZoneClientId"> -->
+                       <!--                    </div> -->
+                       
+                                               <div t:type="Delegate" 
to="componentsTable"></div>
+                                               <div t:type="Delegate" 
to="pagesTable"></div>
+                                               <div t:type="Delegate" 
to="mixinsTable"></div>
+                                               
+                                       </div>
+                                       
+                               </div>
        
-                               <p t:type="If" t:test="!info" 
class="noInformation">No additional information provided for 
<code>${libraryMapping.libraryName}</code>.</p>
+                       </t:If>                 
                                
-       <!--                    <div t:type="Zone" t:id="pages" 
id="prop:libraryClientZoneClientId"> -->
-       <!--                    </div> -->
-       
-                               <div t:type="Delegate" 
to="componentsTable"></div>
-                               <div t:type="Delegate" to="pagesTable"></div>
-                               <div t:type="Delegate" to="mixinsTable"></div>
+                       <t:If test="selectedComponent">
+                               <h1><strong>${selectedComponent}</strong> 
(${componentClassName})</h1>
                                
-                       </div>
-                       
-               </div>
-               
-       </t:if>
+                               <p>
+                                       <t:eventLink event="reset" 
zone="^">Back to component listing</t:eventLink>
+                               </p>
+                               
+                           <div class="panel panel-default vert-offset">
+                               <div class="panel-heading">Component dependency 
information for ${selectedComponent} (just direct dependencies)</div>
+                               <div class="panel-body">
+                                       <ul>
+                                               <li t:type="Loop" 
t:value="dependency" t:source="dependencies">
+                                                       ${displayLogicalName} 
(${dependency})
+                                               </li>
+                                       </ul>
+                               </div>
+                           </div>
+                           <div class="panel panel-default vert-offset">
+                               <div class="panel-heading">Components depending 
on ${selectedComponent} (just direct dependencies)</div>
+                               <div class="panel-body">
+                                       <ul>
+                                               <li t:type="Loop" 
t:value="dependency" t:source="dependents">
+                                                       ${displayLogicalName} 
(${dependency})
+                                               </li>
+                                       </ul>
+                               </div>
+                           </div>
+                           <div class="panel panel-default vert-offset">
+                               <div 
class="panel-heading">${selectedComponent}'s dependency tree</div>
+                               <div class="panel-body">
+                                       <t:graphviz value="graphvizValue" 
showSource="true"/>
+                               </div>
+                           </div>
+                       </t:If>
+               </t:zone>               
+       </t:If>
        
        <t:block id="classesTable">
                <div t:type="If" t:test="!logicalNames.empty">
@@ -138,7 +193,11 @@
                                </thead>
                                <tbody>
                                        <tr t:type="Loop" 
t:source="logicalNames" t:value="logicalName">
-                                               
<td><code>${simpleLogicalName}</code></td>
+                                               <td>
+                                                       <t:eventLink 
event="selectComponent" t:context="context" t:zone="^">
+                                                               
<code>${simpleLogicalName}</code>
+                                                       </t:eventLink>
+                                               </td>
                                                <td>${description?.text()}</td>
                                                <td>
                                                        <ul t:type="If" 
t:test="classHasTags" style="padding: 0; margin: 0;">
@@ -163,5 +222,5 @@
                        </table>
                </div>
        </t:block>
-
+       
 </t:block>
diff --git 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
index bdac0f8b1..2eea91036 100644
--- 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
+++ 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
@@ -77,7 +77,7 @@
                    <div class="panel panel-default vert-offset" t:type="If" 
t:test="selectedPage">
                        <div class="panel-heading">${selectedPage.name}'s 
dependency tree</div>
                        <div class="panel-body">
-                               <t:graphviz value="graphvizValue"/>
+                               <t:graphviz value="graphvizValue" 
showSource="true"/>
                        </div>
                    </div>
            </t:if>
diff --git 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
new file mode 100644
index 000000000..8e39c0f0e
--- /dev/null
+++ 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
@@ -0,0 +1,17 @@
+<t:block id="content" 
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd";
+         xmlns:p="tapestry:parameter">
+       <h1>Page classloader contexts <t:glyphicon name="list" /></h1>
+       <style>
+               #tree li {
+                       display: block;
+                       margin-top: 0.25em;
+                       line-height: 2m;
+               }
+               #tree .glyphicon {
+                       margin-right: 0.25em;
+               }
+       </style>
+       <ul id="tree">          
+               <t:trigger event="render"/>
+       </ul>
+</t:block>
\ No newline at end of file
diff --git a/tapestry-core/src/test/app1/AtComponentType.tml 
b/tapestry-core/src/test/app1/AtComponentType.tml
new file mode 100644
index 000000000..50a2bf92e
--- /dev/null
+++ b/tapestry-core/src/test/app1/AtComponentType.tml
@@ -0,0 +1,6 @@
+<t:border xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";
+          xmlns:p="tapestry:parameter">
+
+    <input t:id="input"/>
+
+</t:border>
\ No newline at end of file
diff --git a/tapestry-core/src/test/app1/GridDemo.tml 
b/tapestry-core/src/test/app1/GridDemo.tml
index 5a4f12eb7..650ecf163 100644
--- a/tapestry-core/src/test/app1/GridDemo.tml
+++ b/tapestry-core/src/test/app1/GridDemo.tml
@@ -1,6 +1,6 @@
 <html t:type="Border" 
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";>
 
-    <h1>Grid Demo</h1>
+    <h1>Grid Demo!!!!</h1>
 
     <table t:type="grid" t:id="grid" source="tracks" row="track">
         <t:parameter name="ratingheader">
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
new file mode 100644
index 000000000..de0627822
--- /dev/null
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
@@ -0,0 +1,24 @@
+// Copyright 2024 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.
+// 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.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.Field;
+import org.apache.tapestry5.annotations.Component;
+
+public class AtComponentType
+{
+    @Component(type = "textfield")
+    private Field input;
+}
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
index 1d064920d..888794360 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.integration.app1.pages;
 
+import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.InjectComponent;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.corelib.components.Grid;
@@ -48,4 +49,9 @@ public class GridDemo
     {
         grid.getSortModel().updateSort("rating");
     }
+    
+    public void afterRender(MarkupWriter writer)
+    {
+        writer.getDocument().find("html/body").elementAt(1, "p").text("@@@@@");
+    }
 }
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
index 8b89e7216..6cc761ac3 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
@@ -19,17 +19,20 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.tapestry5.commons.MappedConfiguration;
+import org.apache.tapestry5.commons.Resource;
 import org.apache.tapestry5.corelib.base.AbstractComponentEventLink;
 import org.apache.tapestry5.corelib.base.AbstractField;
 import org.apache.tapestry5.corelib.base.AbstractLink;
@@ -52,6 +55,7 @@ import org.apache.tapestry5.corelib.components.Loop;
 import org.apache.tapestry5.corelib.components.Output;
 import org.apache.tapestry5.corelib.components.OutputRaw;
 import org.apache.tapestry5.corelib.components.PageLink;
+import org.apache.tapestry5.corelib.components.PasswordField;
 import org.apache.tapestry5.corelib.components.PropertyDisplay;
 import org.apache.tapestry5.corelib.components.PropertyEditor;
 import org.apache.tapestry5.corelib.components.RenderObject;
@@ -61,9 +65,7 @@ import org.apache.tapestry5.corelib.components.TextOutput;
 import org.apache.tapestry5.corelib.components.Zone;
 import org.apache.tapestry5.corelib.mixins.FormGroup;
 import org.apache.tapestry5.corelib.mixins.RenderDisabled;
-import org.apache.tapestry5.corelib.pages.PropertyEditBlocks;
 import org.apache.tapestry5.integration.app1.base.BaseLayoutPage;
-import org.apache.tapestry5.integration.app1.base.EmptyExtendTemplate;
 import org.apache.tapestry5.integration.app1.components.Border;
 import org.apache.tapestry5.integration.app1.components.ErrorComponent;
 import org.apache.tapestry5.integration.app1.components.OuterAny;
@@ -75,15 +77,17 @@ import 
org.apache.tapestry5.integration.app1.mixins.EchoValue;
 import org.apache.tapestry5.integration.app1.mixins.EchoValue2;
 import org.apache.tapestry5.integration.app1.mixins.TextOnlyOnDisabled;
 import org.apache.tapestry5.integration.app1.pages.AlertsDemo;
+import org.apache.tapestry5.integration.app1.pages.AtComponentType;
 import org.apache.tapestry5.integration.app1.pages.BlockCaller;
 import org.apache.tapestry5.integration.app1.pages.BlockHolder;
-import 
org.apache.tapestry5.integration.app1.pages.EmbeddedComponentTypeConflict;
 import org.apache.tapestry5.integration.app1.pages.InstanceMixinDependencies;
 import org.apache.tapestry5.integration.app1.pages.MixinParameterDefault;
-import org.apache.tapestry5.integration.app1.pages.TemplateOverrideDemo;
 import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
 import org.apache.tapestry5.internal.services.templates.DefaultTemplateLocator;
+import org.apache.tapestry5.internal.services.templates.PageTemplateLocator;
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker;
+import org.apache.tapestry5.ioc.internal.util.AbstractResource;
+import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.modules.TapestryModule;
 import org.apache.tapestry5.plastic.PlasticManager;
 import org.apache.tapestry5.services.ComponentClassResolver;
@@ -96,6 +100,7 @@ import org.testng.annotations.Test;
 /**
  * Tests {@link ComponentDependencyRegistryImpl}.
  */
+@SuppressWarnings("deprecation")
 public class ComponentDependencyRegistryImplTest
 {
     
@@ -123,11 +128,37 @@ public class ComponentDependencyRegistryImplTest
         TapestryModule.contributeTemplateParser(templateConfiguration);
         templateParser = new TemplateParserImpl(templateConfiguration.map, 
false, new QuietOperationTracker());
         
-        componentTemplateLocator = new DefaultTemplateLocator();
+        final String rootFolder = "src/test/app1/";
+        final File folderFile = new File(rootFolder);
+        final Resource app1ContextFolderResource = new FileResource("/", 
folderFile);
+        
+        final PageTemplateLocator pageTemplateLocator = new 
PageTemplateLocator(app1ContextFolderResource, resolver, "");
+        final DefaultTemplateLocator defaultTemplateLocator = new 
DefaultTemplateLocator();
+        componentTemplateLocator = new ComponentTemplateLocator() 
+        {
+            @Override
+            public Resource locateTemplate(ComponentModel model, Locale 
locale) 
+            {
+                Resource resource = 
defaultTemplateLocator.locateTemplate(model, locale);
+                if (resource == null)
+                {
+                    resource = pageTemplateLocator.locateTemplate(model, 
locale);
+                }
+                return resource != null && resource.exists() ? resource : null;
+            }
+        };
         
         resolver = EasyMock.createMock(ComponentClassResolver.class);
         
+        
EasyMock.expect(resolver.resolvePageClassNameToPageName(EasyMock.anyString()))
+                .andAnswer(() -> {
+                    String s = ((String) EasyMock.getCurrentArguments()[0]);
+                    final String pageName = s.substring(s.lastIndexOf('.') + 
1);
+                    return pageName;
+                }).anyTimes();
+        
         expectResolveComponent(TextField.class);
+        expectResolveComponent(PasswordField.class);
         expectResolveComponent(Border.class);
         expectResolveComponent(BeanEditForm.class);
         expectResolveComponent(Zone.class);
@@ -163,8 +194,6 @@ public class ComponentDependencyRegistryImplTest
         EasyMock.expect(resolver.resolveMixinTypeToClassName("formgroup"))
             .andReturn(FormGroup.class.getName()).anyTimes();
         
-        // TODO: remove this
-//        
EasyMock.expect(resolver.getLogicalName(EasyMock.anyString())).andAnswer(() -> 
(String) EasyMock.getCurrentArguments()[0]).anyTimes();
         EasyMock.expect(resolver.isPage(EasyMock.anyString())).andAnswer(() -> 
{
             String string = (String) EasyMock.getCurrentArguments()[0];
             return string.contains(".pages.");
@@ -351,7 +380,6 @@ public class ComponentDependencyRegistryImplTest
         
     }
     
-    // Tested code isn't being used at the moment
     @Test
     public void register()
     {
@@ -390,8 +418,8 @@ public class ComponentDependencyRegistryImplTest
         assertDependencies(OuterAny.class, Any.class);
         
         // @Component, type() defined
-        
componentDependencyRegistry.register(EmbeddedComponentTypeConflict.class);
-        assertDependencies(EmbeddedComponentTypeConflict.class, 
TextField.class);
+        componentDependencyRegistry.register(AtComponentType.class);
+        assertDependencies(AtComponentType.class, TextField.class, 
Border.class);
 
         // @Mixin, type() not defined
         componentDependencyRegistry.register(AbstractTextField.class);
@@ -416,8 +444,15 @@ public class ComponentDependencyRegistryImplTest
         // Templates with <t:replace>
         componentDependencyRegistry.register(SubclassWithImport.class);
         assertDependencies(SubclassWithImport.class,
-                OutputRaw.class, SuperclassWithImport.class);
-
+                OutputRaw.class, SuperclassWithImport.class, Loop.class);
+        
+        // Circular dependency: BlockHolder <-> BlockCaller
+        componentDependencyRegistry.register(BlockHolder.class);
+        assertDependencies(BlockHolder.class, 
+                BlockCaller.class, Loop.class, ActionLink.class);
+        assertDependencies(BlockCaller.class, 
+                BlockHolder.class, Border.class, PageLink.class, 
Delegate.class);
+        
     }
     
     private void assertDependencies(Class clasz, Class... dependencies) {
@@ -474,4 +509,46 @@ public class ComponentDependencyRegistryImplTest
         
     }
     
+    final private static class FileResource extends AbstractResource
+    {
+        
+        final private File file;
+        
+        final private String path;
+
+        public FileResource(String path, File file) 
+        {
+            super(path);
+            this.file = file;
+            this.path = path;
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public java.net.URL toURL() 
+        {
+            try 
+            {
+                final File actualFile = new File(file, path);
+                return actualFile.exists() ? actualFile.toURL() : null;
+            } catch (MalformedURLException e) 
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        protected Resource newResource(String path) 
+        {
+            return new FileResource(path, file);
+        }
+
+        @Override
+        public String toString() 
+        {
+            return "FileResource [file=" + file + "/" + path + "]";
+        }
+        
+    }
+    
 }
diff --git a/tapestry-core/src/test/resources/log4j.properties 
b/tapestry-core/src/test/resources/log4j.properties
index febecf129..9dcdaacb2 100644
--- a/tapestry-core/src/test/resources/log4j.properties
+++ b/tapestry-core/src/test/resources/log4j.properties
@@ -21,6 +21,7 @@ 
log4j.category.org.apache.tapestry5.integration.app1.base.InheritBase=debug
 log4j.category.org.apache.tapestry5.integration.app1.pages.inherit=debug
 
 #log4j.category.org.apache.tapestry5.services.pageload=debug
+log4j.category.org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub=off
 
log4j.category.org.apache.tapestry5.integration.app1.services.AppModule.TimingFilter=ERROR
 # 
log4j.category.tapestry.transformer.org.apache.tapestry5.integration.app1.base.InheritBase=debug
 # 
log4j.category.tapestry.transformer.org.apache.tapestry5.integration.app1.pages.inherit=debug

Reply via email to