From 5ff7e7fd3d8179669d7e702c1e427a05c660165b Mon Sep 17 00:00:00 2001
From: Neil C Smith <neilcsmith.net@googlemail.com>
Date: Wed, 26 Jun 2024 09:37:43 +0100
Subject: [PATCH] Delegate to parent container to calculate component type.

---
 .../java/org/praxislive/core/Component.java   |  4 ++++
 .../java/org/praxislive/core/Container.java   | 20 ++++++++++++++++++
 .../praxislive/base/AbstractComponent.java    | 21 ++++++++++++-------
 .../praxislive/base/AbstractContainer.java    | 18 ++++++++++++++--
 .../org/praxislive/code/CodeComponent.java    | 16 +++++++++++++-
 .../java/org/praxislive/code/CodeContext.java | 15 +------------
 6 files changed, 69 insertions(+), 25 deletions(-)

diff --git a/praxiscore-api/src/main/java/org/praxislive/core/Component.java b/praxiscore-api/src/main/java/org/praxislive/core/Component.java
index adfc71b2..c3fe8239 100644
--- a/praxiscore-api/src/main/java/org/praxislive/core/Component.java
+++ b/praxiscore-api/src/main/java/org/praxislive/core/Component.java
@@ -101,6 +101,10 @@ public interface Component {
      * component info, and property values, in that order. It may also add
      * custom annotations.
      * <p>
+     * The component should delegate to
+     * {@link Container#getType(org.praxislive.core.Component)} to find its type
+     * rather than relying directly on {@link ComponentInfo#KEY_COMPONENT_TYPE}.
+     * <p>
      * The default implementation of this method does nothing.
      *
      * @param writer TreeWriter to write to
diff --git a/praxiscore-api/src/main/java/org/praxislive/core/Container.java b/praxiscore-api/src/main/java/org/praxislive/core/Container.java
index b72809de..d580d9e6 100644
--- a/praxiscore-api/src/main/java/org/praxislive/core/Container.java
+++ b/praxiscore-api/src/main/java/org/praxislive/core/Container.java
@@ -21,6 +21,7 @@
  */
 package org.praxislive.core;
 
+import java.util.Optional;
 import java.util.stream.Stream;
 import org.praxislive.core.protocols.ContainerProtocol;
 
@@ -79,4 +80,23 @@ public default void write(TreeWriter writer) {
         // no op
     }
 
+    /**
+     * Get the {@link ComponentType} of the provided child. The default
+     * implementation looks for {@link ComponentInfo#KEY_COMPONENT_TYPE} in the
+     * child's info. Container's may override to provide a more efficient or
+     * suitable result.
+     * <p>
+     * The default implementation does not check if the provided component is
+     * actually a child of this container.
+     *
+     * @param child child component
+     * @return component type, or null if unavailable
+     */
+    public default ComponentType getType(Component child) {
+        return Optional.ofNullable(child.getInfo())
+                .map(info -> info.properties().get(ComponentInfo.KEY_COMPONENT_TYPE))
+                .flatMap(ComponentType::from)
+                .orElse(null);
+    }
+
 }
diff --git a/praxiscore-base/src/main/java/org/praxislive/base/AbstractComponent.java b/praxiscore-base/src/main/java/org/praxislive/base/AbstractComponent.java
index 5d9107d2..6da6ac15 100644
--- a/praxiscore-base/src/main/java/org/praxislive/base/AbstractComponent.java
+++ b/praxiscore-base/src/main/java/org/praxislive/base/AbstractComponent.java
@@ -107,18 +107,23 @@ public void write(TreeWriter writer) {
     }
 
     protected final void writeTypeAndInfo(TreeWriter writer) {
-        ComponentInfo info = getInfo();
-        if (info == null) {
-            return;
+        ComponentType type;
+        if (parent == null) {
+            // assume we're a root?!
+            type = Optional.ofNullable(getInfo())
+                    .map(info -> info.properties().get(ComponentInfo.KEY_COMPONENT_TYPE))
+                    .flatMap(ComponentType::from)
+                    .orElse(null);
+        } else {
+            type = parent.getType(this);
         }
-        ComponentType type = Optional.ofNullable(
-                info.properties().get(ComponentInfo.KEY_COMPONENT_TYPE))
-                .flatMap(ComponentType::from)
-                .orElse(null);
         if (type != null) {
             writer.writeType(type);
         }
-        writer.writeInfo(info);
+        ComponentInfo info = getInfo();
+        if (info != null) {
+            writer.writeInfo(info);
+        }
     }
 
     protected final void writeMeta(TreeWriter writer) {
diff --git a/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java b/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
index f19d3b25..de9323c6 100644
--- a/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
+++ b/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
@@ -21,6 +21,7 @@
  */
 package org.praxislive.base;
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -31,6 +32,7 @@
 import org.praxislive.core.Call;
 import org.praxislive.core.Component;
 import org.praxislive.core.ComponentAddress;
+import org.praxislive.core.ComponentType;
 import org.praxislive.core.Connection;
 import org.praxislive.core.Container;
 import org.praxislive.core.Control;
@@ -63,10 +65,12 @@ public abstract class AbstractContainer extends AbstractComponent implements Con
     private final static System.Logger LOG = System.getLogger(AbstractContainer.class.getName());
 
     private final Map<String, Component> childMap;
+    private final Map<Component, ComponentType> childTypeMap;
     private final Set<Connection> connections;
 
     protected AbstractContainer() {
         childMap = new LinkedHashMap<>();
+        childTypeMap = new HashMap<>();
         connections = new LinkedHashSet<>();
         registerControl(ContainerProtocol.ADD_CHILD, new AddChildControl());
         registerControl(ContainerProtocol.REMOVE_CHILD, new RemoveChildControl());
@@ -102,6 +106,11 @@ public ComponentAddress getAddress(Component child) {
         }
     }
 
+    @Override
+    public ComponentType getType(Component child) {
+        return childTypeMap.computeIfAbsent(child, Container.super::getType);
+    }
+
     @Override
     public void hierarchyChanged() {
         childMap.values().forEach(Component::hierarchyChanged);
@@ -156,6 +165,7 @@ protected Component removeChild(String id) {
                 LOG.log(System.Logger.Level.ERROR, "Child throwing Veto on removal", ex);
             }
             child.hierarchyChanged();
+            childTypeMap.remove(child);
         }
         return child;
     }
@@ -231,11 +241,15 @@ protected Call processResponse(Call call) throws Exception {
             if (args.size() < 1) {
                 throw new IllegalArgumentException("Invalid response");
             }
-            Component c = PReference.from(args.get(0))
+            Component child = PReference.from(args.get(0))
                     .flatMap(r -> r.as(Component.class))
                     .orElseThrow();
             Call active = getActiveCall();
-            addChild(active.args().get(0).toString(), c);
+            addChild(active.args().get(0).toString(), child);
+            ComponentType type = ComponentType.from(active.args().get(1)).orElse(null);
+            if (type != null) {
+                childTypeMap.put(child, type);
+            }
             return active.reply();
         }
     }
diff --git a/praxiscore-code/src/main/java/org/praxislive/code/CodeComponent.java b/praxiscore-code/src/main/java/org/praxislive/code/CodeComponent.java
index 8cafe7e3..eae360e0 100644
--- a/praxiscore-code/src/main/java/org/praxislive/code/CodeComponent.java
+++ b/praxiscore-code/src/main/java/org/praxislive/code/CodeComponent.java
@@ -21,6 +21,7 @@
  */
 package org.praxislive.code;
 
+import java.util.Optional;
 import org.praxislive.base.MetaProperty;
 import org.praxislive.core.Component;
 import org.praxislive.core.ComponentAddress;
@@ -33,6 +34,7 @@
 import org.praxislive.core.Port;
 import org.praxislive.core.VetoException;
 import org.praxislive.core.ComponentInfo;
+import org.praxislive.core.ComponentType;
 import org.praxislive.core.ControlInfo;
 import org.praxislive.core.ThreadContext;
 import org.praxislive.core.TreeWriter;
@@ -125,7 +127,19 @@ public ComponentInfo getInfo() {
 
     @Override
     public void write(TreeWriter writer) {
-        writer.writeType(codeCtxt.getComponentType());
+        ComponentType type;
+        if (parent == null) {
+            // assume we're a root?!
+            type = Optional.ofNullable(getInfo())
+                    .map(info -> info.properties().get(ComponentInfo.KEY_COMPONENT_TYPE))
+                    .flatMap(ComponentType::from)
+                    .orElse(null);
+        } else {
+            type = parent.getType(this);
+        }
+        if (type != null) {
+            writer.writeType(type);
+        }
         writer.writeInfo(getInfo());
         codeCtxt.writeDescriptors(writer);
     }
diff --git a/praxiscore-code/src/main/java/org/praxislive/code/CodeContext.java b/praxiscore-code/src/main/java/org/praxislive/code/CodeContext.java
index 9e3dbcfe..4cd3c12f 100644
--- a/praxiscore-code/src/main/java/org/praxislive/code/CodeContext.java
+++ b/praxiscore-code/src/main/java/org/praxislive/code/CodeContext.java
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2023 Neil C Smith.
+ * Copyright 2024 Neil C Smith.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License version 3 only, as
@@ -29,7 +29,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.stream.Stream;
@@ -44,7 +43,6 @@
 import org.praxislive.core.PacketRouter;
 import org.praxislive.core.Port;
 import org.praxislive.core.ComponentInfo;
-import org.praxislive.core.ComponentType;
 import org.praxislive.core.TreeWriter;
 import org.praxislive.core.Value;
 import org.praxislive.core.services.Service;
@@ -70,7 +68,6 @@ public abstract class CodeContext<D extends CodeDelegate> {
     private final Map<String, ReferenceDescriptor<?>> refs;
     private final List<Descriptor> descriptors;
     private final ComponentInfo info;
-    private final ComponentType componentType;
 
     private final D delegate;
     private final LogBuilder log;
@@ -114,7 +111,6 @@ protected CodeContext(CodeConnector<D> connector, boolean requireClock) {
             ports = connector.extractPorts();
             refs = connector.extractRefs();
             info = connector.extractInfo();
-            componentType = connector.extractComponentType();
             delegate = connector.getDelegate();
             log = new LogBuilder(LogLevel.ERROR);
             this.requireClock = requireClock || connector.requiresClock();
@@ -404,15 +400,6 @@ protected ComponentInfo getInfo() {
         return info;
     }
 
-    /**
-     * Get the component type.
-     *
-     * @return component type
-     */
-    protected final ComponentType getComponentType() {
-        return componentType;
-    }
-
     /**
      * Find the address of the passed in control, or null if it does not have
      * one.