| @@ -0,0 +1,15 | |||
|
|
1 | package org.implab.gradle.common.dsl; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.common.exec.Shell; | |
|
|
4 | ||
|
|
5 | public interface ShellSpecMixin { | |
|
|
6 | void setShell(Shell shell); | |
|
|
7 | ||
|
|
8 | default void useEchoShell() { | |
|
|
9 | setShell(Shell.echo()); | |
|
|
10 | } | |
|
|
11 | ||
|
|
12 | default void useSystemShell() { | |
|
|
13 | setShell(Shell.system()); | |
|
|
14 | } | |
|
|
15 | } | |
| @@ -0,0 +1,212 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | import java.util.HashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.Optional; | |
|
|
6 | import java.util.concurrent.CompletableFuture; | |
|
|
7 | ||
|
|
8 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
9 | import org.eclipse.jdt.annotation.Nullable; | |
|
|
10 | ||
|
|
11 | import java.io.File; | |
|
|
12 | import java.io.IOException; | |
|
|
13 | ||
|
|
14 | import static java.util.Objects.requireNonNull; | |
|
|
15 | ||
|
|
16 | /** Command line builder */ | |
|
|
17 | @NonNullByDefault | |
|
|
18 | public abstract class AbstractExecBuilder<CS> implements ShellExec { | |
|
|
19 | ||
|
|
20 | private boolean inheritEnvironment = true; | |
|
|
21 | ||
|
|
22 | private final Map<String, String> environment = new HashMap<>(); | |
|
|
23 | ||
|
|
24 | private @Nullable File directory; | |
|
|
25 | ||
|
|
26 | private RedirectFrom inputRedirect; | |
|
|
27 | ||
|
|
28 | private RedirectTo outputRedirect; | |
|
|
29 | ||
|
|
30 | private RedirectTo errorRedirect; | |
|
|
31 | ||
|
|
32 | private final CS command; | |
|
|
33 | ||
|
|
34 | protected AbstractExecBuilder(CS command) { | |
|
|
35 | this.command = command; | |
|
|
36 | } | |
|
|
37 | ||
|
|
38 | ||
|
|
39 | /** Sets the working directory */ | |
|
|
40 | @Override | |
|
|
41 | public ShellExec workingDirectory(File directory) { | |
|
|
42 | requireNonNull(directory, "directory parameter can't be null"); | |
|
|
43 | ||
|
|
44 | this.directory = directory; | |
|
|
45 | return this; | |
|
|
46 | } | |
|
|
47 | ||
|
|
48 | @Override | |
|
|
49 | public ShellExec workingDirectory(Optional<? extends File> directory) { | |
|
|
50 | requireNonNull(directory, "directory parameter can't be null"); | |
|
|
51 | ||
|
|
52 | this.directory = directory.orElse(null); | |
|
|
53 | return this; | |
|
|
54 | } | |
|
|
55 | ||
|
|
56 | @Override | |
|
|
57 | public ShellExec inheritEnvironment(boolean inherit) { | |
|
|
58 | this.inheritEnvironment = inherit; | |
|
|
59 | return this; | |
|
|
60 | } | |
|
|
61 | ||
|
|
62 | /** | |
|
|
63 | * Sets the environment value. The value cannot be null. | |
|
|
64 | * | |
|
|
65 | * @param envVar The name of the environment variable | |
|
|
66 | * @param value The value to set, | |
|
|
67 | */ | |
|
|
68 | @Override | |
|
|
69 | public ShellExec putEnvironment(String envVar, String value) { | |
|
|
70 | requireNonNull(value, "Value can't be null"); | |
|
|
71 | requireNonNull(envVar, "envVar parameter can't be null"); | |
|
|
72 | ||
|
|
73 | environment.put(envVar, value); | |
|
|
74 | return this; | |
|
|
75 | } | |
|
|
76 | ||
|
|
77 | @Override | |
|
|
78 | public ShellExec environment(Map<String, ? extends String> env) { | |
|
|
79 | requireNonNull(env, "env parameter can't be null"); | |
|
|
80 | ||
|
|
81 | environment.clear(); | |
|
|
82 | environment.putAll(env); | |
|
|
83 | return this; | |
|
|
84 | } | |
|
|
85 | ||
|
|
86 | /** | |
|
|
87 | * Sets redirection for the stdin, {@link RedirectFrom} will be applied | |
|
|
88 | * every time the process is started. | |
|
|
89 | * | |
|
|
90 | * <p> | |
|
|
91 | * If redirection | |
|
|
92 | */ | |
|
|
93 | @Override | |
|
|
94 | public ShellExec stdin(RedirectFrom from) { | |
|
|
95 | requireNonNull(from, "from parameter can't be null"); | |
|
|
96 | ||
|
|
97 | inputRedirect = from; | |
|
|
98 | return this; | |
|
|
99 | } | |
|
|
100 | ||
|
|
101 | @Override | |
|
|
102 | public ShellExec stdin(Optional<? extends RedirectFrom> from) { | |
|
|
103 | requireNonNull(from, "from parameter can't be null"); | |
|
|
104 | inputRedirect = from.orElse(null); | |
|
|
105 | return this; | |
|
|
106 | } | |
|
|
107 | ||
|
|
108 | /** | |
|
|
109 | * Sets redirection for the stdout, {@link RedirectTo} will be applied | |
|
|
110 | * every time the process is started. | |
|
|
111 | */ | |
|
|
112 | @Override | |
|
|
113 | public ShellExec stdout(RedirectTo out) { | |
|
|
114 | requireNonNull(out, "out parameter can't be null"); | |
|
|
115 | outputRedirect = out; | |
|
|
116 | return this; | |
|
|
117 | } | |
|
|
118 | ||
|
|
119 | @Override | |
|
|
120 | public ShellExec stdout(Optional<? extends RedirectTo> to) { | |
|
|
121 | requireNonNull(to, "from parameter can't be null"); | |
|
|
122 | outputRedirect = to.orElse(null); | |
|
|
123 | return this; | |
|
|
124 | } | |
|
|
125 | ||
|
|
126 | /** | |
|
|
127 | * Sets redirection for the stderr, {@link RedirectTo} will be applied | |
|
|
128 | * every time the process is started. | |
|
|
129 | */ | |
|
|
130 | @Override | |
|
|
131 | public ShellExec stderr(RedirectTo err) { | |
|
|
132 | requireNonNull(err, "err parameter can't be null"); | |
|
|
133 | errorRedirect = err; | |
|
|
134 | return this; | |
|
|
135 | } | |
|
|
136 | ||
|
|
137 | @Override | |
|
|
138 | public ShellExec stderr(Optional<? extends RedirectTo> to) { | |
|
|
139 | requireNonNull(to, "from parameter can't be null"); | |
|
|
140 | errorRedirect = to.orElse(null); | |
|
|
141 | return this; | |
|
|
142 | } | |
|
|
143 | ||
|
|
144 | @Override | |
|
|
145 | public ShellExec from(PipeSpec pipeSpec) { | |
|
|
146 | ShellExec.super.from(pipeSpec); | |
|
|
147 | return this; | |
|
|
148 | } | |
|
|
149 | ||
|
|
150 | @Override | |
|
|
151 | public ShellExec from(EnvironmentSpec environmentSpec) { | |
|
|
152 | ShellExec.super.from(environmentSpec); | |
|
|
153 | return this; | |
|
|
154 | } | |
|
|
155 | ||
|
|
156 | /** Implement this function to */ | |
|
|
157 | protected abstract CompletableFuture<Integer> startInternal( | |
|
|
158 | CS command, | |
|
|
159 | EnvironmentSpec environment, | |
|
|
160 | PipeSpec redirect) throws IOException; | |
|
|
161 | ||
|
|
162 | /** | |
|
|
163 | * Creates and starts new process and returns {@link CompletableFuture}. The | |
|
|
164 | * process may be a remote process, or a shell process or etc. | |
|
|
165 | * | |
|
|
166 | * @return | |
|
|
167 | * @throws IOException | |
|
|
168 | */ | |
|
|
169 | public CompletableFuture<Integer> exec() throws IOException { | |
|
|
170 | return startInternal( | |
|
|
171 | command, | |
|
|
172 | new SelfEnvironmentSpec(), | |
|
|
173 | new SelfPipeSpec()); | |
|
|
174 | } | |
|
|
175 | ||
|
|
176 | private class SelfEnvironmentSpec implements EnvironmentSpec { | |
|
|
177 | @Override | |
|
|
178 | public boolean inheritEnvironment() { | |
|
|
179 | return inheritEnvironment; | |
|
|
180 | } | |
|
|
181 | ||
|
|
182 | @Override | |
|
|
183 | public Map<String, String> environment() { | |
|
|
184 | return environment; | |
|
|
185 | } | |
|
|
186 | ||
|
|
187 | @Override | |
|
|
188 | public Optional<File> workingDirectory() { | |
|
|
189 | return Optional.ofNullable(directory); | |
|
|
190 | } | |
|
|
191 | } | |
|
|
192 | ||
|
|
193 | private class SelfPipeSpec implements PipeSpec { | |
|
|
194 | ||
|
|
195 | @Override | |
|
|
196 | public Optional<RedirectTo> stdout() { | |
|
|
197 | return Optional.ofNullable(outputRedirect); | |
|
|
198 | } | |
|
|
199 | ||
|
|
200 | @Override | |
|
|
201 | public Optional<RedirectTo> stderr() { | |
|
|
202 | return Optional.ofNullable(errorRedirect); | |
|
|
203 | } | |
|
|
204 | ||
|
|
205 | @Override | |
|
|
206 | public Optional<RedirectFrom> stdin() { | |
|
|
207 | return Optional.ofNullable(inputRedirect); | |
|
|
208 | } | |
|
|
209 | ||
|
|
210 | } | |
|
|
211 | ||
|
|
212 | } | |
| @@ -0,0 +1,47 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | import java.util.List; | |
|
|
4 | import static java.util.Objects.requireNonNull; | |
|
|
5 | ||
|
|
6 | import java.util.ArrayList; | |
|
|
7 | ||
|
|
8 | public record CommandSpecRecord(String executable, List<String> arguments) implements CommandSpec { | |
|
|
9 | ||
|
|
10 | static class Builder implements CommandBuilder { | |
|
|
11 | ||
|
|
12 | private String executable; | |
|
|
13 | ||
|
|
14 | private List<String> arguments = new ArrayList<>(); | |
|
|
15 | ||
|
|
16 | @Override | |
|
|
17 | public CommandBuilder executable(String executable) { | |
|
|
18 | requireNonNull(executable, "cmd can't be null"); | |
|
|
19 | this.executable = executable; | |
|
|
20 | return this; | |
|
|
21 | } | |
|
|
22 | ||
|
|
23 | @Override | |
|
|
24 | public CommandBuilder arguments(Iterable<String> args) { | |
|
|
25 | requireNonNull(args, "Args must not be null"); | |
|
|
26 | arguments.clear(); | |
|
|
27 | for (var arg : args) | |
|
|
28 | arguments.add(arg); | |
|
|
29 | return this; | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | @Override | |
|
|
33 | public CommandBuilder addArguments(String... args) { | |
|
|
34 | for (var arg : args) | |
|
|
35 | arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); | |
|
|
36 | ||
|
|
37 | return this; | |
|
|
38 | } | |
|
|
39 | ||
|
|
40 | @Override | |
|
|
41 | public CommandSpec build() { | |
|
|
42 | requireNonNull(executable, "Executable must be specified"); | |
|
|
43 | return new CommandSpecRecord(executable, arguments); | |
|
|
44 | } | |
|
|
45 | ||
|
|
46 | } | |
|
|
47 | } | |
| @@ -0,0 +1,22 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | public interface Shell { | |
|
|
4 | ||
|
|
5 | ShellExec of(CommandSpec spec); | |
|
|
6 | ||
|
|
7 | /** Creates a new shell to start processes using ProcessBuilder. */ | |
|
|
8 | static Shell system() { | |
|
|
9 | return spec -> new SystemExecBuilder(spec); | |
|
|
10 | } | |
|
|
11 | ||
|
|
12 | /** Creates a stub shell which echoes the specified command line. */ | |
|
|
13 | static Shell echo() { | |
|
|
14 | return spec -> new EchoExecBuilder(spec, false); | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | /** Creates a stub shell which echoes the specified command line. */ | |
|
|
18 | static Shell echoStderr() { | |
|
|
19 | return spec -> new EchoExecBuilder(spec, false); | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | } | |
| @@ -0,0 +1,36 | |||
|
|
1 | package org.implab.gradle.common.exec; | |
|
|
2 | ||
|
|
3 | import java.io.IOException; | |
|
|
4 | import java.util.ArrayList; | |
|
|
5 | import java.util.List; | |
|
|
6 | import java.util.concurrent.CompletableFuture; | |
|
|
7 | ||
|
|
8 | import org.implab.gradle.common.utils.Exceptions; | |
|
|
9 | ||
|
|
10 | public interface ShellExec extends PipeBuilder, EnvironmentBuilder { | |
|
|
11 | ||
|
|
12 | default CompletableFuture<List<String>> readAllLines() throws IOException { | |
|
|
13 | List<String> lines = new ArrayList<>(); | |
|
|
14 | stdout(RedirectTo.consumer(lines::add)); | |
|
|
15 | ||
|
|
16 | return exec() | |
|
|
17 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
|
|
18 | .thenApply(v -> lines); | |
|
|
19 | } | |
|
|
20 | ||
|
|
21 | default CompletableFuture<String> readAllText() throws IOException { | |
|
|
22 | List<String> lines = new ArrayList<>(); | |
|
|
23 | stdout(RedirectTo.consumer(lines::add)); | |
|
|
24 | ||
|
|
25 | return exec() | |
|
|
26 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
|
|
27 | .thenApply(v -> String.join("\n", lines)); | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | default void assertExitCode(Integer code) throws IOException { | |
|
|
31 | if (code != 0) | |
|
|
32 | throw new IOException(String.format("The process is terminated with code %d", code)); | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | CompletableFuture<Integer> exec() throws IOException; | |
|
|
36 | } | |
| @@ -0,0 +1,108 | |||
|
|
1 | package org.implab.gradle.common.tasks; | |
|
|
2 | ||
|
|
3 | import java.io.IOException; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.Optional; | |
|
|
6 | import java.util.concurrent.ExecutionException; | |
|
|
7 | ||
|
|
8 | import org.gradle.api.DefaultTask; | |
|
|
9 | import org.gradle.api.file.DirectoryProperty; | |
|
|
10 | import org.gradle.api.provider.MapProperty; | |
|
|
11 | import org.gradle.api.provider.Property; | |
|
|
12 | import org.gradle.api.tasks.Internal; | |
|
|
13 | import org.gradle.api.tasks.TaskAction; | |
|
|
14 | import org.implab.gradle.common.dsl.RedirectFromSpec; | |
|
|
15 | import org.implab.gradle.common.dsl.RedirectToSpec; | |
|
|
16 | import org.implab.gradle.common.dsl.TaskEnvSpecMixin; | |
|
|
17 | import org.implab.gradle.common.dsl.TaskPipeSpecMixin; | |
|
|
18 | import org.implab.gradle.common.exec.RedirectTo; | |
|
|
19 | import org.implab.gradle.common.exec.ShellExec; | |
|
|
20 | import org.implab.gradle.common.utils.Exceptions; | |
|
|
21 | import org.implab.gradle.common.utils.Values; | |
|
|
22 | ||
|
|
23 | public abstract class AbstractShellExecTask | |
|
|
24 | extends DefaultTask | |
|
|
25 | implements TaskPipeSpecMixin, TaskEnvSpecMixin { | |
|
|
26 | ||
|
|
27 | private final RedirectToSpec redirectStderr = new RedirectToSpec(); | |
|
|
28 | ||
|
|
29 | private final RedirectToSpec redirectStdout = new RedirectToSpec(); | |
|
|
30 | ||
|
|
31 | private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); | |
|
|
32 | ||
|
|
33 | @Internal | |
|
|
34 | @Override | |
|
|
35 | public abstract Property<Boolean> getInheritEnvironment(); | |
|
|
36 | ||
|
|
37 | @Internal | |
|
|
38 | @Override | |
|
|
39 | public abstract DirectoryProperty getWorkingDirectory(); | |
|
|
40 | ||
|
|
41 | @Internal | |
|
|
42 | @Override | |
|
|
43 | public abstract MapProperty<String, String> getEnvironment(); | |
|
|
44 | ||
|
|
45 | /** | |
|
|
46 | * STDIN redirection, if not specified, no input will be passed to the command | |
|
|
47 | */ | |
|
|
48 | @Internal | |
|
|
49 | @Override | |
|
|
50 | public RedirectFromSpec getStdin() { | |
|
|
51 | return redirectStdin; | |
|
|
52 | } | |
|
|
53 | ||
|
|
54 | /** | |
|
|
55 | * STDOUT redirection, if not specified, redirected to logger::info | |
|
|
56 | */ | |
|
|
57 | @Internal | |
|
|
58 | @Override | |
|
|
59 | public RedirectToSpec getStdout() { | |
|
|
60 | return redirectStdout; | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | /** | |
|
|
64 | * STDERR redirection, if not specified, redirected to logger::error | |
|
|
65 | */ | |
|
|
66 | @Internal | |
|
|
67 | @Override | |
|
|
68 | public RedirectToSpec getStderr() { | |
|
|
69 | return redirectStderr; | |
|
|
70 | } | |
|
|
71 | ||
|
|
72 | protected abstract ShellExec execBuilder(); | |
|
|
73 | ||
|
|
74 | protected Optional<RedirectTo> conventionalStderr() { | |
|
|
75 | return Optional.of(RedirectTo.eachLine(getLogger()::error)); | |
|
|
76 | } | |
|
|
77 | ||
|
|
78 | protected Optional<RedirectTo> conventionalStdout() { | |
|
|
79 | return Optional.of(RedirectTo.eachLine(getLogger()::info)); | |
|
|
80 | } | |
|
|
81 | ||
|
|
82 | @TaskAction | |
|
|
83 | public final void run() throws IOException, InterruptedException, ExecutionException { | |
|
|
84 | // create new shell process | |
|
|
85 | var execBuilder = execBuilder(); | |
|
|
86 | ||
|
|
87 | // configure environment | |
|
|
88 | execBuilder | |
|
|
89 | .workingDirectory(Values.optional(getWorkingDirectory().getAsFile())) | |
|
|
90 | .environment(getEnvironment().getOrElse(Map.of())); | |
|
|
91 | ||
|
|
92 | // configure redirects | |
|
|
93 | execBuilder | |
|
|
94 | .stdout(getStdout().getRedirection().or(this::conventionalStdout)) | |
|
|
95 | .stderr(getStderr().getRedirection().or(this::conventionalStderr)) | |
|
|
96 | .stdin(getStdin().getRedirection()); | |
|
|
97 | ||
|
|
98 | // execute | |
|
|
99 | execBuilder.exec() | |
|
|
100 | .thenAccept(Exceptions.unchecked(this::assertExitCode)) | |
|
|
101 | .join(); | |
|
|
102 | } | |
|
|
103 | ||
|
|
104 | protected void assertExitCode(Integer code) throws IOException { | |
|
|
105 | if (code != 0) | |
|
|
106 | throw new IOException(String.format("The process is terminated with code %s", code)); | |
|
|
107 | } | |
|
|
108 | } No newline at end of file | |
| @@ -0,0 +1,51 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import java.util.function.Consumer; | |
|
|
4 | import java.util.function.Function; | |
|
|
5 | ||
|
|
6 | public class Exceptions { | |
|
|
7 | /** | |
|
|
8 | * Helper function which declares that this block can throw the specified | |
|
|
9 | * exception. | |
|
|
10 | * | |
|
|
11 | * @param <E> | |
|
|
12 | * @param clazz | |
|
|
13 | * @throws E | |
|
|
14 | */ | |
|
|
15 | @SuppressWarnings("unused") | |
|
|
16 | public static <E extends Throwable> void mayThrow(Class<E> clazz) throws E { | |
|
|
17 | } | |
|
|
18 | ||
|
|
19 | @SuppressWarnings("unchecked") | |
|
|
20 | public static <E extends Throwable> E sneakyThrow(Throwable t) throws E { | |
|
|
21 | throw (E) t; | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | public static <T, U> Function<T, U> unchecked(ThrowingFunction<T, U> fn) { | |
|
|
25 | return val -> { | |
|
|
26 | try { | |
|
|
27 | return fn.apply(val); | |
|
|
28 | } catch (Exception e) { | |
|
|
29 | throw sneakyThrow(e); | |
|
|
30 | } | |
|
|
31 | }; | |
|
|
32 | } | |
|
|
33 | ||
|
|
34 | public static <T> Consumer<T> unchecked(ThrowingConsumer<T> c) { | |
|
|
35 | return val -> { | |
|
|
36 | try { | |
|
|
37 | c.accept(val); | |
|
|
38 | } catch (Exception e) { | |
|
|
39 | throw sneakyThrow(e); | |
|
|
40 | } | |
|
|
41 | }; | |
|
|
42 | } | |
|
|
43 | ||
|
|
44 | public interface ThrowingConsumer<T> { | |
|
|
45 | void accept(T value) throws Exception; | |
|
|
46 | } | |
|
|
47 | ||
|
|
48 | public interface ThrowingFunction<T, U> { | |
|
|
49 | U apply(T value) throws Exception; | |
|
|
50 | } | |
|
|
51 | } | |
| @@ -0,0 +1,84 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import java.util.LinkedHashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.Objects; | |
|
|
6 | import java.util.function.Function; | |
|
|
7 | import java.util.stream.Stream; | |
|
|
8 | ||
|
|
9 | import groovy.lang.Closure; | |
|
|
10 | import groovy.lang.GroovyObjectSupport; | |
|
|
11 | ||
|
|
12 | public class JsonDelegate extends GroovyObjectSupport { | |
|
|
13 | private final Map<String, Object> content; | |
|
|
14 | ||
|
|
15 | private JsonDelegate() { | |
|
|
16 | content = new LinkedHashMap<>(); | |
|
|
17 | } | |
|
|
18 | ||
|
|
19 | private JsonDelegate(Map<String, Object> content) { | |
|
|
20 | this.content = new LinkedHashMap<>(content); | |
|
|
21 | } | |
|
|
22 | ||
|
|
23 | public Object invokeMethod(String name, Object args) { | |
|
|
24 | Object val = null; | |
|
|
25 | if (args != null && Object[].class.isAssignableFrom(args.getClass())) { | |
|
|
26 | Object[] arr = (Object[]) args; | |
|
|
27 | if (arr.length == 1) { | |
|
|
28 | val = processValue(arr[0], content.get(name)); | |
|
|
29 | } else if (arr.length == 2 && arr[1] instanceof Closure<?> c) { | |
|
|
30 | if (arr[0] instanceof Iterable<?> iter) { | |
|
|
31 | val = processStream(Values.stream(iter.iterator()), c); | |
|
|
32 | } else if(arr[0] != null && arr[0].getClass().isArray()) { | |
|
|
33 | val = processStream(Stream.of((Object)arr[0]), c); | |
|
|
34 | } else { | |
|
|
35 | val = arr; | |
|
|
36 | } | |
|
|
37 | } else { | |
|
|
38 | val = arr; | |
|
|
39 | } | |
|
|
40 | } | |
|
|
41 | ||
|
|
42 | this.content.put(name, val); | |
|
|
43 | return val; | |
|
|
44 | } | |
|
|
45 | ||
|
|
46 | private Object processValue(Object value, Object original) { | |
|
|
47 | if (value instanceof Closure<?> c) { | |
|
|
48 | return Objects.nonNull(original) ? of(c, original) : of(c); | |
|
|
49 | } else { | |
|
|
50 | return value; | |
|
|
51 | } | |
|
|
52 | } | |
|
|
53 | ||
|
|
54 | private Object processStream(Stream<?> stream, Closure<?> c) { | |
|
|
55 | return stream.map(transform(c)).toArray(); | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | public static Map<String, Object> of(Closure<?> c) { | |
|
|
59 | return invoke((Closure<?>) c.clone(), new JsonDelegate()); | |
|
|
60 | } | |
|
|
61 | ||
|
|
62 | public static Function<Object, Map<String, Object>> transform(Closure<?> c) { | |
|
|
63 | return o -> of(c, o); | |
|
|
64 | } | |
|
|
65 | ||
|
|
66 | public static Map<String, Object> of(Closure<?> c, Object o) { | |
|
|
67 | return invoke((Closure<?>) c.curry(o), new JsonDelegate()); | |
|
|
68 | } | |
|
|
69 | ||
|
|
70 | public static Map<String, Object> with(Closure<?> c, Map<String, Object> m) { | |
|
|
71 | return invoke((Closure<?>) c.clone(), new JsonDelegate(m)); | |
|
|
72 | } | |
|
|
73 | ||
|
|
74 | private static Map<String, Object> invoke(Closure<?> c, JsonDelegate d) { | |
|
|
75 | c.setDelegate(d); | |
|
|
76 | c.setResolveStrategy(1); | |
|
|
77 | c.call(); | |
|
|
78 | return d.getContent(); | |
|
|
79 | } | |
|
|
80 | ||
|
|
81 | public Map<String, Object> getContent() { | |
|
|
82 | return this.content; | |
|
|
83 | } | |
|
|
84 | } | |
| @@ -0,0 +1,63 | |||
|
|
1 | package org.implab.gradle.common.utils; | |
|
|
2 | ||
|
|
3 | import java.util.HashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.function.Function; | |
|
|
6 | ||
|
|
7 | import org.gradle.api.Action; | |
|
|
8 | import org.gradle.api.provider.ListProperty; | |
|
|
9 | import org.gradle.api.provider.MapProperty; | |
|
|
10 | import org.gradle.api.provider.Provider; | |
|
|
11 | ||
|
|
12 | public final class Properties { | |
|
|
13 | private Properties() { | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { | |
|
|
17 | map.forEach((k, v) -> { | |
|
|
18 | if (v instanceof Provider<?>) | |
|
|
19 | property.put(k, (Provider<?>) v); | |
|
|
20 | else | |
|
|
21 | property.put(k, v); | |
|
|
22 | }); | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, Function<Object, V> mapper) { | |
|
|
26 | map.forEach((k, v) -> { | |
|
|
27 | if (v instanceof Provider<?>) | |
|
|
28 | property.put(k, ((Provider<?>) v).map(mapper::apply)); | |
|
|
29 | else | |
|
|
30 | property.put(k, mapper.apply(v)); | |
|
|
31 | }); | |
|
|
32 | } | |
|
|
33 | ||
|
|
34 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { | |
|
|
35 | values.forEach(v -> { | |
|
|
36 | if (v instanceof Provider<?>) | |
|
|
37 | property.add((Provider<?>) v); | |
|
|
38 | else | |
|
|
39 | property.add(v); | |
|
|
40 | }); | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, Function<Object, V> mapper) { | |
|
|
44 | values.forEach(v -> { | |
|
|
45 | if (v instanceof Provider<?>) | |
|
|
46 | property.add(((Provider<?>) v).map(mapper::apply)); | |
|
|
47 | else | |
|
|
48 | property.add(mapper.apply(v)); | |
|
|
49 | }); | |
|
|
50 | } | |
|
|
51 | ||
|
|
52 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<Map<K, ?>> configure) { | |
|
|
53 | var map = new HashMap<K, Object>(); | |
|
|
54 | configure.execute(map); | |
|
|
55 | mergeMap(prop, map); | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | public static <K, V> void configureMap(MapProperty<K, V> prop, Action<Map<K, ?>> configure, Function<Object, V> mapper) { | |
|
|
59 | var map = new HashMap<K, Object>(); | |
|
|
60 | configure.execute(map); | |
|
|
61 | mergeMap(prop, map, mapper); | |
|
|
62 | } | |
|
|
63 | } | |
| @@ -1,45 +1,49 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.InputStream; |
|
|
5 | 5 | import java.util.Optional; |
|
|
6 | 6 | import java.util.function.Supplier; |
|
|
7 | 7 | |
|
|
8 | 8 | import org.gradle.api.provider.Provider; |
|
|
9 | 9 | import org.implab.gradle.common.exec.RedirectFrom; |
|
|
10 | 10 | |
|
|
11 | 11 | public class RedirectFromSpec { |
|
|
12 | 12 | private Supplier<RedirectFrom> streamRedirect; |
|
|
13 | 13 | |
|
|
14 | 14 | public boolean isRedirected() { |
|
|
15 | 15 | return streamRedirect != null; |
|
|
16 | 16 | } |
|
|
17 | 17 | |
|
|
18 | 18 | public Optional<RedirectFrom> getRedirection() { |
|
|
19 | 19 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
|
20 | 20 | } |
|
|
21 | 21 | |
|
|
22 | 22 | public void fromFile(File file) { |
|
|
23 |
|
|
|
|
23 | streamRedirect = () -> RedirectFrom.file(file); | |
|
|
24 | 24 | } |
|
|
25 | 25 | |
|
|
26 | 26 | public void fromFile(Provider<File> fileProvider) { |
|
|
27 |
|
|
|
|
27 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; | |
|
|
28 | 28 | } |
|
|
29 | 29 | |
|
|
30 | 30 | public void fromStream(InputStream stream) { |
|
|
31 |
|
|
|
|
31 | streamRedirect = () -> RedirectFrom.stream(stream); | |
|
|
32 | 32 | } |
|
|
33 | 33 | |
|
|
34 | 34 | public void fromStream(Provider<InputStream> streamProvider) { |
|
|
35 |
|
|
|
|
35 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; | |
|
|
36 | 36 | } |
|
|
37 | 37 | |
|
|
38 | 38 | public void from(Object input) { |
|
|
39 | 39 | if (input instanceof Provider<?> inputProvider) { |
|
|
40 |
|
|
|
|
40 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; | |
|
|
41 | 41 | } else { |
|
|
42 |
|
|
|
|
42 | streamRedirect = () -> RedirectFrom.any(input); | |
|
|
43 | 43 | } |
|
|
44 | 44 | } |
|
|
45 | ||
|
|
46 | public void empty() { | |
|
|
47 | streamRedirect = () -> null; | |
|
|
48 | } | |
|
|
45 | 49 | } |
| @@ -1,57 +1,65 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.OutputStream; |
|
|
5 | 5 | import java.util.Optional; |
|
|
6 | 6 | import java.util.function.Consumer; |
|
|
7 | 7 | import java.util.function.Supplier; |
|
|
8 | 8 | |
|
|
9 | 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
10 | 10 | import org.eclipse.jdt.annotation.Nullable; |
|
|
11 | 11 | import org.gradle.api.provider.Provider; |
|
|
12 | 12 | import org.implab.gradle.common.exec.RedirectTo; |
|
|
13 | 13 | |
|
|
14 | 14 | @NonNullByDefault |
|
|
15 | 15 | public class RedirectToSpec { |
|
|
16 | 16 | private Supplier<RedirectTo> streamRedirect; |
|
|
17 | ||
|
|
17 | ||
|
|
18 | 18 | public boolean isRedirected() { |
|
|
19 | 19 | return getRedirection().isPresent(); |
|
|
20 | 20 | } |
|
|
21 | 21 | |
|
|
22 | 22 | public Optional<RedirectTo> getRedirection() { |
|
|
23 | 23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
|
24 | 24 | } |
|
|
25 | 25 | |
|
|
26 | 26 | public @Nullable RedirectTo getRedirectionOrNull() { |
|
|
27 | 27 | return streamRedirect != null ? streamRedirect.get() : null; |
|
|
28 | 28 | } |
|
|
29 | 29 | |
|
|
30 | 30 | public void toFile(File file) { |
|
|
31 |
|
|
|
|
31 | streamRedirect = () -> RedirectTo.file(file); | |
|
|
32 | 32 | } |
|
|
33 | 33 | |
|
|
34 | 34 | public void toFile(Provider<File> fileProvider) { |
|
|
35 |
|
|
|
|
35 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; | |
|
|
36 | 36 | } |
|
|
37 | 37 | |
|
|
38 | 38 | public void toStream(OutputStream stream) { |
|
|
39 |
|
|
|
|
39 | streamRedirect = () -> RedirectTo.stream(stream); | |
|
|
40 | 40 | } |
|
|
41 | 41 | |
|
|
42 | 42 | public void toStream(Provider<OutputStream> streamProvider) { |
|
|
43 |
|
|
|
|
43 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; | |
|
|
44 | 44 | } |
|
|
45 | 45 | |
|
|
46 | 46 | public void to(Object output) { |
|
|
47 | 47 | if (output instanceof Provider<?> outputProvider) { |
|
|
48 |
|
|
|
|
48 | streamRedirect = outputProvider.map(RedirectTo::any)::get; | |
|
|
49 | 49 | } else { |
|
|
50 |
|
|
|
|
50 | streamRedirect = () -> RedirectTo.any(output); | |
|
|
51 | 51 | } |
|
|
52 | 52 | } |
|
|
53 | 53 | |
|
|
54 |
public void |
|
|
|
55 |
|
|
|
|
54 | public void eachLine(Consumer<String> consumer) { | |
|
|
55 | streamRedirect = () -> RedirectTo.eachLine(consumer); | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | public void allText(Consumer<String> consumer) { | |
|
|
59 | streamRedirect = () -> RedirectTo.allText(consumer); | |
|
|
60 | } | |
|
|
61 | ||
|
|
62 | public void discard() { | |
|
|
63 | streamRedirect = () -> null; | |
|
|
56 | 64 | } |
|
|
57 | 65 | } |
| @@ -1,13 +1,26 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | import java.util.stream.Stream; | |
|
|
3 | 4 | |
|
|
4 | 5 | import org.gradle.api.provider.ListProperty; |
|
|
6 | import org.implab.gradle.common.utils.Properties; | |
|
|
5 | 7 | |
|
|
6 | 8 | public interface TaskCommandSpecMixin { |
|
|
7 | 9 | ListProperty<String> getCommandLine(); |
|
|
8 | 10 | |
|
|
9 |
void commandLine(Object arg0, Object... args) |
|
|
|
11 | default void commandLine(Object arg0, Object... args) { | |
|
|
12 | getCommandLine().empty(); | |
|
|
13 | Properties.mergeList( | |
|
|
14 | getCommandLine(), | |
|
|
15 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), | |
|
|
16 | Object::toString); | |
|
|
17 | } | |
|
|
10 | 18 | |
|
|
11 |
void args(Object arg0, Object... args) |
|
|
|
19 | default void args(Object arg0, Object... args) { | |
|
|
20 | Properties.mergeList( | |
|
|
21 | getCommandLine(), | |
|
|
22 | () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), | |
|
|
23 | Object::toString); | |
|
|
24 | } | |
|
|
12 | 25 | |
|
|
13 | 26 | } |
| @@ -1,47 +1,45 | |||
|
|
1 | 1 | package org.implab.gradle.common.dsl; |
|
|
2 | 2 | |
|
|
3 | import java.util.HashMap; | |
|
|
4 | 3 | import java.util.Map; |
|
|
5 | 4 | |
|
|
6 | 5 | import org.gradle.api.Action; |
|
|
7 | 6 | import org.gradle.api.file.DirectoryProperty; |
|
|
8 | 7 | import org.gradle.api.provider.MapProperty; |
|
|
9 | 8 | import org.gradle.api.provider.Property; |
|
|
10 | import org.gradle.api.provider.Provider; | |
|
|
11 | 9 | import org.implab.gradle.common.utils.Closures; |
|
|
12 |
import org.implab.gradle.common.utils. |
|
|
|
10 | import org.implab.gradle.common.utils.Properties; | |
|
|
13 | 11 | |
|
|
14 | 12 | import groovy.lang.Closure; |
|
|
15 | 13 | |
|
|
16 | 14 | /** |
|
|
17 | 15 | * Configuration properties of the execution shell. This object specifies a |
|
|
18 | 16 | * working directory and environment variables for the processes started within |
|
|
19 | 17 | * this shell. |
|
|
20 | 18 | */ |
|
|
21 | 19 | public interface TaskEnvSpecMixin { |
|
|
22 | 20 | |
|
|
23 | 21 | /** Inherit environment from current process */ |
|
|
24 | 22 | Property<Boolean> getInheritEnvironment(); |
|
|
25 | 23 | |
|
|
26 | 24 | /** Working directory */ |
|
|
27 | 25 | DirectoryProperty getWorkingDirectory(); |
|
|
28 | 26 | |
|
|
29 | 27 | /** Environment variables */ |
|
|
30 | 28 | MapProperty<String, String> getEnvironment(); |
|
|
31 | 29 | |
|
|
32 | 30 | /** |
|
|
33 | 31 | * Configures the environment variable using the specified action. The |
|
|
34 | 32 | * action is called when the value is calculated; |
|
|
35 | 33 | * |
|
|
36 | 34 | * <p> |
|
|
37 | 35 | * The configuration action is called immediately. To support lazy evaluation, |
|
|
38 | 36 | * properties may be assigned to providers. |
|
|
39 | 37 | */ |
|
|
40 | 38 | default void env(Action<Map<String, ?>> configure) { |
|
|
41 |
|
|
|
|
39 | Properties.configureMap(getEnvironment(), configure, Object::toString); | |
|
|
42 | 40 | } |
|
|
43 | 41 | |
|
|
44 | 42 | default void env(Closure<?> configure) { |
|
|
45 | 43 | env(Closures.action(configure)); |
|
|
46 | 44 | } |
|
|
47 | 45 | } |
| @@ -1,83 +1,85 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.function.Consumer; |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | import org.implab.gradle.common.utils.Values; |
|
|
7 | 7 | |
|
|
8 | 8 | /** Command builder interface, used to specify the executable and parameters */ |
|
|
9 | 9 | @NonNullByDefault |
|
|
10 | 10 | public interface CommandBuilder { |
|
|
11 | 11 | |
|
|
12 | 12 | /** Sets the executable, the parameters are left intact. */ |
|
|
13 | 13 | CommandBuilder executable(String executable); |
|
|
14 | 14 | |
|
|
15 | 15 | /** |
|
|
16 | 16 | * Sets the specified executable and parameters, old executable and parameters |
|
|
17 | 17 | * are discarded. |
|
|
18 | 18 | */ |
|
|
19 | 19 | default CommandBuilder commandLine(String executable, String... args) { |
|
|
20 | 20 | return executable(executable) |
|
|
21 | 21 | .arguments(args); |
|
|
22 | 22 | } |
|
|
23 | 23 | |
|
|
24 | 24 | /** |
|
|
25 | 25 | * Sets the specified executable and parameters, old executable and parameters |
|
|
26 | 26 | * are discarded. |
|
|
27 |
* |
|
|
|
27 | * | |
|
|
28 | 28 | * @param command The command line. Must contain at least one element |
|
|
29 | 29 | * (executable). |
|
|
30 |
* |
|
|
|
30 | * | |
|
|
31 | 31 | */ |
|
|
32 | 32 | default CommandBuilder commandLine(Iterable<? extends String> command) { |
|
|
33 | 33 | var iterator = command.iterator(); |
|
|
34 | 34 | |
|
|
35 | 35 | // set executable |
|
|
36 | 36 | executable(Values.take(iterator).orElseThrow()); |
|
|
37 | 37 | // cleat arguments |
|
|
38 | 38 | arguments(); |
|
|
39 | 39 | |
|
|
40 | 40 | // set new arguments |
|
|
41 | 41 | while (iterator.hasNext()) |
|
|
42 | 42 | addArguments(iterator.next()); |
|
|
43 | 43 | |
|
|
44 | 44 | return this; |
|
|
45 | 45 | } |
|
|
46 | 46 | |
|
|
47 | 47 | default Consumer<Boolean> flag(String name) { |
|
|
48 | 48 | return f -> { |
|
|
49 | 49 | if (f) |
|
|
50 | 50 | addArguments(name); |
|
|
51 | 51 | }; |
|
|
52 | 52 | } |
|
|
53 | 53 | |
|
|
54 | 54 | default Consumer<String> param(String name) { |
|
|
55 | 55 | return v -> { |
|
|
56 | 56 | if (v != null && v.length() > 0) |
|
|
57 | 57 | addArguments(name, v); |
|
|
58 | 58 | }; |
|
|
59 | 59 | } |
|
|
60 | 60 | |
|
|
61 | 61 | /** Adds the specified arguments to this builder */ |
|
|
62 | 62 | CommandBuilder addArguments(String... args); |
|
|
63 | 63 | |
|
|
64 | 64 | default CommandBuilder addArguments(Iterable<String> args) { |
|
|
65 | 65 | for (String arg : args) |
|
|
66 | 66 | addArguments(arg); |
|
|
67 | 67 | |
|
|
68 | 68 | return this; |
|
|
69 | 69 | } |
|
|
70 | 70 | |
|
|
71 | 71 | /** Replaces arguments in the builder with the specified one. */ |
|
|
72 | 72 | default CommandBuilder arguments(String... args) { |
|
|
73 | 73 | return arguments(Values.iterable(args)); |
|
|
74 | 74 | } |
|
|
75 | 75 | |
|
|
76 | 76 | /** Replaces arguments in the builder with the specified one. */ |
|
|
77 | 77 | CommandBuilder arguments(Iterable<String> args); |
|
|
78 | 78 | |
|
|
79 | 79 | default CommandBuilder from(CommandSpec commandSpec) { |
|
|
80 | 80 | return executable(commandSpec.executable()) |
|
|
81 | 81 | .arguments(commandSpec.arguments()); |
|
|
82 | 82 | } |
|
|
83 | ||
|
|
84 | CommandSpec build(); | |
|
|
83 | 85 | } |
| @@ -1,16 +1,20 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.List; |
|
|
4 | 4 | import java.util.stream.Stream; |
|
|
5 | 5 | |
|
|
6 | 6 | public interface CommandSpec { |
|
|
7 | 7 | |
|
|
8 | 8 | String executable(); |
|
|
9 | 9 | |
|
|
10 | 10 | List<String> arguments(); |
|
|
11 | 11 | |
|
|
12 | 12 | default List<String> commandLine() { |
|
|
13 | 13 | return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); |
|
|
14 | 14 | } |
|
|
15 | 15 | |
|
|
16 | public static CommandBuilder builder() { | |
|
|
17 | return new CommandSpecRecord.Builder(); | |
|
|
18 | } | |
|
|
19 | ||
|
|
16 | 20 | } |
| @@ -1,39 +1,40 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.ByteArrayInputStream; |
|
|
4 | 4 | import java.io.IOException; |
|
|
5 | 5 | import java.nio.charset.StandardCharsets; |
|
|
6 | 6 | import java.util.concurrent.CompletableFuture; |
|
|
7 | 7 | import java.util.stream.Collectors; |
|
|
8 | 8 | |
|
|
9 | class EchoExecBuilder extends ExecBuilder { | |
|
|
9 | class EchoExecBuilder extends AbstractExecBuilder<CommandSpec> { | |
|
|
10 | 10 | |
|
|
11 | 11 | private final boolean echoToStderr; |
|
|
12 | 12 | |
|
|
13 | public EchoExecBuilder(boolean echoToStderr) { | |
|
|
13 | public EchoExecBuilder(CommandSpec command, boolean echoToStderr) { | |
|
|
14 | super(command); | |
|
|
14 | 15 | this.echoToStderr = echoToStderr; |
|
|
15 | 16 | } |
|
|
16 | 17 | |
|
|
17 | 18 | @Override |
|
|
18 | 19 | protected CompletableFuture<Integer> startInternal( |
|
|
19 | 20 | CommandSpec command, |
|
|
20 | 21 | EnvironmentSpec environment, |
|
|
21 | 22 | PipeSpec redirect) throws IOException { |
|
|
22 | 23 | |
|
|
23 | 24 | var outputRedirect = echoToStderr ? redirect.stderr() : redirect.stdout(); |
|
|
24 | 25 | |
|
|
25 | 26 | return outputRedirect |
|
|
26 | 27 | .map(to -> { |
|
|
27 | 28 | var bytes = String |
|
|
28 | 29 | .format( |
|
|
29 | 30 | "exec: %s", |
|
|
30 | 31 | command.commandLine().stream().collect(Collectors.joining(" "))) |
|
|
31 | 32 | .getBytes(StandardCharsets.UTF_8); |
|
|
32 | 33 | |
|
|
33 | 34 | return to.redirect(new ByteArrayInputStream(bytes)) |
|
|
34 | 35 | .thenApply((x) -> 0); |
|
|
35 | 36 | }) |
|
|
36 | 37 | .orElse(CompletableFuture.completedFuture(0)); |
|
|
37 | 38 | } |
|
|
38 | 39 | |
|
|
39 | 40 | } |
| @@ -1,14 +1,20 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.Optional; |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | |
|
|
7 | /** | |
|
|
8 | * The execution shell uses this specification when starting | |
|
|
9 | * a new process. The shell may check for the specified | |
|
|
10 | * redirections and apply them when launching the process. | |
|
|
11 | * The exact moment they are applied is at the shell’s discretion. | |
|
|
12 | */ | |
|
|
7 | 13 | @NonNullByDefault |
|
|
8 | 14 | public interface PipeSpec { |
|
|
9 | 15 | Optional<RedirectTo> stdout(); |
|
|
10 | 16 | |
|
|
11 | 17 | Optional<RedirectTo> stderr(); |
|
|
12 | 18 | |
|
|
13 | 19 | Optional<RedirectFrom> stdin(); |
|
|
14 | 20 | } |
| @@ -1,90 +1,111 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.File; |
|
|
4 | 4 | import java.io.FileOutputStream; |
|
|
5 | 5 | import java.io.InputStream; |
|
|
6 | 6 | import java.io.OutputStream; |
|
|
7 | 7 | import java.util.Scanner; |
|
|
8 | 8 | import java.util.concurrent.CompletableFuture; |
|
|
9 | 9 | import java.util.function.Consumer; |
|
|
10 | import java.util.stream.Collectors; | |
|
|
11 | import java.util.stream.Stream; | |
|
|
10 | 12 | |
|
|
11 | 13 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
12 | 14 | |
|
|
13 | 15 | /** |
|
|
14 | 16 | * Redirection specification for the {@link InputStream}. Redirection is invoked |
|
|
15 | 17 | * when the {@link InputStream} becomes available, for example, on process |
|
|
16 | 18 | * start. Before the process is started the redirection isn't invoked and no |
|
|
17 | 19 | * resources are allocated or used. |
|
|
18 | 20 | */ |
|
|
19 | 21 | @NonNullByDefault |
|
|
20 | 22 | public interface RedirectTo { |
|
|
21 | 23 | CompletableFuture<Void> redirect(InputStream from); |
|
|
22 | 24 | |
|
|
23 | 25 | public interface StringConsumer extends Consumer<String> { |
|
|
26 | default void complete() { | |
|
|
27 | } | |
|
|
24 | 28 | } |
|
|
25 | 29 | |
|
|
26 | 30 | /** Creates a redirect to the specified consumer */ |
|
|
27 |
public static RedirectTo |
|
|
|
31 | public static RedirectTo eachLine(final Consumer<String> consumer) { | |
|
|
28 | 32 | return consumer(new StringConsumer() { |
|
|
29 | 33 | @Override |
|
|
30 | 34 | public void accept(String s) { |
|
|
31 | 35 | consumer.accept(s); |
|
|
32 | 36 | } |
|
|
33 | 37 | }); |
|
|
34 | 38 | } |
|
|
35 | 39 | |
|
|
40 | public static RedirectTo allText(final Consumer<String> consumer) { | |
|
|
41 | return consumer(new StringConsumer() { | |
|
|
42 | final Stream.Builder<String> builder = Stream.builder(); | |
|
|
43 | ||
|
|
44 | @Override | |
|
|
45 | public void accept(String s) { | |
|
|
46 | builder.accept(s); | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | @Override | |
|
|
50 | public void complete() { | |
|
|
51 | consumer.accept(builder.build().collect(Collectors.joining("\n"))); | |
|
|
52 | } | |
|
|
53 | }); | |
|
|
54 | } | |
|
|
55 | ||
|
|
36 | 56 | /** Creates a redirect to the specified consumer */ |
|
|
37 | 57 | public static RedirectTo consumer(final StringConsumer consumer) { |
|
|
38 | 58 | return (src) -> CompletableFuture.runAsync(() -> { |
|
|
39 | 59 | try (Scanner sc = new Scanner(src)) { |
|
|
40 | 60 | while (sc.hasNextLine()) { |
|
|
41 | 61 | consumer.accept(sc.nextLine()); |
|
|
42 | 62 | } |
|
|
63 | consumer.complete(); | |
|
|
43 | 64 | } |
|
|
44 | 65 | }); |
|
|
45 | 66 | } |
|
|
46 | 67 | |
|
|
47 | 68 | /** |
|
|
48 | 69 | * Creates a redirect to the specified file. There will be opened the output |
|
|
49 | 70 | * stream in this redirection and original stream will be transferred to this |
|
|
50 | 71 | * this output stream. |
|
|
51 | 72 | */ |
|
|
52 | 73 | public static RedirectTo file(final File file) { |
|
|
53 | 74 | return src -> CompletableFuture.runAsync(() -> { |
|
|
54 | try (OutputStream out = new FileOutputStream(file)) { | |
|
|
75 | try (src; OutputStream out = new FileOutputStream(file)) { | |
|
|
55 | 76 | src.transferTo(out); |
|
|
56 | 77 | } catch (Exception e) { |
|
|
57 | 78 | // silence! |
|
|
58 | 79 | } |
|
|
59 | 80 | }); |
|
|
60 | 81 | } |
|
|
61 | 82 | |
|
|
62 | 83 | /** Creates a redirect to the specified output stream. */ |
|
|
63 | 84 | public static RedirectTo stream(final OutputStream dest) { |
|
|
64 | 85 | return src -> CompletableFuture.runAsync(() -> { |
|
|
65 | 86 | try (dest; src) { |
|
|
66 | 87 | src.transferTo(dest); |
|
|
67 | 88 | } catch (Exception e) { |
|
|
68 | 89 | // silence! |
|
|
69 | 90 | } |
|
|
70 | 91 | }); |
|
|
71 | 92 | } |
|
|
72 | 93 | |
|
|
73 | 94 | /** |
|
|
74 | 95 | * Creates the redirection to the specified destination, actual type of |
|
|
75 | 96 | * redirection will be determined from the type of the output object. |
|
|
76 | 97 | */ |
|
|
77 | 98 | public static RedirectTo any(final Object output) { |
|
|
78 | 99 | if (output instanceof StringConsumer fn) { |
|
|
79 | 100 | return consumer(s -> fn.accept(s)); |
|
|
80 | 101 | } else if (output instanceof File f) { |
|
|
81 | 102 | return file(f); |
|
|
82 | 103 | } else if (output instanceof OutputStream stm) { |
|
|
83 | 104 | return stream(stm); |
|
|
84 | 105 | } else if (output instanceof RedirectTo self) { |
|
|
85 | 106 | return self; |
|
|
86 | 107 | } else { |
|
|
87 | 108 | throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); |
|
|
88 | 109 | } |
|
|
89 | 110 | } |
|
|
90 | 111 | } No newline at end of file |
| @@ -1,45 +1,49 | |||
|
|
1 | 1 | package org.implab.gradle.common.exec; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.io.IOException; |
|
|
4 | 4 | import java.lang.ProcessBuilder.Redirect; |
|
|
5 | 5 | import java.util.ArrayList; |
|
|
6 | 6 | import java.util.concurrent.CompletableFuture; |
|
|
7 | 7 | |
|
|
8 | class SystemExecBuilder extends ExecBuilder { | |
|
|
8 | class SystemExecBuilder extends AbstractExecBuilder<CommandSpec> { | |
|
|
9 | SystemExecBuilder(CommandSpec command) { | |
|
|
10 | super(command); | |
|
|
11 | } | |
|
|
12 | ||
|
|
9 | 13 | @Override |
|
|
10 | 14 | protected CompletableFuture<Integer> startInternal( |
|
|
11 | 15 | CommandSpec command, |
|
|
12 | 16 | EnvironmentSpec environment, |
|
|
13 | 17 | PipeSpec redirect) throws IOException { |
|
|
14 | 18 | |
|
|
15 | 19 | var builder = new ProcessBuilder(command.commandLine()); |
|
|
16 | 20 | |
|
|
17 | 21 | environment.workingDirectory().ifPresent(builder::directory); |
|
|
18 | 22 | |
|
|
19 | 23 | // if the env isn't inherited we need to clear it |
|
|
20 | 24 | if (!environment.inheritEnvironment()) |
|
|
21 | 25 | builder.environment().clear(); |
|
|
22 | 26 | |
|
|
23 | 27 | builder.environment().putAll(environment.environment()); |
|
|
24 | 28 | |
|
|
25 | 29 | var tasks = new ArrayList<CompletableFuture<?>>(); |
|
|
26 | 30 | |
|
|
27 | 31 | if (!redirect.stdout().isPresent()) |
|
|
28 | 32 | builder.redirectOutput(Redirect.DISCARD); |
|
|
29 | 33 | if (!redirect.stderr().isPresent()) |
|
|
30 | 34 | builder.redirectError(Redirect.DISCARD); |
|
|
31 | 35 | |
|
|
32 | 36 | // run process |
|
|
33 | 37 | var proc = builder.start(); |
|
|
34 | 38 | |
|
|
35 | 39 | tasks.add(proc.onExit()); |
|
|
36 | 40 | |
|
|
37 | 41 | redirect.stdin().map(from -> from.redirect(proc.getOutputStream())).ifPresent(tasks::add); |
|
|
38 | 42 | redirect.stdout().map(to -> to.redirect(proc.getInputStream())).ifPresent(tasks::add); |
|
|
39 | 43 | redirect.stderr().map(to -> to.redirect(proc.getErrorStream())).ifPresent(tasks::add); |
|
|
40 | 44 | |
|
|
41 | 45 | return CompletableFuture |
|
|
42 | 46 | .allOf(tasks.toArray(new CompletableFuture<?>[0])) |
|
|
43 | 47 | .thenApply(t -> proc.exitValue()); |
|
|
44 | 48 | } |
|
|
45 | 49 | } |
| @@ -1,115 +1,31 | |||
|
|
1 | 1 | package org.implab.gradle.common.tasks; |
|
|
2 | 2 | |
|
|
3 | import java.io.IOException; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.concurrent.ExecutionException; | |
|
|
6 | import java.util.stream.Stream; | |
|
|
7 | ||
|
|
8 | import org.gradle.api.DefaultTask; | |
|
|
9 | import org.gradle.api.file.DirectoryProperty; | |
|
|
10 | 3 | import org.gradle.api.provider.ListProperty; |
|
|
11 | import org.gradle.api.provider.MapProperty; | |
|
|
12 | 4 | import org.gradle.api.provider.Property; |
|
|
13 | 5 | import org.gradle.api.tasks.Internal; |
|
|
14 | import org.gradle.api.tasks.TaskAction; | |
|
|
15 | 6 | import org.implab.gradle.common.dsl.TaskCommandSpecMixin; |
|
|
16 |
import org.implab.gradle.common. |
|
|
|
17 |
import org.implab.gradle.common. |
|
|
|
18 |
import org.implab.gradle.common. |
|
|
|
19 | import org.implab.gradle.common.dsl.TaskEnvSpecMixin; | |
|
|
20 | import org.implab.gradle.common.exec.ExecBuilder; | |
|
|
21 | import org.implab.gradle.common.utils.ObjectsMixin; | |
|
|
22 | import org.implab.gradle.common.utils.Strings; | |
|
|
23 | import org.implab.gradle.common.utils.ThrowingConsumer; | |
|
|
7 | import org.implab.gradle.common.exec.CommandSpec; | |
|
|
8 | import org.implab.gradle.common.exec.Shell; | |
|
|
9 | import org.implab.gradle.common.exec.ShellExec; | |
|
|
24 | 10 | |
|
|
25 | 11 | public abstract class ShellExecTask |
|
|
26 |
extends |
|
|
|
27 |
implements TaskCommandSpecMixin |
|
|
|
28 | ||
|
|
29 | private final RedirectToSpec redirectStderr = new RedirectToSpec(); | |
|
|
30 | ||
|
|
31 | private final RedirectToSpec redirectStdout = new RedirectToSpec(); | |
|
|
32 | ||
|
|
33 | private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); | |
|
|
12 | extends AbstractShellExecTask | |
|
|
13 | implements TaskCommandSpecMixin { | |
|
|
34 | 14 | |
|
|
35 | 15 | @Internal |
|
|
36 | @Override | |
|
|
37 | public abstract Property<Boolean> getInheritEnvironment(); | |
|
|
38 | ||
|
|
39 | @Internal | |
|
|
40 | @Override | |
|
|
41 | public abstract DirectoryProperty getWorkingDirectory(); | |
|
|
42 | ||
|
|
43 | @Internal | |
|
|
44 | @Override | |
|
|
45 | public abstract MapProperty<String, String> getEnvironment(); | |
|
|
16 | public abstract Property<Shell> getShell(); | |
|
|
46 | 17 | |
|
|
47 | 18 | @Internal |
|
|
48 | 19 | @Override |
|
|
49 | 20 | public abstract ListProperty<String> getCommandLine(); |
|
|
50 | 21 | |
|
|
51 | /** | |
|
|
52 | * STDIN redirection, if not specified, no input will be passed to the command | |
|
|
53 | */ | |
|
|
54 | @Internal | |
|
|
55 | @Override | |
|
|
56 | public RedirectFromSpec getStdin() { | |
|
|
57 | return redirectStdin; | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | /** | |
|
|
61 | * STDOUT redirection, if not specified, redirected to logger::info | |
|
|
62 | */ | |
|
|
63 | @Internal | |
|
|
64 | @Override | |
|
|
65 | public RedirectToSpec getStdout() { | |
|
|
66 | return redirectStdout; | |
|
|
67 | } | |
|
|
68 | ||
|
|
69 | /** | |
|
|
70 | * STDERR redirection, if not specified, redirected to logger::error | |
|
|
71 | */ | |
|
|
72 | @Internal | |
|
|
73 | @Override | |
|
|
74 | public RedirectToSpec getStderr() { | |
|
|
75 | return redirectStderr; | |
|
|
76 | } | |
|
|
77 | 22 | |
|
|
78 | 23 | @Override |
|
|
79 | public void commandLine(Object arg0, Object... args) { | |
|
|
80 | getCommandLine().set(provider(() -> Stream.concat( | |
|
|
81 |
|
|
|
|
82 | Stream.of(args)) | |
|
|
83 | .map(Strings::asString).toList())); | |
|
|
84 | ||
|
|
85 | } | |
|
|
86 | ||
|
|
87 | @Override | |
|
|
88 | public void args(Object arg0, Object... args) { | |
|
|
89 | getCommandLine().addAll(provider(() -> Stream.concat( | |
|
|
90 | Stream.of(arg0), | |
|
|
91 | Stream.of(args)) | |
|
|
92 | .map(Strings::asString).toList())); | |
|
|
93 | } | |
|
|
94 | ||
|
|
95 | protected abstract ExecBuilder execBuilder(); | |
|
|
96 | ||
|
|
97 | @TaskAction | |
|
|
98 | public final void run() throws IOException, InterruptedException, ExecutionException { | |
|
|
99 | var execBuilder = execBuilder(); | |
|
|
100 | execBuilder.workingDirectory(getWorkingDirectory().get().getAsFile()); | |
|
|
101 | execBuilder.environment(getEnvironment().getOrElse(Map.of())); | |
|
|
102 | execBuilder.commandLine(getCommandLine().get()); | |
|
|
103 | ||
|
|
104 | getStdout().getRedirection().ifPresent(execBuilder::stdout); | |
|
|
105 | getStderr().getRedirection().ifPresent(execBuilder::stderr); | |
|
|
106 | getStdin().getRedirection().ifPresent(execBuilder::stdin); | |
|
|
107 | ||
|
|
108 | execBuilder.exec().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); | |
|
|
109 | } | |
|
|
110 | ||
|
|
111 | protected void checkRetCode(Integer code) throws IOException { | |
|
|
112 | throw new IOException(String.format("The process is terminated with code %s", code)); | |
|
|
24 | protected ShellExec execBuilder() { | |
|
|
25 | return getShell().get() | |
|
|
26 | .of(CommandSpec.builder() | |
|
|
27 | .commandLine(getCommandLine().get()) | |
|
|
28 | .build()); | |
|
|
113 | 29 | } |
|
|
114 | 30 | |
|
|
115 | 31 | } |
| @@ -1,34 +1,32 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import groovy.lang.Closure; |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | import org.gradle.api.Action; |
|
|
7 | 7 | |
|
|
8 | 8 | @NonNullByDefault |
|
|
9 | 9 | public final class Closures { |
|
|
10 | 10 | private Closures() { |
|
|
11 | 11 | } |
|
|
12 | 12 | |
|
|
13 | 13 | /** |
|
|
14 | 14 | * Wraps {@link Action} around the specified closure. The parameter |
|
|
15 | 15 | * of the action will be used as delegate in the specified closure. |
|
|
16 | 16 | * |
|
|
17 | 17 | * @param <T> The type of the action parameter |
|
|
18 | 18 | * @param closure The closure |
|
|
19 | 19 | * @return |
|
|
20 | 20 | */ |
|
|
21 | 21 | public static <T> Action<T> action(Closure<?> closure) { |
|
|
22 | 22 | return arg -> apply(closure, arg); |
|
|
23 | 23 | } |
|
|
24 | 24 | |
|
|
25 | 25 | public static void apply(Closure<?> action, Object target) { |
|
|
26 | var prevDelegate = action.getDelegate(); | |
|
|
27 | try { | |
|
|
28 |
|
|
|
|
29 |
|
|
|
|
30 | } finally { | |
|
|
31 | action.setDelegate(prevDelegate); | |
|
|
32 | } | |
|
|
26 | var c = (Closure<?>)action.clone(); | |
|
|
27 | c.setResolveStrategy(0); | |
|
|
28 | c.setDelegate(target); | |
|
|
29 | c.call(target); | |
|
|
30 | ||
|
|
33 | 31 | } |
|
|
34 | 32 | } |
| @@ -1,32 +1,54 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.regex.Pattern; |
|
|
4 | 4 | |
|
|
5 | 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
6 | 6 | import org.gradle.api.provider.Provider; |
|
|
7 | 7 | |
|
|
8 | 8 | @NonNullByDefault |
|
|
9 | 9 | public class Strings { |
|
|
10 | 10 | |
|
|
11 | 11 | private static final Pattern firstLetter = Pattern.compile("^\\w"); |
|
|
12 | 12 | |
|
|
13 | 13 | public static String capitalize(String string) { |
|
|
14 | 14 | return string == null ? null |
|
|
15 | 15 | : string.length() == 0 ? string |
|
|
16 | 16 | : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); |
|
|
17 | 17 | } |
|
|
18 | 18 | |
|
|
19 | public static String toCamelCase(String name) { | |
|
|
20 | if (name == null || name.isEmpty()) | |
|
|
21 | return name; | |
|
|
22 | StringBuilder out = new StringBuilder(name.length()); | |
|
|
23 | boolean up = false; | |
|
|
24 | boolean first = true; | |
|
|
25 | for (int i = 0; i < name.length(); i++) { | |
|
|
26 | char c = name.charAt(i); | |
|
|
27 | switch (c) { | |
|
|
28 | case '-', '_', ' ', '.' -> up = true; | |
|
|
29 | default -> { | |
|
|
30 | out.append( | |
|
|
31 | first ? Character.toLowerCase(c) | |
|
|
32 | : up ? Character.toUpperCase(c): c); | |
|
|
33 | up = false; | |
|
|
34 | first = false; | |
|
|
35 | } | |
|
|
36 | } | |
|
|
37 | } | |
|
|
38 | return out.toString(); | |
|
|
39 | } | |
|
|
40 | ||
|
|
19 | 41 | public static void argumentNotNullOrEmpty(String value, String argumentName) { |
|
|
20 | 42 | if (value == null || value.length() == 0) |
|
|
21 | 43 | throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName)); |
|
|
22 | 44 | } |
|
|
23 | 45 | |
|
|
24 | 46 | public static String asString(Object value) { |
|
|
25 | 47 | if (value == null) |
|
|
26 | 48 | return null; |
|
|
27 | 49 | if (value instanceof Provider<?> provider) |
|
|
28 | 50 | return asString(provider.get()); |
|
|
29 | 51 | else |
|
|
30 | 52 | return value.toString(); |
|
|
31 | 53 | } |
|
|
32 | 54 | } |
| @@ -1,123 +1,72 | |||
|
|
1 | 1 | package org.implab.gradle.common.utils; |
|
|
2 | 2 | |
|
|
3 | import java.util.HashMap; | |
|
|
4 | 3 | import java.util.Iterator; |
|
|
5 | import java.util.Map; | |
|
|
6 | 4 | import java.util.Spliterators; |
|
|
7 | import java.util.Map.Entry; | |
|
|
8 | 5 | import java.util.Optional; |
|
|
9 | import java.util.function.Function; | |
|
|
10 | 6 | import java.util.function.Supplier; |
|
|
11 | import java.util.stream.Collectors; | |
|
|
12 | 7 | import java.util.stream.Stream; |
|
|
13 | 8 | import java.util.stream.StreamSupport; |
|
|
14 | 9 | |
|
|
15 | import org.gradle.api.Action; | |
|
|
16 | import org.gradle.api.provider.MapProperty; | |
|
|
17 | 10 | import org.gradle.api.provider.Provider; |
|
|
18 | 11 | |
|
|
19 | 12 | public final class Values { |
|
|
20 | 13 | |
|
|
21 | 14 | private Values() { |
|
|
22 | 15 | } |
|
|
23 | 16 | |
|
|
24 | 17 | /** |
|
|
25 | * Converts values in the specified map | |
|
|
26 | * | |
|
|
27 | * @param <K> | |
|
|
28 | * @param <V> | |
|
|
29 | * @param <U> | |
|
|
30 | * @param map | |
|
|
31 | * @param mapper | |
|
|
32 | * @return | |
|
|
33 | */ | |
|
|
34 | public static <K, V, U> Map<K, U> mapValues(Map<K, V> map, Function<V, U> mapper) { | |
|
|
35 | Function<Entry<K, V>, V> getter = Entry::getValue; | |
|
|
36 | ||
|
|
37 | return map.entrySet().stream() | |
|
|
38 | .collect(Collectors.toMap(Entry::getKey, getter.andThen(mapper))); | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | public static <K, V> void mergeMap(MapProperty<K,V> prop, Map<K, ? > map, Class<V> clazz) { | |
|
|
42 | map.forEach((k,v) -> { | |
|
|
43 | if(v instanceof Provider<?>) | |
|
|
44 | prop.put(k, ((Provider<?>)v).map(clazz::cast)); | |
|
|
45 | else | |
|
|
46 | prop.put(k, clazz.cast(v)); | |
|
|
47 | }); | |
|
|
48 | } | |
|
|
49 | ||
|
|
50 | public static <K,V,U> void mergeMap(MapProperty<K,V> prop, Map<K, ?> map, Class<U> clazz, Function<U,V> mapper) { | |
|
|
51 | Function<Object, U> caster = clazz::cast; | |
|
|
52 | var castMap = caster.andThen(mapper); | |
|
|
53 | ||
|
|
54 | map.forEach((k,v) -> { | |
|
|
55 | if(v instanceof Provider<?>) | |
|
|
56 | prop.put(k, ((Provider<?>)v).map(x -> castMap.apply(x))); | |
|
|
57 | else | |
|
|
58 | prop.put(k, castMap.apply(v)); | |
|
|
59 | }); | |
|
|
60 | } | |
|
|
61 | ||
|
|
62 | public static <K,V,U> void configureMap(MapProperty<K,V> prop, Action<Map<K, ?>> configure, Class<U> clazz, Function<U,V> mapper) { | |
|
|
63 | var map = new HashMap<K,V>(); | |
|
|
64 | configure.execute(map); | |
|
|
65 | mergeMap(prop, map, clazz, mapper); | |
|
|
66 | } | |
|
|
67 | ||
|
|
68 | /** | |
|
|
69 | 18 | * Converts the supplied value to a string. |
|
|
70 | 19 | */ |
|
|
71 | 20 | public static String toString(Object value) { |
|
|
72 | 21 | if (value == null) { |
|
|
73 | 22 | return null; |
|
|
74 | 23 | } else if (value instanceof String string) { |
|
|
75 | 24 | return string; |
|
|
76 | 25 | } else if (value instanceof Provider<?> provider) { |
|
|
77 | 26 | return toString(provider.getOrNull()); |
|
|
78 | 27 | } else if (value instanceof Supplier<?> supplier) { |
|
|
79 | 28 | return toString(supplier.get()); |
|
|
80 | 29 | } else { |
|
|
81 | 30 | return value.toString(); |
|
|
82 | 31 | } |
|
|
83 | 32 | } |
|
|
84 | 33 | |
|
|
85 | 34 | public static <T> Stream<T> stream(Iterator<T> remaining) { |
|
|
86 | 35 | return StreamSupport.stream( |
|
|
87 | 36 | Spliterators.spliteratorUnknownSize(remaining, 0), |
|
|
88 | 37 | false); |
|
|
89 | 38 | } |
|
|
90 | 39 | |
|
|
91 | 40 | public static <T> Optional<T> take(Iterator<T> iterator) { |
|
|
92 | 41 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); |
|
|
93 | 42 | } |
|
|
94 | 43 | |
|
|
95 | 44 | public static <T> Iterable<T> iterable(T[] values) { |
|
|
96 | 45 | return () -> new ArrayIterator<>(values); |
|
|
97 | 46 | } |
|
|
98 | 47 | |
|
|
99 | 48 | public static <T> Optional<T> optional(Provider<T> provider) { |
|
|
100 | 49 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); |
|
|
101 | 50 | } |
|
|
102 | 51 | |
|
|
103 | 52 | private static class ArrayIterator<T> implements Iterator<T> { |
|
|
104 | 53 | private final T[] data; |
|
|
105 | 54 | |
|
|
106 | 55 | private int pos = 0; |
|
|
107 | 56 | |
|
|
108 | 57 | ArrayIterator(T[] data) { |
|
|
109 | 58 | this.data = data; |
|
|
110 | 59 | } |
|
|
111 | 60 | |
|
|
112 | 61 | @Override |
|
|
113 | 62 | public boolean hasNext() { |
|
|
114 | 63 | return pos < data.length; |
|
|
115 | 64 | } |
|
|
116 | 65 | |
|
|
117 | 66 | @Override |
|
|
118 | 67 | public T next() { |
|
|
119 | 68 | return data[pos++]; |
|
|
120 | 69 | } |
|
|
121 | 70 | } |
|
|
122 | 71 | |
|
|
123 | 72 | } |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
