From b69989fea6e7aa2b783ee7382ad218c4e8c2669c Mon Sep 17 00:00:00 2001 From: rakow Date: Wed, 18 Dec 2024 14:19:12 +0100 Subject: [PATCH] improve the dependency declaration of commands --- .../org/matsim/application/CommandRunner.java | 22 +++++++++------- .../org/matsim/application/CommandSpec.java | 5 ++-- .../org/matsim/application/Dependency.java | 20 ++++++++++++++ .../analysis/impact/ImpactAnalysis.java | 12 ++++----- .../application/options/InputOptions.java | 26 +++++++++++++++++++ .../analysis/TestDependentAnalysis.java | 6 ++++- .../analysis/TestOtherDependentAnalysis.java | 6 ++++- 7 files changed, 76 insertions(+), 21 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/CommandRunner.java b/contribs/application/src/main/java/org/matsim/application/CommandRunner.java index ea0af2e6dd2..443b2e7f104 100644 --- a/contribs/application/src/main/java/org/matsim/application/CommandRunner.java +++ b/contribs/application/src/main/java/org/matsim/application/CommandRunner.java @@ -75,10 +75,12 @@ public void run(Path input) { for (Map.Entry, String[]> e : args.entrySet()) { Class clazz = e.getKey(); graph.addVertex(clazz); - Class[] depends = ApplicationUtils.getSpec(clazz).dependsOn(); - for (Class d : depends) { - graph.addVertex(d); - graph.addEdge(d, clazz); + Dependency[] depends = ApplicationUtils.getSpec(clazz).dependsOn(); + for (Dependency d : depends) { + if (d.required()) { + graph.addVertex(d.value()); + graph.addEdge(d.value(), clazz); + } } if (depends.length == 0) start.add(clazz); @@ -149,11 +151,11 @@ private String[] createArgs(Class command, String[] if (present) continue; - for (Class depend : spec.dependsOn()) { - CommandSpec dependency = ApplicationUtils.getSpec(depend); + for (Dependency depend : spec.dependsOn()) { + CommandSpec dependency = ApplicationUtils.getSpec(depend.value()); if (ArrayUtils.contains(dependency.produces(), require)) { - String path = getPath(depend, require); + String path = getPath(depend.value(), require); args.add(arg); args.add(path); @@ -308,9 +310,9 @@ public void add(Class command, String... args) { CommandSpec spec = ApplicationUtils.getSpec(command); // Add dependent classes - for (Class depends : spec.dependsOn()) { - if (!this.args.containsKey(depends)) - add(depends); + for (Dependency depends : spec.dependsOn()) { + if (!this.args.containsKey(depends.value())) + add(depends.value()); } } diff --git a/contribs/application/src/main/java/org/matsim/application/CommandSpec.java b/contribs/application/src/main/java/org/matsim/application/CommandSpec.java index de07cfe7f8a..adb2ab3f662 100644 --- a/contribs/application/src/main/java/org/matsim/application/CommandSpec.java +++ b/contribs/application/src/main/java/org/matsim/application/CommandSpec.java @@ -48,10 +48,9 @@ String[] produces() default {}; /** - * Other commands that produce the input needed by this command. + * Other commands that produce input needed by this command. */ - Class[] dependsOn() default {}; -// Dependency[] dependsOn() default {}; + Dependency[] dependsOn() default {}; /** * Group name / identifier. Will use the package name if this is not changed here. diff --git a/contribs/application/src/main/java/org/matsim/application/Dependency.java b/contribs/application/src/main/java/org/matsim/application/Dependency.java index 1743874681f..4d8a28875fc 100644 --- a/contribs/application/src/main/java/org/matsim/application/Dependency.java +++ b/contribs/application/src/main/java/org/matsim/application/Dependency.java @@ -1,11 +1,31 @@ package org.matsim.application; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines a dependency to be used in a {@link CommandSpec}. + * This annotation is not used directly on other classes. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) public @interface Dependency { + /** + * Analysis command required by this command. + */ Class value(); + /** + * List of file names that are used as input for this class. + */ String[] files() default {}; + /** + * Whether this dependency is required. + */ boolean required() default false; } diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/impact/ImpactAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/impact/ImpactAnalysis.java index 9171ae75e2e..95abf018e95 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/impact/ImpactAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/impact/ImpactAnalysis.java @@ -4,7 +4,9 @@ import org.matsim.api.core.v01.Scenario; import org.matsim.application.ApplicationUtils; import org.matsim.application.CommandSpec; +import org.matsim.application.Dependency; import org.matsim.application.MATSimAppCommand; +import org.matsim.application.analysis.emissions.AirPollutionAnalysis; import org.matsim.application.analysis.population.TripAnalysis; import org.matsim.application.options.InputOptions; import org.matsim.application.options.OutputOptions; @@ -23,12 +25,10 @@ name = "impact" ) @CommandSpec(requireRunDirectory = true, - requires = {"trip_stats.csv"}, - dependsOn = {TripAnalysis.class}, -// dependsOn = { -// @Dependency(value = TripAnalysis.class, files = "trip_stats.csv") -// // @Dependency(value = AirPollutionAnalysis.class, files = "...") -// }, + dependsOn = { + @Dependency(value = TripAnalysis.class, files = "trip_stats.csv"), + @Dependency(value = AirPollutionAnalysis.class, files = "...") + }, produces = { "data.csv", } diff --git a/contribs/application/src/main/java/org/matsim/application/options/InputOptions.java b/contribs/application/src/main/java/org/matsim/application/options/InputOptions.java index eae4b3a7c21..dc1fda457b7 100644 --- a/contribs/application/src/main/java/org/matsim/application/options/InputOptions.java +++ b/contribs/application/src/main/java/org/matsim/application/options/InputOptions.java @@ -4,14 +4,18 @@ import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Population; import org.matsim.application.CommandSpec; +import org.matsim.application.Dependency; import org.matsim.application.MATSimAppCommand; import org.matsim.core.network.NetworkUtils; import org.matsim.core.population.PopulationUtils; import picocli.CommandLine; +import javax.annotation.Nullable; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -106,6 +110,28 @@ public String getPath(String name) { return inputs.get(name); } + /** + * Return the path to a file provided by command defined as dependency. + * @param clazz dependency + * @param name file name + * + * @return can be null if not provided and the dependency is not required. + */ + @Nullable + public String getPath(Class clazz, String name) { + Optional first = Arrays.stream(spec.dependsOn()) + .filter(d -> d.value().equals(clazz)) + .findFirst(); + + if (first.isEmpty()) + throw new IllegalArgumentException(String.format("Dependency to '%s' is not defined in @CommandSpec.", clazz)); + + if (first.get().required() && !inputs.containsKey(name)) + throw new IllegalArgumentException(String.format("Path to '%s' is required but not provided.", name)); + + return inputs.get(name); + } + public Network getNetwork() { if (!spec.requireNetwork()) throw new IllegalArgumentException("Network can not be accessed unless, requireNetwork=true."); diff --git a/contribs/application/src/test/java/org/matsim/application/analysis/TestDependentAnalysis.java b/contribs/application/src/test/java/org/matsim/application/analysis/TestDependentAnalysis.java index 68a1139ef7e..139befca64f 100644 --- a/contribs/application/src/test/java/org/matsim/application/analysis/TestDependentAnalysis.java +++ b/contribs/application/src/test/java/org/matsim/application/analysis/TestDependentAnalysis.java @@ -1,6 +1,7 @@ package org.matsim.application.analysis; import org.matsim.application.CommandSpec; +import org.matsim.application.Dependency; import org.matsim.application.MATSimAppCommand; import org.matsim.application.options.InputOptions; import org.matsim.application.options.OutputOptions; @@ -9,7 +10,10 @@ import java.nio.file.Files; import java.nio.file.Path; -@CommandSpec(requires = {"out.xml"}, produces = "processed.csv", dependsOn = {TestAnalysis.class}) +@CommandSpec( + produces = "processed.csv", + dependsOn = {@Dependency(value = TestAnalysis.class, files = {"out.xml"}, required = true)} +) public class TestDependentAnalysis implements MATSimAppCommand { @CommandLine.Mixin diff --git a/contribs/application/src/test/java/org/matsim/application/analysis/TestOtherDependentAnalysis.java b/contribs/application/src/test/java/org/matsim/application/analysis/TestOtherDependentAnalysis.java index 51c5164e077..8700efe6cd6 100644 --- a/contribs/application/src/test/java/org/matsim/application/analysis/TestOtherDependentAnalysis.java +++ b/contribs/application/src/test/java/org/matsim/application/analysis/TestOtherDependentAnalysis.java @@ -1,6 +1,7 @@ package org.matsim.application.analysis; import org.matsim.application.CommandSpec; +import org.matsim.application.Dependency; import org.matsim.application.MATSimAppCommand; import org.matsim.application.options.InputOptions; import org.matsim.application.options.OutputOptions; @@ -8,7 +9,10 @@ import java.nio.file.Files; -@CommandSpec(requires = {"out.xml"}, produces = "processed.csv", dependsOn = {TestAnalysis.class}) +@CommandSpec( + produces = "processed.csv", + dependsOn = @Dependency(value = TestAnalysis.class, files = {"out.xml"}, required = true) +) public class TestOtherDependentAnalysis implements MATSimAppCommand { @CommandLine.Mixin