From 5ff7e7fd3d8179669d7e702c1e427a05c660165b Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 26 Jun 2024 09:37:43 +0100 Subject: [PATCH 1/3] 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. *

+ * 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}. + *

* 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. + *

+ * 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 childMap; + private final Map childTypeMap; private final Set 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 { private final Map> refs; private final List descriptors; private final ComponentInfo info; - private final ComponentType componentType; private final D delegate; private final LogBuilder log; @@ -114,7 +111,6 @@ protected CodeContext(CodeConnector 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. From a7ea005cbc1e21427fbdd73b425c67564c35c50f Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 26 Jun 2024 13:33:07 +0100 Subject: [PATCH 2/3] Refactor and document various code module services. --- .../code/CodeComponentFactoryService.java | 41 +++++++++++++++---- .../code/CodeContextFactoryService.java | 20 +++++---- .../code/CodeRootFactoryService.java | 40 +++++++++++++++--- .../praxislive/code/SharedCodeService.java | 11 ++++- 4 files changed, 91 insertions(+), 21 deletions(-) diff --git a/praxiscore-code/src/main/java/org/praxislive/code/CodeComponentFactoryService.java b/praxiscore-code/src/main/java/org/praxislive/code/CodeComponentFactoryService.java index f7f71f37..4963f6bf 100644 --- a/praxiscore-code/src/main/java/org/praxislive/code/CodeComponentFactoryService.java +++ b/praxiscore-code/src/main/java/org/praxislive/code/CodeComponentFactoryService.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 @@ -19,16 +19,43 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.code; +import java.util.stream.Stream; +import org.praxislive.core.ControlInfo; +import org.praxislive.core.services.ComponentFactory; import org.praxislive.core.services.ComponentFactoryService; +import org.praxislive.core.services.Service; /** - * - * + * A factory service for code components. Provides the same API as + * {@link ComponentFactoryService} for use as a redirect with + * {@link ComponentFactory#componentRedirect()}. */ -public class CodeComponentFactoryService extends ComponentFactoryService { - - +public final class CodeComponentFactoryService implements Service { + + /** + * Control ID of the new instance control. + */ + public final static String NEW_INSTANCE = ComponentFactoryService.NEW_INSTANCE; + + /** + * ControlInfo for the new instance control. + */ + public final static ControlInfo NEW_INSTANCE_INFO + = ComponentFactoryService.NEW_INSTANCE_INFO; + + @Override + public Stream controls() { + return Stream.of(NEW_INSTANCE); + } + + @Override + public ControlInfo getControlInfo(String control) { + if (NEW_INSTANCE.equals(control)) { + return NEW_INSTANCE_INFO; + } + throw new IllegalArgumentException(); + } + } diff --git a/praxiscore-code/src/main/java/org/praxislive/code/CodeContextFactoryService.java b/praxiscore-code/src/main/java/org/praxislive/code/CodeContextFactoryService.java index a75cf2e8..fe0b8bdd 100644 --- a/praxiscore-code/src/main/java/org/praxislive/code/CodeContextFactoryService.java +++ b/praxiscore-code/src/main/java/org/praxislive/code/CodeContextFactoryService.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2021 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 @@ -37,9 +37,16 @@ * Should make use of a {@link CodeCompilerService} implementation for compiling * source code (which does support other processes). */ -public class CodeContextFactoryService implements Service { +public final class CodeContextFactoryService implements Service { + /** + * Control ID of the new context control. + */ public final static String NEW_CONTEXT = "new-context"; + + /** + * ControlInfo for the new context control. + */ public final static ControlInfo NEW_CONTEXT_INFO = ControlInfo.createFunctionInfo( List.of(PReference.info(Task.class)), @@ -87,7 +94,7 @@ public Task(CodeFactory factory, Class previous) { this(factory, code, logLevel, previous, null); } - + /** * Create task. * @@ -147,13 +154,12 @@ public Class getPrevious() { /** * Get the shared code classloader to use as parent (optional). - * + * * @return shared classloader, or null */ public ClassLoader getSharedClassLoader() { return sharedClassLoader; } - } @@ -180,7 +186,7 @@ public Result(CodeContext context, LogBuilder log) { /** * Get created context. - * + * * @return context */ public CodeContext getContext() { @@ -189,7 +195,7 @@ public CodeContext getContext() { /** * Get log builder with any warning or error messages. - * + * * @return log */ public LogBuilder getLog() { diff --git a/praxiscore-code/src/main/java/org/praxislive/code/CodeRootFactoryService.java b/praxiscore-code/src/main/java/org/praxislive/code/CodeRootFactoryService.java index bb4db8e5..a156a756 100644 --- a/praxiscore-code/src/main/java/org/praxislive/code/CodeRootFactoryService.java +++ b/praxiscore-code/src/main/java/org/praxislive/code/CodeRootFactoryService.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 @@ -19,15 +19,45 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.code; +import java.util.stream.Stream; +import org.praxislive.core.ControlInfo; +import org.praxislive.core.services.ComponentFactory; import org.praxislive.core.services.RootFactoryService; +import org.praxislive.core.services.Service; /** + * A factory service for code root components. Provides the same API as + * {@link RootFactoryService} for use as a redirect with + * {@link ComponentFactory#rootRedirect()}. * - * */ -public class CodeRootFactoryService extends RootFactoryService { - +public final class CodeRootFactoryService implements Service { + + /** + * Control ID of the new root instance control. + */ + public final static String NEW_ROOT_INSTANCE + = RootFactoryService.NEW_ROOT_INSTANCE; + + /** + * ControlInfo of the new root instance control. + */ + public final static ControlInfo NEW_ROOT_INSTANCE_INFO + = RootFactoryService.NEW_ROOT_INSTANCE_INFO; + + @Override + public Stream controls() { + return Stream.of(NEW_ROOT_INSTANCE); + } + + @Override + public ControlInfo getControlInfo(String control) { + if (NEW_ROOT_INSTANCE.equals(control)) { + return NEW_ROOT_INSTANCE_INFO; + } + throw new IllegalArgumentException(); + } + } diff --git a/praxiscore-code/src/main/java/org/praxislive/code/SharedCodeService.java b/praxiscore-code/src/main/java/org/praxislive/code/SharedCodeService.java index 6a9b7217..afdd5162 100644 --- a/praxiscore-code/src/main/java/org/praxislive/code/SharedCodeService.java +++ b/praxiscore-code/src/main/java/org/praxislive/code/SharedCodeService.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2022 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 @@ -40,9 +40,16 @@ * {@link CodeCompilerService} implementation for compiling source code (which * does support other processes). */ -public class SharedCodeService implements Service { +public final class SharedCodeService implements Service { + /** + * Control ID of the new shared code control. + */ public final static String NEW_SHARED = "new-shared"; + + /** + * ControlInfo of the new shared code control. + */ public final static ControlInfo NEW_SHARED_INFO = ControlInfo.createFunctionInfo( List.of(PReference.info(Task.class)), From 235e748cb5a75714042bd54915d72d8305221a36 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 26 Jun 2024 15:49:57 +0100 Subject: [PATCH 3/3] FIX defaults filled from input info in ControlInfo map parsing. --- .../src/main/java/org/praxislive/core/ControlInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/praxiscore-api/src/main/java/org/praxislive/core/ControlInfo.java b/praxiscore-api/src/main/java/org/praxislive/core/ControlInfo.java index 7bf05d3e..fe20045e 100644 --- a/praxiscore-api/src/main/java/org/praxislive/core/ControlInfo.java +++ b/praxiscore-api/src/main/java/org/praxislive/core/ControlInfo.java @@ -342,7 +342,7 @@ private static ControlInfo propertyFromData(PMap data) { var inputs = PArray.from(data.asMap().getOrDefault(KEY_INPUTS, PArray.EMPTY)) .map(a -> a.asListOf(ArgumentInfo.class)) .orElseThrow(IllegalArgumentException::new); - var defaults = PArray.from(data.asMap().getOrDefault(KEY_INPUTS, PArray.EMPTY)) + var defaults = PArray.from(data.asMap().getOrDefault(KEY_DEFAULTS, PArray.EMPTY)) .map(a -> a.asList()) .orElseThrow(IllegalArgumentException::new);