From 94443c8e8441089dedf5c795619e49c88416e512 Mon Sep 17 00:00:00 2001 From: Thad House Date: Tue, 28 Apr 2026 09:54:10 -1000 Subject: [PATCH] [wpilib] Fix OpModeRobot initialization (#8834) There were 3 cases failing before. 1. An OpModeRobot with no annotation. 2. An OpModeRobot with an annotation but a parameterless constructor. 3. An OpMode with a UserControls constructor This PR solves both of these issues. The first one is solved by adding the null check before setting the user controls instance. That one will never have an opmode instance. The second one is solved by falling back to a parameterless constructor if one with a parameter is not found. The 3rd one is solved by using the annotation type rather than the instance for constructor lookup. Also fixes ExpansionHubSample's missing annotations. --- .../main/java/org/wpilib/framework/OpModeRobot.java | 9 ++++----- .../src/main/java/org/wpilib/framework/RobotBase.java | 9 ++++++--- wpilibjExamples/build.gradle | 10 ++++++++++ .../examples/expansionhubsample/DefaultAutoMode.java | 2 ++ .../examples/expansionhubsample/DefaultTeleMode.java | 2 ++ .../main/java/org/wpilib/util/ConstructorMatch.java | 8 ++++---- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/wpilibj/src/main/java/org/wpilib/framework/OpModeRobot.java b/wpilibj/src/main/java/org/wpilib/framework/OpModeRobot.java index 0746f5c531..f3e7739aeb 100644 --- a/wpilibj/src/main/java/org/wpilib/framework/OpModeRobot.java +++ b/wpilibj/src/main/java/org/wpilib/framework/OpModeRobot.java @@ -115,9 +115,8 @@ public abstract class OpModeRobot extends RobotBase { Optional> ctor; // try 2-parameter constructor - if (m_userControlsInstance != null) { - ctor = - ConstructorMatch.findBestConstructor(cls, getClass(), m_userControlsInstance.getClass()); + if (m_userControlsBaseClass.isPresent()) { + ctor = ConstructorMatch.findBestConstructor(cls, getClass(), m_userControlsBaseClass.get()); if (ctor.isPresent()) { return ctor; } @@ -130,8 +129,8 @@ public abstract class OpModeRobot extends RobotBase { } // try 1-parameter constructor with UserControls parameter - if (m_userControlsInstance != null) { - ctor = ConstructorMatch.findBestConstructor(cls, m_userControlsInstance.getClass()); + if (m_userControlsBaseClass.isPresent()) { + ctor = ConstructorMatch.findBestConstructor(cls, m_userControlsBaseClass.get()); if (ctor.isPresent()) { return ctor; } diff --git a/wpilibj/src/main/java/org/wpilib/framework/RobotBase.java b/wpilibj/src/main/java/org/wpilib/framework/RobotBase.java index a73a6aa67a..bf890ce2a7 100644 --- a/wpilibj/src/main/java/org/wpilib/framework/RobotBase.java +++ b/wpilibj/src/main/java/org/wpilib/framework/RobotBase.java @@ -294,12 +294,15 @@ public abstract class RobotBase implements AutoCloseable { UserControlsInstance userControlsAttribute = robotClass.getDeclaredAnnotation(UserControlsInstance.class); UserControls userControlsInstance = null; - Optional> constructorMatch; + Optional> constructorMatch = Optional.empty(); if (userControlsAttribute != null) { var userControlsClass = userControlsAttribute.value(); userControlsInstance = userControlsClass.getDeclaredConstructor().newInstance(); constructorMatch = ConstructorMatch.findBestConstructor(robotClass, userControlsClass); - } else { + } + + if (constructorMatch.isEmpty()) { + // Try to find a constructor with no parameters if there is no UserControls constructor constructorMatch = ConstructorMatch.findBestConstructor(robotClass); } @@ -310,7 +313,7 @@ public abstract class RobotBase implements AutoCloseable { T robot = constructorMatch.get().newInstance(userControlsInstance); - if (robot instanceof OpModeRobot opModeRobot) { + if (userControlsInstance != null && robot instanceof OpModeRobot opModeRobot) { // Insert the UserControls instance into the opModeRobot for use when constructing opmodes opModeRobot.setUserControlsInstance(userControlsInstance); } diff --git a/wpilibjExamples/build.gradle b/wpilibjExamples/build.gradle index 2eec1d8cc2..fec10b45ad 100644 --- a/wpilibjExamples/build.gradle +++ b/wpilibjExamples/build.gradle @@ -47,6 +47,16 @@ eclipse { } } +tasks.withType(JavaExec).configureEach { + // Commands v3 needs reflective access to the continuation classes + jvmArgs += [ + '--add-opens', + 'java.base/jdk.internal.vm=ALL-UNNAMED', + '--add-opens', + 'java.base/java.lang=ALL-UNNAMED', + ] +} + jacoco { toolVersion = "0.8.14" } diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultAutoMode.java b/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultAutoMode.java index b769b570c5..2690ce1853 100644 --- a/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultAutoMode.java +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultAutoMode.java @@ -4,9 +4,11 @@ package org.wpilib.examples.expansionhubsample; +import org.wpilib.opmode.Autonomous; import org.wpilib.opmode.PeriodicOpMode; import org.wpilib.system.Timer; +@Autonomous public class DefaultAutoMode extends PeriodicOpMode { private final Robot robot; private final Timer timer = new Timer(); diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultTeleMode.java b/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultTeleMode.java index 9649835aab..a3225ef7a3 100644 --- a/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultTeleMode.java +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/expansionhubsample/DefaultTeleMode.java @@ -6,7 +6,9 @@ package org.wpilib.examples.expansionhubsample; import org.wpilib.driverstation.DefaultUserControls; import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +@Teleop public class DefaultTeleMode extends PeriodicOpMode { private final Robot robot; private final DefaultUserControls userControls; diff --git a/wpiutil/src/main/java/org/wpilib/util/ConstructorMatch.java b/wpiutil/src/main/java/org/wpilib/util/ConstructorMatch.java index b279d48261..523632c15d 100644 --- a/wpiutil/src/main/java/org/wpilib/util/ConstructorMatch.java +++ b/wpiutil/src/main/java/org/wpilib/util/ConstructorMatch.java @@ -57,8 +57,8 @@ public class ConstructorMatch { /** * Creates a new instance of the constructor's class using the given arguments. The arguments must * match the parameter types of the constructor, and must not be assignable to each other. The - * order of the arguments does not matter, as they will be matched to the parameter types. - * Duplicate arguments are ignored, as the first match will match. + * order of the arguments does matter, as they will be matched to the constructor parameter types + * in order. * * @param args the arguments to pass to the constructor * @return a new instance of the constructor's class @@ -90,8 +90,8 @@ public class ConstructorMatch { * types must not be assignable to each other. If multiple constructors match, the one with the * most specific parameter types is chosen. If there is still a tie, the one with the most * specific first parameter type is chosen, then the second parameter type, and so on. The order - * of the parameter types does not matter, as they will be matched to the constructor's parameter - * types. Duplicate parameter types are ignored, as the first match will match. + * of the parameter types does matter, as they will be matched to the constructor parameter types + * in order. * * @param the type of the class to find the constructor for * @param clazz the class to find the constructor for