| @@ -0,0 +1,60 | |||||
|
|
1 | package org.implab.gradle.common; | |||
|
|
2 | ||||
|
|
3 | import org.gradle.api.Action; | |||
|
|
4 | import org.gradle.api.Plugin; | |||
|
|
5 | import org.gradle.api.Project; | |||
|
|
6 | import org.gradle.api.logging.Logger; | |||
|
|
7 | import org.gradle.api.logging.Logging; | |||
|
|
8 | import org.gradle.api.tasks.Copy; | |||
|
|
9 | import org.gradle.api.tasks.Delete; | |||
|
|
10 | import org.gradle.api.tasks.TaskContainer; | |||
|
|
11 | import org.gradle.api.tasks.TaskProvider; | |||
|
|
12 | import org.gradle.language.base.plugins.LifecycleBasePlugin; | |||
|
|
13 | import org.implab.gradle.common.files.GenericSourceSet; | |||
|
|
14 | import org.implab.gradle.common.utils.Strings; | |||
|
|
15 | ||||
|
|
16 | /** | |||
|
|
17 | * This plugin creates a {@code sources} extension which is | |||
|
|
18 | * a container for {@link GenericSourceSet}. | |||
|
|
19 | * | |||
|
|
20 | */ | |||
|
|
21 | public abstract class SourcesPlugin implements Plugin<Project> { | |||
|
|
22 | private final Logger logger = Logging.getLogger(SourcesPlugin.class); | |||
|
|
23 | ||||
|
|
24 | private final String POLICY_NAME = "SourcesPolicy"; | |||
|
|
25 | ||||
|
|
26 | private final String EXTENSION_NAME = "sources"; | |||
|
|
27 | ||||
|
|
28 | @Override | |||
|
|
29 | public void apply(Project target) { | |||
|
|
30 | target.getPlugins().apply(LifecycleBasePlugin.class); | |||
|
|
31 | ||||
|
|
32 | var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class); | |||
|
|
33 | var tasks = target.getTasks(); | |||
|
|
34 | var clean = tasks.named(LifecycleBasePlugin.CLEAN_TASK_NAME); | |||
|
|
35 | ||||
|
|
36 | target.getExtensions().add(EXTENSION_NAME, sources); | |||
|
|
37 | ||||
|
|
38 | sources.configureEach(configureSourceSet(tasks, clean)); | |||
|
|
39 | } | |||
|
|
40 | ||||
|
|
41 | private Action<GenericSourceSet> configureSourceSet(TaskContainer tasks, TaskProvider<?> clean) { | |||
|
|
42 | return sourceSet -> { | |||
|
|
43 | var name = sourceSet.getName(); | |||
|
|
44 | ||||
|
|
45 | logger.info("{}: Processing source set '{}'", POLICY_NAME, name); | |||
|
|
46 | ||||
|
|
47 | var taskName = "clean" + Strings.capitalize(sourceSet.getName()); | |||
|
|
48 | ||||
|
|
49 | logger.info("{}: Register task '{}' of type [{}]", POLICY_NAME, taskName, Copy.class.getTypeName()); | |||
|
|
50 | ||||
|
|
51 | var task = tasks.register(taskName, Delete.class, self -> { | |||
|
|
52 | self.delete(sourceSet.getOutputsDir()); | |||
|
|
53 | }); | |||
|
|
54 | ||||
|
|
55 | clean.configure(self -> { | |||
|
|
56 | self.dependsOn(task); | |||
|
|
57 | }); | |||
|
|
58 | }; | |||
|
|
59 | } | |||
|
|
60 | } | |||
| @@ -0,0 +1,136 | |||||
|
|
1 | package org.implab.gradle.common.files; | |||
|
|
2 | ||||
|
|
3 | import java.io.File; | |||
|
|
4 | import java.nio.file.Paths; | |||
|
|
5 | import java.util.HashSet; | |||
|
|
6 | import java.util.List; | |||
|
|
7 | import java.util.Objects; | |||
|
|
8 | import java.util.Set; | |||
|
|
9 | import java.util.concurrent.Callable; | |||
|
|
10 | import java.util.stream.Collectors; | |||
|
|
11 | ||||
|
|
12 | import javax.inject.Inject; | |||
|
|
13 | ||||
|
|
14 | import org.gradle.api.Named; | |||
|
|
15 | import org.gradle.api.NamedDomainObjectContainer; | |||
|
|
16 | import org.gradle.api.file.DirectoryProperty; | |||
|
|
17 | import org.gradle.api.file.FileCollection; | |||
|
|
18 | import org.gradle.api.file.ProjectLayout; | |||
|
|
19 | import org.gradle.api.file.SourceDirectorySet; | |||
|
|
20 | import org.gradle.api.logging.Logger; | |||
|
|
21 | import org.gradle.api.logging.Logging; | |||
|
|
22 | import org.gradle.api.model.ObjectFactory; | |||
|
|
23 | import org.gradle.util.Configurable; | |||
|
|
24 | import org.implab.gradle.common.utils.Closures; | |||
|
|
25 | ||||
|
|
26 | import groovy.lang.Closure; | |||
|
|
27 | ||||
|
|
28 | public abstract class GenericSourceSet | |||
|
|
29 | implements Named, Configurable<GenericSourceSet> { | |||
|
|
30 | private final Logger logger = Logging.getLogger(GenericSourceSet.class); | |||
|
|
31 | ||||
|
|
32 | private final String name; | |||
|
|
33 | ||||
|
|
34 | private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets; | |||
|
|
35 | ||||
|
|
36 | private final NamedDomainObjectContainer<GenericSourceSetOutput> outputs; | |||
|
|
37 | ||||
|
|
38 | private final FileCollection allOutputs; | |||
|
|
39 | ||||
|
|
40 | private final FileCollection allSourceDirectories; | |||
|
|
41 | ||||
|
|
42 | private final ObjectFactory objects; | |||
|
|
43 | ||||
|
|
44 | private final Set<String> declaredOutputs = new HashSet<>(); | |||
|
|
45 | ||||
|
|
46 | @Inject | |||
|
|
47 | public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) { | |||
|
|
48 | this.name = name; | |||
|
|
49 | this.objects = objects; | |||
|
|
50 | ||||
|
|
51 | sourceDirectorySets = objects.domainObjectContainer( | |||
|
|
52 | SourceDirectorySet.class, | |||
|
|
53 | this::createSourceDirectorySet); | |||
|
|
54 | ||||
|
|
55 | outputs = objects.domainObjectContainer(GenericSourceSetOutput.class); | |||
|
|
56 | ||||
|
|
57 | allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider()); | |||
|
|
58 | ||||
|
|
59 | allOutputs = objects.fileCollection().from(outputsProvider()); | |||
|
|
60 | ||||
|
|
61 | outputs.addRule("Register a declared set on demand", this::registerOutputOnDemand); | |||
|
|
62 | ||||
|
|
63 | getSourceSetDir().convention(layout | |||
|
|
64 | .getProjectDirectory() | |||
|
|
65 | .dir(Paths.get("src", name).toString())); | |||
|
|
66 | ||||
|
|
67 | getOutputsDir().convention(layout | |||
|
|
68 | .getBuildDirectory() | |||
|
|
69 | .dir(name)); | |||
|
|
70 | } | |||
|
|
71 | ||||
|
|
72 | @Override | |||
|
|
73 | public String getName() { | |||
|
|
74 | return name; | |||
|
|
75 | } | |||
|
|
76 | ||||
|
|
77 | public abstract DirectoryProperty getSourceSetDir(); | |||
|
|
78 | ||||
|
|
79 | public abstract DirectoryProperty getOutputsDir(); | |||
|
|
80 | ||||
|
|
81 | public NamedDomainObjectContainer<SourceDirectorySet> getSets() { | |||
|
|
82 | return sourceDirectorySets; | |||
|
|
83 | } | |||
|
|
84 | ||||
|
|
85 | public NamedDomainObjectContainer<GenericSourceSetOutput> getOutputs() { | |||
|
|
86 | return outputs; | |||
|
|
87 | } | |||
|
|
88 | ||||
|
|
89 | public FileCollection getAllOutputs() { | |||
|
|
90 | return allOutputs; | |||
|
|
91 | } | |||
|
|
92 | ||||
|
|
93 | public FileCollection getAllSourceDirectories() { | |||
|
|
94 | return allSourceDirectories; | |||
|
|
95 | } | |||
|
|
96 | ||||
|
|
97 | public FileCollection output(String name) { | |||
|
|
98 | return getOutputs().getAt(name).getFileCollection(); | |||
|
|
99 | } | |||
|
|
100 | ||||
|
|
101 | public void declareOutputs(String name, String... extra) { | |||
|
|
102 | declaredOutputs.add(Objects.requireNonNull(name, "declareOutputs: The output name cannot be null")); | |||
|
|
103 | for (var x : extra) | |||
|
|
104 | declaredOutputs.add(Objects.requireNonNull(x, "declareOutputs: The output name cannot be null")); | |||
|
|
105 | } | |||
|
|
106 | ||||
|
|
107 | @Override | |||
|
|
108 | public GenericSourceSet configure(@SuppressWarnings("rawtypes") Closure configure) { | |||
|
|
109 | Closures.apply(configure, this); | |||
|
|
110 | return this; | |||
|
|
111 | } | |||
|
|
112 | ||||
|
|
113 | private void registerOutputOnDemand(String outputName) { | |||
|
|
114 | logger.info("SourceSet '{}': registerOutputOnDemand '{}'", name, outputName); | |||
|
|
115 | ||||
|
|
116 | if (declaredOutputs.contains(outputName)) | |||
|
|
117 | outputs.register(outputName); | |||
|
|
118 | } | |||
|
|
119 | ||||
|
|
120 | private SourceDirectorySet createSourceDirectorySet(String name) { | |||
|
|
121 | return objects.sourceDirectorySet(name, name); | |||
|
|
122 | } | |||
|
|
123 | ||||
|
|
124 | private Callable<List<? extends FileCollection>> outputsProvider() { | |||
|
|
125 | return () -> outputs.stream() | |||
|
|
126 | .map(GenericSourceSetOutput::getFileCollection) | |||
|
|
127 | .toList(); | |||
|
|
128 | } | |||
|
|
129 | ||||
|
|
130 | private Callable<Set<File>> sourceDirectoriesProvider() { | |||
|
|
131 | return () -> sourceDirectorySets.stream() | |||
|
|
132 | .flatMap(x -> x.getSrcDirs().stream()) | |||
|
|
133 | .collect(Collectors.toSet()); | |||
|
|
134 | } | |||
|
|
135 | ||||
|
|
136 | } | |||
| @@ -0,0 +1,46 | |||||
|
|
1 | package org.implab.gradle.common.files; | |||
|
|
2 | ||||
|
|
3 | import org.gradle.api.Action; | |||
|
|
4 | import org.gradle.api.Named; | |||
|
|
5 | import org.gradle.api.file.ConfigurableFileCollection; | |||
|
|
6 | import org.gradle.api.file.FileCollection; | |||
|
|
7 | import org.gradle.api.model.ObjectFactory; | |||
|
|
8 | import org.gradle.util.Configurable; | |||
|
|
9 | import org.implab.gradle.common.utils.Closures; | |||
|
|
10 | ||||
|
|
11 | import groovy.lang.Closure; | |||
|
|
12 | ||||
|
|
13 | /** Simple wrapper to add {@link Named} to {@link FileCollection} */ | |||
|
|
14 | public abstract class GenericSourceSetOutput | |||
|
|
15 | implements Named, Configurable<GenericSourceSetOutput> { | |||
|
|
16 | ||||
|
|
17 | private final String name; | |||
|
|
18 | ||||
|
|
19 | private final ConfigurableFileCollection outputFileCollection; | |||
|
|
20 | ||||
|
|
21 | public GenericSourceSetOutput(String name, ObjectFactory objects) { | |||
|
|
22 | this.name = name; | |||
|
|
23 | outputFileCollection = objects.fileCollection(); | |||
|
|
24 | } | |||
|
|
25 | ||||
|
|
26 | public FileCollection getFileCollection() { | |||
|
|
27 | return outputFileCollection; | |||
|
|
28 | } | |||
|
|
29 | ||||
|
|
30 | @Override | |||
|
|
31 | public String getName() { | |||
|
|
32 | return name; | |||
|
|
33 | } | |||
|
|
34 | ||||
|
|
35 | @Override | |||
|
|
36 | public GenericSourceSetOutput configure(@SuppressWarnings("rawtypes") Closure cl) { | |||
|
|
37 | Closures.apply(cl, outputFileCollection); | |||
|
|
38 | return this; | |||
|
|
39 | } | |||
|
|
40 | ||||
|
|
41 | public GenericSourceSetOutput configure(Action<ConfigurableFileCollection> cl) { | |||
|
|
42 | cl.execute(outputFileCollection); | |||
|
|
43 | return this; | |||
|
|
44 | } | |||
|
|
45 | ||||
|
|
46 | } | |||
| @@ -0,0 +1,29 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | ||||
|
|
4 | import org.gradle.api.provider.Provider; | |||
|
|
5 | ||||
|
|
6 | import java.util.ArrayList; | |||
|
|
7 | import java.util.Collections; | |||
|
|
8 | import java.util.List; | |||
|
|
9 | ||||
|
|
10 | ||||
|
|
11 | public class DefaultJsonArraySpec implements JsonArraySpec { | |||
|
|
12 | ||||
|
|
13 | private final List<Object> values = new ArrayList<>(); | |||
|
|
14 | ||||
|
|
15 | @Override | |||
|
|
16 | public void add(Object value) { | |||
|
|
17 | if (value instanceof Provider<?>) { | |||
|
|
18 | throw new IllegalArgumentException( | |||
|
|
19 | "Providers are not allowed inside JSON arrays; " + | |||
|
|
20 | "use top-level metadata.set(\"key\", provider) instead." | |||
|
|
21 | ); | |||
|
|
22 | } | |||
|
|
23 | values.add(value); | |||
|
|
24 | } | |||
|
|
25 | ||||
|
|
26 | public List<Object> toList() { | |||
|
|
27 | return Collections.unmodifiableList(values); | |||
|
|
28 | } | |||
|
|
29 | } No newline at end of file | |||
| @@ -0,0 +1,26 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import org.gradle.api.provider.Provider; | |||
|
|
4 | ||||
|
|
5 | import java.util.Collections; | |||
|
|
6 | import java.util.LinkedHashMap; | |||
|
|
7 | import java.util.Map; | |||
|
|
8 | ||||
|
|
9 | public class DefaultJsonObjectSpec implements GroovyObjectSpec { | |||
|
|
10 | ||||
|
|
11 | private final Map<String, Object> values = new LinkedHashMap<>(); | |||
|
|
12 | ||||
|
|
13 | @Override | |||
|
|
14 | public void set(String key, Object value) { | |||
|
|
15 | if (value instanceof Provider<?>) { | |||
|
|
16 | throw new IllegalArgumentException( | |||
|
|
17 | "Providers are not allowed inside nested JSON objects; " + | |||
|
|
18 | "use top-level metadata.set(\"" + key + "\", provider) instead."); | |||
|
|
19 | } | |||
|
|
20 | values.put(key, value); | |||
|
|
21 | } | |||
|
|
22 | ||||
|
|
23 | public Map<String, Object> toMap() { | |||
|
|
24 | return Collections.unmodifiableMap(values); | |||
|
|
25 | } | |||
|
|
26 | } No newline at end of file | |||
| @@ -0,0 +1,38 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import java.util.Arrays; | |||
|
|
4 | ||||
|
|
5 | import groovy.lang.Closure; | |||
|
|
6 | import groovy.lang.MissingMethodException; | |||
|
|
7 | ||||
|
|
8 | public interface GroovyObjectSpec extends JsonObjectSpec { | |||
|
|
9 | ||||
|
|
10 | default void propertyMissing(String name, Object value) { | |||
|
|
11 | set(name, value); | |||
|
|
12 | } | |||
|
|
13 | ||||
|
|
14 | default Object methodMissing(String name, Object args) { | |||
|
|
15 | Object[] arr = (Object[]) args; | |||
|
|
16 | ||||
|
|
17 | // author { ... }, repository { ... } | |||
|
|
18 | if (arr.length == 1 && arr[0] instanceof Closure<?>) { | |||
|
|
19 | DefaultJsonObjectSpec spec = new DefaultJsonObjectSpec(); | |||
|
|
20 | Closure<?> cl = (Closure<?>) arr[0]; | |||
|
|
21 | cl.setDelegate(spec); | |||
|
|
22 | cl.setResolveStrategy(Closure.DELEGATE_FIRST); | |||
|
|
23 | cl.call(); | |||
|
|
24 | set(name, spec.toMap()); | |||
|
|
25 | return null; | |||
|
|
26 | } else { | |||
|
|
27 | boolean hasClosure = Arrays.stream(arr) | |||
|
|
28 | .anyMatch(a -> a instanceof Closure<?>); | |||
|
|
29 | ||||
|
|
30 | if (!hasClosure) { | |||
|
|
31 | set(name, Arrays.asList(arr)); | |||
|
|
32 | return null; | |||
|
|
33 | } | |||
|
|
34 | } | |||
|
|
35 | ||||
|
|
36 | throw new MissingMethodException(name, getClass(), arr); | |||
|
|
37 | } | |||
|
|
38 | } | |||
| @@ -0,0 +1,20 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import org.gradle.api.Action; | |||
|
|
4 | ||||
|
|
5 | public interface JsonArraySpec { | |||
|
|
6 | ||||
|
|
7 | void add(Object value); | |||
|
|
8 | ||||
|
|
9 | default void obj(Action<? super JsonObjectSpec> action) { | |||
|
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |||
|
|
11 | action.execute(child); | |||
|
|
12 | add(child.toMap()); | |||
|
|
13 | } | |||
|
|
14 | ||||
|
|
15 | default void arr(Action<? super JsonArraySpec> action) { | |||
|
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |||
|
|
17 | action.execute(child); | |||
|
|
18 | add(child.toList()); | |||
|
|
19 | } | |||
|
|
20 | } No newline at end of file | |||
| @@ -0,0 +1,23 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import java.io.File; | |||
|
|
4 | ||||
|
|
5 | import org.gradle.api.Action; | |||
|
|
6 | import org.gradle.api.file.RegularFile; | |||
|
|
7 | import org.gradle.api.provider.Provider; | |||
|
|
8 | ||||
|
|
9 | public interface JsonMapSpec extends GroovyObjectSpec { | |||
|
|
10 | void from(File file, Action<? super MapImportSpec> action); | |||
|
|
11 | ||||
|
|
12 | default void from(File file) { | |||
|
|
13 | from(file, spec -> { | |||
|
|
14 | }); | |||
|
|
15 | } | |||
|
|
16 | ||||
|
|
17 | void from(Provider<RegularFile> fileProvider, Action<? super MapImportSpec> action); | |||
|
|
18 | ||||
|
|
19 | default void from(Provider<RegularFile> fileProvider) { | |||
|
|
20 | from(fileProvider, spec -> { | |||
|
|
21 | }); | |||
|
|
22 | } | |||
|
|
23 | } | |||
| @@ -0,0 +1,20 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import org.gradle.api.Action; | |||
|
|
4 | ||||
|
|
5 | public interface JsonObjectSpec { | |||
|
|
6 | ||||
|
|
7 | void set(String key, Object value); | |||
|
|
8 | ||||
|
|
9 | default void obj(String key, Action<? super JsonObjectSpec> action) { | |||
|
|
10 | DefaultJsonObjectSpec child = new DefaultJsonObjectSpec(); | |||
|
|
11 | action.execute(child); | |||
|
|
12 | set(key, child.toMap()); | |||
|
|
13 | } | |||
|
|
14 | ||||
|
|
15 | default void arr(String key, Action<? super JsonArraySpec> action) { | |||
|
|
16 | DefaultJsonArraySpec child = new DefaultJsonArraySpec(); | |||
|
|
17 | action.execute(child); | |||
|
|
18 | set(key, child.toList()); | |||
|
|
19 | } | |||
|
|
20 | } No newline at end of file | |||
| @@ -0,0 +1,66 | |||||
|
|
1 | package org.implab.gradle.common.json; | |||
|
|
2 | ||||
|
|
3 | import java.util.Arrays; | |||
|
|
4 | import java.util.LinkedHashSet; | |||
|
|
5 | import java.util.Set; | |||
|
|
6 | import java.util.function.Predicate; | |||
|
|
7 | ||||
|
|
8 | import org.gradle.api.Action; | |||
|
|
9 | import org.implab.gradle.common.utils.Closures; | |||
|
|
10 | ||||
|
|
11 | import groovy.lang.Closure; | |||
|
|
12 | ||||
|
|
13 | /** | |||
|
|
14 | * Простая include/exclude-маска по ключам верхнего уровня. | |||
|
|
15 | */ | |||
|
|
16 | public class MapImportSpec { | |||
|
|
17 | ||||
|
|
18 | private final Set<String> includes = new LinkedHashSet<>(); | |||
|
|
19 | private final Set<String> excludes = new LinkedHashSet<>(); | |||
|
|
20 | ||||
|
|
21 | public void include(String... keys) { | |||
|
|
22 | includes.addAll(Arrays.asList(keys)); | |||
|
|
23 | } | |||
|
|
24 | ||||
|
|
25 | public void exclude(String... keys) { | |||
|
|
26 | excludes.addAll(Arrays.asList(keys)); | |||
|
|
27 | } | |||
|
|
28 | ||||
|
|
29 | public Set<String> getIncludes() { | |||
|
|
30 | return includes; | |||
|
|
31 | } | |||
|
|
32 | ||||
|
|
33 | public Set<String> getExcludes() { | |||
|
|
34 | return excludes; | |||
|
|
35 | } | |||
|
|
36 | ||||
|
|
37 | public boolean hasIncludes() { | |||
|
|
38 | return !includes.isEmpty(); | |||
|
|
39 | } | |||
|
|
40 | ||||
|
|
41 | public boolean isExcluded(String key) { | |||
|
|
42 | return excludes.contains(key); | |||
|
|
43 | } | |||
|
|
44 | ||||
|
|
45 | public boolean isIncluded(String key) { | |||
|
|
46 | return !hasIncludes() || includes.contains(key); | |||
|
|
47 | } | |||
|
|
48 | ||||
|
|
49 | public boolean matches(String key) { | |||
|
|
50 | return isIncluded(key) && !isExcluded(key); | |||
|
|
51 | } | |||
|
|
52 | ||||
|
|
53 | public Predicate<String> toPredicate() { | |||
|
|
54 | return this::matches; | |||
|
|
55 | } | |||
|
|
56 | ||||
|
|
57 | public static Predicate<String> buildPredicate(Action<? super MapImportSpec> action) { | |||
|
|
58 | var spec = new MapImportSpec(); | |||
|
|
59 | action.execute(spec); | |||
|
|
60 | return spec.toPredicate(); | |||
|
|
61 | } | |||
|
|
62 | ||||
|
|
63 | public static Predicate<String> buildPredicate(Closure<?> closure) { | |||
|
|
64 | return buildPredicate(Closures.action(closure)); | |||
|
|
65 | } | |||
|
|
66 | } No newline at end of file | |||
| @@ -0,0 +1,181 | |||||
|
|
1 | package org.implab.gradle.common.tasks; | |||
|
|
2 | ||||
|
|
3 | import java.util.LinkedHashMap; | |||
|
|
4 | import java.util.Map; | |||
|
|
5 | import java.util.Objects; | |||
|
|
6 | import java.util.function.Predicate; | |||
|
|
7 | import java.util.stream.Collectors; | |||
|
|
8 | ||||
|
|
9 | import javax.inject.Inject; | |||
|
|
10 | ||||
|
|
11 | import org.eclipse.jdt.annotation.NonNullByDefault; | |||
|
|
12 | import org.gradle.api.Action; | |||
|
|
13 | import org.gradle.api.DefaultTask; | |||
|
|
14 | import org.gradle.api.file.RegularFile; | |||
|
|
15 | import org.gradle.api.file.RegularFileProperty; | |||
|
|
16 | import org.gradle.api.provider.MapProperty; | |||
|
|
17 | import org.gradle.api.provider.Provider; | |||
|
|
18 | import org.gradle.api.provider.ProviderFactory; | |||
|
|
19 | import org.gradle.api.tasks.Input; | |||
|
|
20 | import org.gradle.api.tasks.Internal; | |||
|
|
21 | import org.gradle.api.tasks.Optional; | |||
|
|
22 | import org.gradle.api.tasks.OutputFile; | |||
|
|
23 | import org.gradle.api.tasks.SkipWhenEmpty; | |||
|
|
24 | import org.gradle.api.tasks.TaskAction; | |||
|
|
25 | import org.implab.gradle.common.json.MapImportSpec; | |||
|
|
26 | import org.implab.gradle.common.json.Json; | |||
|
|
27 | import org.implab.gradle.common.json.JsonObjectSpec; | |||
|
|
28 | import org.implab.gradle.common.utils.Closures; | |||
|
|
29 | import org.implab.gradle.common.utils.Properties; | |||
|
|
30 | ||||
|
|
31 | import groovy.lang.Closure; | |||
|
|
32 | ||||
|
|
33 | /** | |||
|
|
34 | * A Gradle task that writes JSON content to a file. | |||
|
|
35 | * | |||
|
|
36 | * This task allows you to build up a JSON object through various methods and | |||
|
|
37 | * write it to a file. | |||
|
|
38 | * You can add content directly, import from other JSON files, or merge in map | |||
|
|
39 | * data with optional filtering. | |||
|
|
40 | * | |||
|
|
41 | * <h3>Usage Example:</h3> | |||
|
|
42 | * | |||
|
|
43 | * <pre> | |||
|
|
44 | * tasks.register('generateConfig', WriteJson) { | |||
|
|
45 | * outputFile = file('build/config.json') | |||
|
|
46 | * content { | |||
|
|
47 | * version = '1.0.0' | |||
|
|
48 | * name = 'myapp' | |||
|
|
49 | * } | |||
|
|
50 | * from file('src/base-config.json') | |||
|
|
51 | * putAll providers.provider { ['debug': true] } | |||
|
|
52 | * } | |||
|
|
53 | * </pre> | |||
|
|
54 | * | |||
|
|
55 | * <h3>Properties:</h3> | |||
|
|
56 | * <ul> | |||
|
|
57 | * <li><strong>content</strong> - A map of string keys to object values that | |||
|
|
58 | * will be serialized to JSON</li> | |||
|
|
59 | * <li><strong>outputFile</strong> - The file where the JSON will be | |||
|
|
60 | * written</li> | |||
|
|
61 | * </ul> | |||
|
|
62 | * | |||
|
|
63 | * <h3>Methods:</h3> | |||
|
|
64 | * <ul> | |||
|
|
65 | * <li><strong>content()</strong> - Configure content using a closure or | |||
|
|
66 | * action</li> | |||
|
|
67 | * <li><strong>from()</strong> - Import JSON content from a file with optional | |||
|
|
68 | * key filtering</li> | |||
|
|
69 | * <li><strong>putAll()</strong> - Merge a map provider with optional key | |||
|
|
70 | * filtering</li> | |||
|
|
71 | * </ul> | |||
|
|
72 | * | |||
|
|
73 | * @since 1.0 | |||
|
|
74 | */ | |||
|
|
75 | @NonNullByDefault | |||
|
|
76 | public abstract class WriteJson extends DefaultTask { | |||
|
|
77 | ||||
|
|
78 | private final ProviderFactory providers; | |||
|
|
79 | ||||
|
|
80 | private final JsonObjectSpec contentSpec = new JsonObjectSpec() { | |||
|
|
81 | @Override | |||
|
|
82 | public void set(String key, Object value) { | |||
|
|
83 | Properties.putMapEntry(getContent(), key, value); | |||
|
|
84 | } | |||
|
|
85 | }; | |||
|
|
86 | ||||
|
|
87 | @Input | |||
|
|
88 | @Optional | |||
|
|
89 | public abstract MapProperty<String, Object> getContent(); | |||
|
|
90 | ||||
|
|
91 | @OutputFile | |||
|
|
92 | public abstract RegularFileProperty getOutputFile(); | |||
|
|
93 | ||||
|
|
94 | @Internal | |||
|
|
95 | public Provider<String> getJson() { | |||
|
|
96 | return getContent().map(Json::stringify); | |||
|
|
97 | } | |||
|
|
98 | ||||
|
|
99 | @Inject | |||
|
|
100 | public WriteJson(ProviderFactory providers) { | |||
|
|
101 | this.providers = providers; | |||
|
|
102 | } | |||
|
|
103 | ||||
|
|
104 | public void content(Action<? super JsonObjectSpec> configure) { | |||
|
|
105 | configure.execute(contentSpec); | |||
|
|
106 | } | |||
|
|
107 | ||||
|
|
108 | public void content(Closure<?> configure) { | |||
|
|
109 | Closures.apply(configure, contentSpec); | |||
|
|
110 | } | |||
|
|
111 | ||||
|
|
112 | public void from(RegularFile file, Closure<?> configure) { | |||
|
|
113 | importContents(file, MapImportSpec.buildPredicate(configure)); | |||
|
|
114 | } | |||
|
|
115 | ||||
|
|
116 | public void from(RegularFile file, Action<? super MapImportSpec> configure) { | |||
|
|
117 | importContents(file, MapImportSpec.buildPredicate(configure)); | |||
|
|
118 | } | |||
|
|
119 | ||||
|
|
120 | public void from(RegularFile file) { | |||
|
|
121 | importContents(file, x -> true); | |||
|
|
122 | } | |||
|
|
123 | ||||
|
|
124 | public void from(Provider<RegularFile> file, Closure<?> configure) { | |||
|
|
125 | importContents(file, MapImportSpec.buildPredicate(configure)); | |||
|
|
126 | } | |||
|
|
127 | ||||
|
|
128 | public void from(Provider<RegularFile> file, Action<? super MapImportSpec> configure) { | |||
|
|
129 | importContents(file, MapImportSpec.buildPredicate(configure)); | |||
|
|
130 | } | |||
|
|
131 | ||||
|
|
132 | public void from(Provider<RegularFile> file) { | |||
|
|
133 | importContents(file, x -> true); | |||
|
|
134 | } | |||
|
|
135 | ||||
|
|
136 | public void putAll(Provider<Map<String, Object>> mapProvider, Action<? super MapImportSpec> configure) { | |||
|
|
137 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |||
|
|
138 | } | |||
|
|
139 | ||||
|
|
140 | public void putAll(Provider<Map<String, Object>> mapProvider, Closure<?> configure) { | |||
|
|
141 | importMap(mapProvider, MapImportSpec.buildPredicate(configure)); | |||
|
|
142 | } | |||
|
|
143 | ||||
|
|
144 | public void putAll(Provider<Map<String, Object>> mapProvider) { | |||
|
|
145 | importMap(mapProvider, x -> true); | |||
|
|
146 | } | |||
|
|
147 | ||||
|
|
148 | private void importContents(Provider<RegularFile> file, Predicate<String> keyPredicate) { | |||
|
|
149 | var jsonProvider = providers.fileContents(file).getAsText() | |||
|
|
150 | .map(Json.mapParser(Object.class)::apply); | |||
|
|
151 | ||||
|
|
152 | importMap(jsonProvider, keyPredicate); | |||
|
|
153 | } | |||
|
|
154 | ||||
|
|
155 | private void importContents(RegularFile file, Predicate<String> keyPredicate) { | |||
|
|
156 | var jsonProvider = providers.fileContents(file).getAsText() | |||
|
|
157 | .map(Json.mapParser(Object.class)::apply); | |||
|
|
158 | ||||
|
|
159 | importMap(jsonProvider, keyPredicate); | |||
|
|
160 | } | |||
|
|
161 | ||||
|
|
162 | private void importMap(Provider<Map<String, Object>> mapProvider, Predicate<? super String> keyPredicate) { | |||
|
|
163 | var filteredProvider = mapProvider.map(v -> v.entrySet().stream() | |||
|
|
164 | .filter(pair -> keyPredicate.test(pair.getKey()) && Objects.nonNull(pair.getValue())) | |||
|
|
165 | .collect(Collectors.toMap( | |||
|
|
166 | Map.Entry::getKey, | |||
|
|
167 | Map.Entry::getValue, | |||
|
|
168 | (a, b) -> b, | |||
|
|
169 | LinkedHashMap::new))); | |||
|
|
170 | ||||
|
|
171 | getContent().putAll(filteredProvider); | |||
|
|
172 | } | |||
|
|
173 | ||||
|
|
174 | @TaskAction | |||
|
|
175 | public void run() { | |||
|
|
176 | var data = getContent().getOrElse(Map.of()); | |||
|
|
177 | var file = getOutputFile().getAsFile().get(); | |||
|
|
178 | Json.write(file, data); | |||
|
|
179 | } | |||
|
|
180 | ||||
|
|
181 | } | |||
| @@ -0,0 +1,15 | |||||
|
|
1 | package org.implab.gradle.common.utils; | |||
|
|
2 | ||||
|
|
3 | import java.util.function.Consumer; | |||
|
|
4 | ||||
|
|
5 | import org.gradle.api.plugins.ExtensionContainer; | |||
|
|
6 | ||||
|
|
7 | public final class Extensions { | |||
|
|
8 | private Extensions() { | |||
|
|
9 | } | |||
|
|
10 | ||||
|
|
11 | public static Consumer<Class<?>> registerClass(ExtensionContainer extensions) { | |||
|
|
12 | var extra = extensions.getExtraProperties(); | |||
|
|
13 | return clazz -> extra.set(clazz.getSimpleName(), clazz); | |||
|
|
14 | } | |||
|
|
15 | } | |||
| @@ -0,0 +1,316 | |||||
|
|
1 | package org.implab.gradle.common.utils; | |||
|
|
2 | ||||
|
|
3 | import com.fasterxml.jackson.annotation.JsonCreator; | |||
|
|
4 | import com.fasterxml.jackson.annotation.JsonValue; | |||
|
|
5 | ||||
|
|
6 | import java.util.Objects; | |||
|
|
7 | import java.util.Optional; | |||
|
|
8 | import java.util.regex.Matcher; | |||
|
|
9 | import java.util.regex.Pattern; | |||
|
|
10 | ||||
|
|
11 | /** | |||
|
|
12 | * Immutable Semantic Version (SemVer 2.0.0) of the form: | |||
|
|
13 | * | |||
|
|
14 | * <pre> | |||
|
|
15 | * MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] | |||
|
|
16 | * </pre> | |||
|
|
17 | * | |||
|
|
18 | * Ordering (as defined by {@link #compareTo(SemVersion)}) follows | |||
|
|
19 | * the SemVer 2.0.0 precedence rules: | |||
|
|
20 | * <ul> | |||
|
|
21 | * <li>Compare {@code major}, then {@code minor}, then {@code patch}.</li> | |||
|
|
22 | * <li>Pre-release versions have lower precedence than the corresponding | |||
|
|
23 | * normal version.</li> | |||
|
|
24 | * <li>Build metadata does not affect precedence.</li> | |||
|
|
25 | * </ul> | |||
|
|
26 | * | |||
|
|
27 | * Public API does not use {@code null} for semantic values: | |||
|
|
28 | * {@link Optional} is used for pre-release and build metadata. | |||
|
|
29 | */ | |||
|
|
30 | public record SemVersion( | |||
|
|
31 | int major, | |||
|
|
32 | int minor, | |||
|
|
33 | int patch, | |||
|
|
34 | Optional<String> preRelease, | |||
|
|
35 | Optional<String> buildMetadata) implements Comparable<SemVersion> { | |||
|
|
36 | ||||
|
|
37 | // Pattern close to the official SemVer 2.0.0 recommendation. | |||
|
|
38 | // Groups: | |||
|
|
39 | // 1 - major | |||
|
|
40 | // 2 - minor | |||
|
|
41 | // 3 - patch | |||
|
|
42 | // 4 - pre-release (without '-') | |||
|
|
43 | // 5 - build metadata (without '+') | |||
|
|
44 | private static final Pattern SEMVER_PATTERN = Pattern.compile( | |||
|
|
45 | "^(0|[1-9]\\d*)" + // major | |||
|
|
46 | "\\.(0|[1-9]\\d*)" + // minor | |||
|
|
47 | "\\.(0|[1-9]\\d*)" + // patch | |||
|
|
48 | "(?:-((?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*)" + | |||
|
|
49 | "(?:\\.(?:0|[1-9]\\d*|[0-9A-Za-z-][0-9A-Za-z-]*))*" + | |||
|
|
50 | "))?" + // pre-release | |||
|
|
51 | "(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" // build metadata | |||
|
|
52 | ); | |||
|
|
53 | ||||
|
|
54 | /** | |||
|
|
55 | * Compact constructor with basic invariants. | |||
|
|
56 | */ | |||
|
|
57 | public SemVersion { | |||
|
|
58 | if (major < 0 || minor < 0 || patch < 0) { | |||
|
|
59 | throw new IllegalArgumentException("Version numbers must be >= 0"); | |||
|
|
60 | } | |||
|
|
61 | // Be tolerant internally, but never expose null Optionals. | |||
|
|
62 | preRelease = (preRelease != null) ? preRelease : Optional.empty(); | |||
|
|
63 | buildMetadata = (buildMetadata != null) ? buildMetadata : Optional.empty(); | |||
|
|
64 | } | |||
|
|
65 | ||||
|
|
66 | /** | |||
|
|
67 | * Creates a version without pre-release and build metadata. | |||
|
|
68 | * | |||
|
|
69 | * @param major non-negative MAJOR number | |||
|
|
70 | * @param minor non-negative MINOR number | |||
|
|
71 | * @param patch non-negative PATCH number | |||
|
|
72 | */ | |||
|
|
73 | public static SemVersion of(int major, int minor, int patch) { | |||
|
|
74 | return new SemVersion(major, minor, patch, Optional.empty(), Optional.empty()); | |||
|
|
75 | } | |||
|
|
76 | ||||
|
|
77 | /** | |||
|
|
78 | * Creates a version from components. | |||
|
|
79 | * | |||
|
|
80 | * @param major non-negative MAJOR number | |||
|
|
81 | * @param minor non-negative MINOR number | |||
|
|
82 | * @param patch non-negative PATCH number | |||
|
|
83 | * @param preRelease optional pre-release part (without '-') | |||
|
|
84 | * @param buildMetadata optional build metadata (without '+') | |||
|
|
85 | */ | |||
|
|
86 | public static SemVersion of( | |||
|
|
87 | int major, | |||
|
|
88 | int minor, | |||
|
|
89 | int patch, | |||
|
|
90 | Optional<String> preRelease, | |||
|
|
91 | Optional<String> buildMetadata) { | |||
|
|
92 | return new SemVersion( | |||
|
|
93 | major, | |||
|
|
94 | minor, | |||
|
|
95 | patch, | |||
|
|
96 | Objects.requireNonNull(preRelease, "preRelease"), | |||
|
|
97 | Objects.requireNonNull(buildMetadata, "buildMetadata")); | |||
|
|
98 | } | |||
|
|
99 | ||||
|
|
100 | /** | |||
|
|
101 | * Parses a SemVer string of the form | |||
|
|
102 | * {@code MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]}. | |||
|
|
103 | * | |||
|
|
104 | * @param value the string to parse (must not be {@code null}) | |||
|
|
105 | * @return a new {@code SemVersion} | |||
|
|
106 | * @throws IllegalArgumentException if the string is not a valid SemVer | |||
|
|
107 | */ | |||
|
|
108 | public static SemVersion parse(String value) { | |||
|
|
109 | Objects.requireNonNull(value, "value"); | |||
|
|
110 | ||||
|
|
111 | Matcher m = SEMVER_PATTERN.matcher(value); | |||
|
|
112 | if (!m.matches()) { | |||
|
|
113 | throw new IllegalArgumentException("Invalid SemVer: " + value); | |||
|
|
114 | } | |||
|
|
115 | ||||
|
|
116 | int major = Integer.parseInt(m.group(1)); | |||
|
|
117 | int minor = Integer.parseInt(m.group(2)); | |||
|
|
118 | int patch = Integer.parseInt(m.group(3)); | |||
|
|
119 | ||||
|
|
120 | String pre = m.group(4); | |||
|
|
121 | String meta = m.group(5); | |||
|
|
122 | ||||
|
|
123 | Optional<String> preOpt = Optional.ofNullable(pre); | |||
|
|
124 | Optional<String> metaOpt = Optional.ofNullable(meta); | |||
|
|
125 | ||||
|
|
126 | // Extra validation for numeric pre-release identifiers (no leading zeros) | |||
|
|
127 | preOpt.ifPresent(SemVersion::validatePreReleaseIdentifiers); | |||
|
|
128 | ||||
|
|
129 | return new SemVersion(major, minor, patch, preOpt, metaOpt); | |||
|
|
130 | } | |||
|
|
131 | ||||
|
|
132 | private static void validatePreReleaseIdentifiers(String preRelease) { | |||
|
|
133 | String[] parts = preRelease.split("\\."); | |||
|
|
134 | for (String p : parts) { | |||
|
|
135 | if (isNumericIdentifier(p)) { | |||
|
|
136 | // numeric identifiers must not have leading zeros (except "0") | |||
|
|
137 | if (p.length() > 1 && p.charAt(0) == '0') { | |||
|
|
138 | throw new IllegalArgumentException( | |||
|
|
139 | "Numeric pre-release identifier must not contain leading zeros: " + p); | |||
|
|
140 | } | |||
|
|
141 | } | |||
|
|
142 | } | |||
|
|
143 | } | |||
|
|
144 | ||||
|
|
145 | private static boolean isNumericIdentifier(String s) { | |||
|
|
146 | int len = s.length(); | |||
|
|
147 | if (len == 0) { | |||
|
|
148 | return false; | |||
|
|
149 | } | |||
|
|
150 | for (int i = 0; i < len; i++) { | |||
|
|
151 | char c = s.charAt(i); | |||
|
|
152 | if (c < '0' || c > '9') { | |||
|
|
153 | return false; | |||
|
|
154 | } | |||
|
|
155 | } | |||
|
|
156 | return true; | |||
|
|
157 | } | |||
|
|
158 | ||||
|
|
159 | /** | |||
|
|
160 | * Returns {@code true} if this version has a pre-release part. | |||
|
|
161 | * | |||
|
|
162 | * @return {@code true} if {@link #preRelease()} is present | |||
|
|
163 | */ | |||
|
|
164 | public boolean isPreRelease() { | |||
|
|
165 | return preRelease().isPresent(); | |||
|
|
166 | } | |||
|
|
167 | ||||
|
|
168 | /** | |||
|
|
169 | * Returns {@code true} if this version is considered "stable". | |||
|
|
170 | * <p> | |||
|
|
171 | * By convention: | |||
|
|
172 | * <ul> | |||
|
|
173 | * <li>MAJOR must be greater than 0</li> | |||
|
|
174 | * <li>No pre-release part is present</li> | |||
|
|
175 | * </ul> | |||
|
|
176 | * | |||
|
|
177 | * @return {@code true} if this version is a stable release | |||
|
|
178 | */ | |||
|
|
179 | public boolean isStable() { | |||
|
|
180 | return !isPreRelease() && major() > 0; | |||
|
|
181 | } | |||
|
|
182 | ||||
|
|
183 | /** | |||
|
|
184 | * Returns {@code true} if this version has strictly lower precedence | |||
|
|
185 | * than the given {@code other} version according to | |||
|
|
186 | * {@link #compareTo(SemVersion)}. | |||
|
|
187 | * | |||
|
|
188 | * @param other the version to compare to | |||
|
|
189 | * @return {@code true} if {@code this.compareTo(other) < 0} | |||
|
|
190 | */ | |||
|
|
191 | public boolean isBefore(SemVersion other) { | |||
|
|
192 | return compareTo(other) < 0; | |||
|
|
193 | } | |||
|
|
194 | ||||
|
|
195 | /** | |||
|
|
196 | * Returns {@code true} if this version has strictly higher precedence | |||
|
|
197 | * than the given {@code other} version according to | |||
|
|
198 | * {@link #compareTo(SemVersion)}. | |||
|
|
199 | * | |||
|
|
200 | * @param other the version to compare to | |||
|
|
201 | * @return {@code true} if {@code this.compareTo(other) > 0} | |||
|
|
202 | */ | |||
|
|
203 | public boolean isAfter(SemVersion other) { | |||
|
|
204 | return compareTo(other) > 0; | |||
|
|
205 | } | |||
|
|
206 | ||||
|
|
207 | /** | |||
|
|
208 | * Canonical SemVer string representation. | |||
|
|
209 | * <p> | |||
|
|
210 | * This method is also used by Jackson during serialization. | |||
|
|
211 | * | |||
|
|
212 | * @return canonical SemVer string, e.g. {@code "1.2.3-alpha+build.1"} | |||
|
|
213 | */ | |||
|
|
214 | @JsonValue | |||
|
|
215 | public String asString() { | |||
|
|
216 | StringBuilder sb = new StringBuilder() | |||
|
|
217 | .append(major).append('.') | |||
|
|
218 | .append(minor).append('.') | |||
|
|
219 | .append(patch); | |||
|
|
220 | ||||
|
|
221 | preRelease.ifPresent(pr -> sb.append('-').append(pr)); | |||
|
|
222 | buildMetadata.ifPresent(md -> sb.append('+').append(md)); | |||
|
|
223 | ||||
|
|
224 | return sb.toString(); | |||
|
|
225 | } | |||
|
|
226 | ||||
|
|
227 | /** | |||
|
|
228 | * Creates a {@code SemVersion} from a canonical SemVer string. | |||
|
|
229 | * <p> | |||
|
|
230 | * Jackson will use this factory method when deserializing from a JSON string. | |||
|
|
231 | * | |||
|
|
232 | * @param value canonical SemVer string | |||
|
|
233 | * @return parsed {@code SemVersion} | |||
|
|
234 | */ | |||
|
|
235 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) | |||
|
|
236 | public static SemVersion fromJson(String value) { | |||
|
|
237 | return parse(value); | |||
|
|
238 | } | |||
|
|
239 | ||||
|
|
240 | /** | |||
|
|
241 | * Compares this version to another {@link SemVersion} according to the | |||
|
|
242 | * SemVer 2.0.0 precedence rules. | |||
|
|
243 | * <p> | |||
|
|
244 | * Build metadata is ignored for ordering. | |||
|
|
245 | */ | |||
|
|
246 | @Override | |||
|
|
247 | public int compareTo(SemVersion other) { | |||
|
|
248 | // 1. major / minor / patch | |||
|
|
249 | int c = Integer.compare(this.major, other.major); | |||
|
|
250 | if (c != 0) | |||
|
|
251 | return c; | |||
|
|
252 | ||||
|
|
253 | c = Integer.compare(this.minor, other.minor); | |||
|
|
254 | if (c != 0) | |||
|
|
255 | return c; | |||
|
|
256 | ||||
|
|
257 | c = Integer.compare(this.patch, other.patch); | |||
|
|
258 | if (c != 0) | |||
|
|
259 | return c; | |||
|
|
260 | ||||
|
|
261 | // 2. pre-release (absence > presence) | |||
|
|
262 | boolean thisHasPre = this.preRelease.isPresent(); | |||
|
|
263 | boolean otherHasPre = other.preRelease.isPresent(); | |||
|
|
264 | ||||
|
|
265 | if (!thisHasPre && !otherHasPre) { | |||
|
|
266 | return 0; | |||
|
|
267 | } else if (!thisHasPre) { | |||
|
|
268 | // normal version > pre-release | |||
|
|
269 | return 1; | |||
|
|
270 | } else if (!otherHasPre) { | |||
|
|
271 | return -1; | |||
|
|
272 | } | |||
|
|
273 | ||||
|
|
274 | // 3. both have pre-release: compare identifiers | |||
|
|
275 | return comparePreRelease(this.preRelease.get(), other.preRelease.get()); | |||
|
|
276 | } | |||
|
|
277 | ||||
|
|
278 | private static int comparePreRelease(String a, String b) { | |||
|
|
279 | String[] aParts = a.split("\\."); | |||
|
|
280 | String[] bParts = b.split("\\."); | |||
|
|
281 | ||||
|
|
282 | int len = Math.min(aParts.length, bParts.length); | |||
|
|
283 | for (int i = 0; i < len; i++) { | |||
|
|
284 | String ai = aParts[i]; | |||
|
|
285 | String bi = bParts[i]; | |||
|
|
286 | ||||
|
|
287 | boolean aNum = isNumericIdentifier(ai); | |||
|
|
288 | boolean bNum = isNumericIdentifier(bi); | |||
|
|
289 | ||||
|
|
290 | if (aNum && bNum) { | |||
|
|
291 | int aiVal = Integer.parseInt(ai); | |||
|
|
292 | int biVal = Integer.parseInt(bi); | |||
|
|
293 | int c = Integer.compare(aiVal, biVal); | |||
|
|
294 | if (c != 0) | |||
|
|
295 | return c; | |||
|
|
296 | } else if (aNum && !bNum) { | |||
|
|
297 | // numeric identifiers have lower precedence than non-numeric | |||
|
|
298 | return -1; | |||
|
|
299 | } else if (!aNum && bNum) { | |||
|
|
300 | return 1; | |||
|
|
301 | } else { | |||
|
|
302 | int c = ai.compareTo(bi); | |||
|
|
303 | if (c != 0) | |||
|
|
304 | return c; | |||
|
|
305 | } | |||
|
|
306 | } | |||
|
|
307 | ||||
|
|
308 | // If all common identifiers are equal, the shorter one has lower precedence | |||
|
|
309 | return Integer.compare(aParts.length, bParts.length); | |||
|
|
310 | } | |||
|
|
311 | ||||
|
|
312 | @Override | |||
|
|
313 | public String toString() { | |||
|
|
314 | return asString(); | |||
|
|
315 | } | |||
|
|
316 | } | |||
| @@ -1,43 +1,42 | |||||
| 1 | plugins { |
|
1 | plugins { | |
| 2 | id "java-library" |
|
2 | id "java-library" | |
| 3 | id "ivy-publish" |
|
3 | id "ivy-publish" | |
| 4 | } |
|
4 | } | |
| 5 |
|
5 | |||
| 6 | java { |
|
6 | java { | |
| 7 | withJavadocJar() |
|
7 | withJavadocJar() | |
| 8 | withSourcesJar() |
|
8 | withSourcesJar() | |
| 9 | toolchain { |
|
9 | toolchain { | |
| 10 | languageVersion = JavaLanguageVersion.of(21) |
|
10 | languageVersion = JavaLanguageVersion.of(21) | |
| 11 | } |
|
11 | } | |
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | dependencies { |
|
14 | dependencies { | |
| 15 | compileOnly libs.jdt.annotations |
|
15 | compileOnly libs.jdt.annotations | |
| 16 |
|
16 | |||
| 17 | implementation libs.bundles.jackson |
|
17 | api gradleApi(), | |
| 18 |
|
18 | libs.bundles.jackson | ||
| 19 | api gradleApi() |
|
|||
| 20 | } |
|
19 | } | |
| 21 |
|
20 | |||
| 22 | task printVersion{ |
|
21 | task printVersion{ | |
| 23 | doLast { |
|
22 | doLast { | |
| 24 | println "project: $project.group:$project.name:$project.version" |
|
23 | println "project: $project.group:$project.name:$project.version" | |
| 25 | println "jar: ${->jar.archiveFileName.get()}" |
|
24 | println "jar: ${->jar.archiveFileName.get()}" | |
| 26 | } |
|
25 | } | |
| 27 | } |
|
26 | } | |
| 28 |
|
27 | |||
| 29 | publishing { |
|
28 | publishing { | |
| 30 | repositories { |
|
29 | repositories { | |
| 31 | ivy { |
|
30 | ivy { | |
| 32 | url "${System.properties["user.home"]}/ivy-repo" |
|
31 | url "${System.properties["user.home"]}/ivy-repo" | |
| 33 | } |
|
32 | } | |
| 34 | } |
|
33 | } | |
| 35 | publications { |
|
34 | publications { | |
| 36 | ivy(IvyPublication) { |
|
35 | ivy(IvyPublication) { | |
| 37 | from components.java |
|
36 | from components.java | |
| 38 | descriptor.description { |
|
37 | descriptor.description { | |
| 39 | text = providers.provider({ description }) |
|
38 | text = providers.provider({ description }) | |
| 40 | } |
|
39 | } | |
| 41 | } |
|
40 | } | |
| 42 | } |
|
41 | } | |
| 43 | } No newline at end of file |
|
42 | } | |
| @@ -1,49 +1,61 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.InputStream; |
|
4 | import java.io.InputStream; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.function.Supplier; |
|
6 | import java.util.function.Supplier; | |
| 7 |
|
7 | |||
| 8 | import org.gradle.api.provider.Provider; |
|
8 | import org.gradle.api.provider.Provider; | |
|
|
9 | import org.gradle.util.Configurable; | |||
| 9 | import org.implab.gradle.common.exec.RedirectFrom; |
|
10 | import org.implab.gradle.common.exec.RedirectFrom; | |
|
|
11 | import org.implab.gradle.common.utils.Closures; | |||
| 10 |
|
12 | |||
| 11 | public class RedirectFromSpec { |
|
13 | import groovy.lang.Closure; | |
|
|
14 | ||||
|
|
15 | public class RedirectFromSpec implements Configurable<RedirectFromSpec> { | |||
| 12 | private Supplier<RedirectFrom> streamRedirect; |
|
16 | private Supplier<RedirectFrom> streamRedirect; | |
| 13 |
|
17 | |||
| 14 | public boolean isRedirected() { |
|
18 | public boolean isRedirected() { | |
| 15 | return streamRedirect != null; |
|
19 | return streamRedirect != null; | |
| 16 | } |
|
20 | } | |
| 17 |
|
21 | |||
| 18 | public Optional<RedirectFrom> getRedirection() { |
|
22 | public Optional<RedirectFrom> getRedirection() { | |
| 19 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); | |
| 20 | } |
|
24 | } | |
| 21 |
|
25 | |||
| 22 | public void fromFile(File file) { |
|
26 | public void fromFile(File file) { | |
| 23 | streamRedirect = () -> RedirectFrom.file(file); |
|
27 | streamRedirect = () -> RedirectFrom.file(file); | |
| 24 | } |
|
28 | } | |
| 25 |
|
29 | |||
| 26 | public void fromFile(Provider<File> fileProvider) { |
|
30 | public void fromFile(Provider<File> fileProvider) { | |
| 27 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; |
|
31 | streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; | |
| 28 | } |
|
32 | } | |
| 29 |
|
33 | |||
| 30 | public void fromStream(InputStream stream) { |
|
34 | public void fromStream(InputStream stream) { | |
| 31 | streamRedirect = () -> RedirectFrom.stream(stream); |
|
35 | streamRedirect = () -> RedirectFrom.stream(stream); | |
| 32 | } |
|
36 | } | |
| 33 |
|
37 | |||
| 34 | public void fromStream(Provider<InputStream> streamProvider) { |
|
38 | public void fromStream(Provider<InputStream> streamProvider) { | |
| 35 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; |
|
39 | streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; | |
| 36 | } |
|
40 | } | |
| 37 |
|
41 | |||
| 38 | public void from(Object input) { |
|
42 | public void from(Object input) { | |
| 39 | if (input instanceof Provider<?> inputProvider) { |
|
43 | if (input instanceof Provider<?> inputProvider) { | |
| 40 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; |
|
44 | streamRedirect = inputProvider.map(RedirectFrom::any)::get; | |
| 41 | } else { |
|
45 | } else { | |
| 42 | streamRedirect = () -> RedirectFrom.any(input); |
|
46 | streamRedirect = () -> RedirectFrom.any(input); | |
| 43 | } |
|
47 | } | |
| 44 | } |
|
48 | } | |
| 45 |
|
49 | |||
| 46 | public void empty() { |
|
50 | public void empty() { | |
| 47 | streamRedirect = () -> null; |
|
51 | streamRedirect = () -> null; | |
| 48 | } |
|
52 | } | |
|
|
53 | ||||
|
|
54 | @Override | |||
|
|
55 | public RedirectFromSpec configure(Closure cl) { | |||
|
|
56 | Closures.apply(cl, this); | |||
|
|
57 | return this; | |||
|
|
58 | } | |||
|
|
59 | ||||
|
|
60 | ||||
| 49 | } |
|
61 | } | |
| @@ -1,65 +1,75 | |||||
| 1 | package org.implab.gradle.common.dsl; |
|
1 | package org.implab.gradle.common.dsl; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.OutputStream; |
|
4 | import java.io.OutputStream; | |
| 5 | import java.util.Optional; |
|
5 | import java.util.Optional; | |
| 6 | import java.util.function.Consumer; |
|
6 | import java.util.function.Consumer; | |
| 7 | import java.util.function.Supplier; |
|
7 | import java.util.function.Supplier; | |
| 8 |
|
8 | |||
| 9 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
9 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 10 | import org.eclipse.jdt.annotation.Nullable; |
|
10 | import org.eclipse.jdt.annotation.Nullable; | |
| 11 | import org.gradle.api.provider.Provider; |
|
11 | import org.gradle.api.provider.Provider; | |
|
|
12 | import org.gradle.util.Configurable; | |||
| 12 | import org.implab.gradle.common.exec.RedirectTo; |
|
13 | import org.implab.gradle.common.exec.RedirectTo; | |
|
|
14 | import org.implab.gradle.common.utils.Closures; | |||
|
|
15 | ||||
|
|
16 | import groovy.lang.Closure; | |||
| 13 |
|
17 | |||
| 14 | @NonNullByDefault |
|
18 | @NonNullByDefault | |
| 15 | public class RedirectToSpec { |
|
19 | public class RedirectToSpec implements Configurable<RedirectToSpec> { | |
| 16 | private Supplier<RedirectTo> streamRedirect; |
|
20 | private Supplier<RedirectTo> streamRedirect; | |
| 17 |
|
21 | |||
| 18 | public boolean isRedirected() { |
|
22 | public boolean isRedirected() { | |
| 19 | return getRedirection().isPresent(); |
|
23 | return getRedirection().isPresent(); | |
| 20 | } |
|
24 | } | |
| 21 |
|
25 | |||
| 22 | public Optional<RedirectTo> getRedirection() { |
|
26 | public Optional<RedirectTo> getRedirection() { | |
| 23 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); |
|
27 | return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); | |
| 24 | } |
|
28 | } | |
| 25 |
|
29 | |||
| 26 | public @Nullable RedirectTo getRedirectionOrNull() { |
|
30 | public @Nullable RedirectTo getRedirectionOrNull() { | |
| 27 | return streamRedirect != null ? streamRedirect.get() : null; |
|
31 | return streamRedirect != null ? streamRedirect.get() : null; | |
| 28 | } |
|
32 | } | |
| 29 |
|
33 | |||
| 30 | public void toFile(File file) { |
|
34 | public void toFile(File file) { | |
| 31 | streamRedirect = () -> RedirectTo.file(file); |
|
35 | streamRedirect = () -> RedirectTo.file(file); | |
| 32 | } |
|
36 | } | |
| 33 |
|
37 | |||
| 34 | public void toFile(Provider<File> fileProvider) { |
|
38 | public void toFile(Provider<File> fileProvider) { | |
| 35 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; |
|
39 | streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; | |
| 36 | } |
|
40 | } | |
| 37 |
|
41 | |||
| 38 | public void toStream(OutputStream stream) { |
|
42 | public void toStream(OutputStream stream) { | |
| 39 | streamRedirect = () -> RedirectTo.stream(stream); |
|
43 | streamRedirect = () -> RedirectTo.stream(stream); | |
| 40 | } |
|
44 | } | |
| 41 |
|
45 | |||
| 42 | public void toStream(Provider<OutputStream> streamProvider) { |
|
46 | public void toStream(Provider<OutputStream> streamProvider) { | |
| 43 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; |
|
47 | streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; | |
| 44 | } |
|
48 | } | |
| 45 |
|
49 | |||
| 46 | public void to(Object output) { |
|
50 | public void to(Object output) { | |
| 47 | if (output instanceof Provider<?> outputProvider) { |
|
51 | if (output instanceof Provider<?> outputProvider) { | |
| 48 | streamRedirect = outputProvider.map(RedirectTo::any)::get; |
|
52 | streamRedirect = outputProvider.map(RedirectTo::any)::get; | |
| 49 | } else { |
|
53 | } else { | |
| 50 | streamRedirect = () -> RedirectTo.any(output); |
|
54 | streamRedirect = () -> RedirectTo.any(output); | |
| 51 | } |
|
55 | } | |
| 52 | } |
|
56 | } | |
| 53 |
|
57 | |||
| 54 | public void eachLine(Consumer<String> consumer) { |
|
58 | public void eachLine(Consumer<String> consumer) { | |
| 55 | streamRedirect = () -> RedirectTo.eachLine(consumer); |
|
59 | streamRedirect = () -> RedirectTo.eachLine(consumer); | |
| 56 | } |
|
60 | } | |
| 57 |
|
61 | |||
| 58 | public void allText(Consumer<String> consumer) { |
|
62 | public void allText(Consumer<String> consumer) { | |
| 59 | streamRedirect = () -> RedirectTo.allText(consumer); |
|
63 | streamRedirect = () -> RedirectTo.allText(consumer); | |
| 60 | } |
|
64 | } | |
| 61 |
|
65 | |||
| 62 | public void discard() { |
|
66 | public void discard() { | |
| 63 | streamRedirect = () -> null; |
|
67 | streamRedirect = () -> null; | |
| 64 | } |
|
68 | } | |
|
|
69 | ||||
|
|
70 | @Override | |||
|
|
71 | public RedirectToSpec configure(Closure cl) { | |||
|
|
72 | Closures.apply(cl, this); | |||
|
|
73 | return this; | |||
|
|
74 | } | |||
| 65 | } |
|
75 | } | |
| @@ -1,126 +1,142 | |||||
| 1 |
package org.implab.gradle.common. |
|
1 | package org.implab.gradle.common.json; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.io.IOException; |
|
4 | import java.io.IOException; | |
|
|
5 | import java.util.Map; | |||
| 5 | import java.util.function.Consumer; |
|
6 | import java.util.function.Consumer; | |
|
|
7 | import java.util.function.Function; | |||
| 6 |
|
8 | |||
| 7 | import org.implab.gradle.common.utils.LazyValue; |
|
9 | import org.implab.gradle.common.utils.LazyValue; | |
| 8 |
|
10 | |||
| 9 | import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|
11 | import com.fasterxml.jackson.annotation.JsonInclude.Include; | |
| 10 | import com.fasterxml.jackson.core.JsonProcessingException; |
|
12 | import com.fasterxml.jackson.core.JsonProcessingException; | |
| 11 | import com.fasterxml.jackson.core.type.TypeReference; |
|
13 | import com.fasterxml.jackson.core.type.TypeReference; | |
| 12 | import com.fasterxml.jackson.core.util.DefaultIndenter; |
|
14 | import com.fasterxml.jackson.core.util.DefaultIndenter; | |
| 13 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; |
|
15 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; | |
| 14 | import com.fasterxml.jackson.databind.JsonMappingException; |
|
16 | import com.fasterxml.jackson.databind.JsonMappingException; | |
| 15 | import com.fasterxml.jackson.databind.ObjectMapper; |
|
17 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 16 | import com.fasterxml.jackson.databind.SerializationFeature; |
|
18 | import com.fasterxml.jackson.databind.SerializationFeature; | |
| 17 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; |
|
19 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; | |
| 18 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
|
20 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |
| 19 |
|
21 | |||
| 20 | public class Json { |
|
22 | public class Json { | |
| 21 |
|
23 | |||
| 22 | private final static LazyValue<ObjectMapper> mapper = new LazyValue<>(Json::createMapper); |
|
24 | private final static LazyValue<ObjectMapper> mapper = new LazyValue<>(Json::createMapper); | |
| 23 |
|
25 | |||
| 24 | private static ObjectMapper createMapper() { |
|
26 | private static ObjectMapper createMapper() { | |
| 25 | DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); |
|
27 | DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); | |
| 26 | prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE); |
|
28 | prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE); | |
| 27 |
|
29 | |||
| 28 | return new ObjectMapper() |
|
30 | return new ObjectMapper() | |
| 29 | .registerModule(new Jdk8Module()) |
|
31 | .registerModule(new Jdk8Module()) | |
| 30 | .registerModule(new JavaTimeModule()) |
|
32 | .registerModule(new JavaTimeModule()) | |
| 31 | .setSerializationInclusion(Include.NON_EMPTY) |
|
33 | .setSerializationInclusion(Include.NON_EMPTY) | |
| 32 | .enable(SerializationFeature.INDENT_OUTPUT) |
|
34 | .enable(SerializationFeature.INDENT_OUTPUT) | |
| 33 | .setDefaultPrettyPrinter(prettyPrinter); |
|
35 | .setDefaultPrettyPrinter(prettyPrinter); | |
| 34 | } |
|
36 | } | |
| 35 |
|
37 | |||
| 36 | public static ObjectMapper jsonMapper() { |
|
38 | public static ObjectMapper jsonMapper() { | |
| 37 | return mapper.get(); |
|
39 | return mapper.get(); | |
| 38 | } |
|
40 | } | |
| 39 |
|
41 | |||
| 40 |
public static Consumer< |
|
42 | public static Consumer<Object> fileWriter(File file) { | |
| 41 |
return |
|
43 | return value -> { | |
| 42 | try { |
|
44 | try { | |
| 43 | jsonMapper().writeValue(file, value); |
|
45 | jsonMapper().writeValue(file, value); | |
| 44 | } catch (IOException e) { |
|
46 | } catch (IOException e) { | |
| 45 | throw new SerializationException(e); |
|
47 | throw new SerializationException(e); | |
| 46 | } |
|
48 | } | |
| 47 | }; |
|
49 | }; | |
| 48 | } |
|
50 | } | |
| 49 |
|
51 | |||
| 50 | public static void write(File file, Object value) { |
|
52 | public static void write(File file, Object value) { | |
| 51 | try { |
|
53 | try { | |
| 52 | jsonMapper().writeValue(file, value); |
|
54 | jsonMapper().writeValue(file, value); | |
| 53 | } catch (IOException e) { |
|
55 | } catch (IOException e) { | |
| 54 | throw new SerializationException(e); |
|
56 | throw new SerializationException(e); | |
| 55 | } |
|
57 | } | |
| 56 | } |
|
58 | } | |
| 57 |
|
59 | |||
| 58 | public static String stringify(Object value) { |
|
60 | public static String stringify(Object value) { | |
| 59 | try { |
|
61 | try { | |
| 60 | return jsonMapper().writeValueAsString(value); |
|
62 | return jsonMapper().writeValueAsString(value); | |
| 61 | } catch (JsonProcessingException e) { |
|
63 | } catch (JsonProcessingException e) { | |
| 62 | throw new SerializationException(e); |
|
64 | throw new SerializationException(e); | |
| 63 | } |
|
65 | } | |
| 64 | } |
|
66 | } | |
| 65 |
|
67 | |||
| 66 | public static <T> T parse(String json, Class<T> clazz) { |
|
68 | public static <T> T parse(String json, Class<T> clazz) { | |
| 67 | try { |
|
69 | try { | |
| 68 | return jsonMapper().readValue(json, clazz); |
|
70 | return jsonMapper().readValue(json, clazz); | |
| 69 | } catch (JsonProcessingException e) { |
|
71 | } catch (JsonProcessingException e) { | |
| 70 | throw new SerializationException(e); |
|
72 | throw new SerializationException(e); | |
| 71 | } |
|
73 | } | |
| 72 | } |
|
74 | } | |
| 73 |
|
75 | |||
| 74 | public static <T> T parse(String json, TypeReference<T> type) { |
|
76 | public static <T> T parse(String json, TypeReference<T> type) { | |
| 75 | try { |
|
77 | try { | |
| 76 | return jsonMapper().readValue(json, type); |
|
78 | return jsonMapper().readValue(json, type); | |
| 77 | } catch (JsonProcessingException e) { |
|
79 | } catch (JsonProcessingException e) { | |
| 78 | throw new SerializationException(e); |
|
80 | throw new SerializationException(e); | |
| 79 | } |
|
81 | } | |
| 80 | } |
|
82 | } | |
| 81 |
|
83 | |||
|
|
84 | public static <V> Map<String, V> parseMap(String json, Class<V> clazz) { | |||
|
|
85 | return parse(json, new TypeReference<>() { | |||
|
|
86 | }); | |||
|
|
87 | } | |||
|
|
88 | ||||
|
|
89 | public static <V> Function<String, Map<String, V>> mapParser(Class<V> clazz) { | |||
|
|
90 | return json -> parseMap(json, clazz); | |||
|
|
91 | } | |||
|
|
92 | ||||
| 82 | public static <T> T read(File file, TypeReference<T> type) { |
|
93 | public static <T> T read(File file, TypeReference<T> type) { | |
| 83 | try { |
|
94 | try { | |
| 84 | return jsonMapper().readValue(file, type); |
|
95 | return jsonMapper().readValue(file, type); | |
| 85 | } catch (IOException e) { |
|
96 | } catch (IOException e) { | |
| 86 | throw new SerializationException(e); |
|
97 | throw new SerializationException(e); | |
| 87 | } |
|
98 | } | |
| 88 | } |
|
99 | } | |
| 89 |
|
100 | |||
|
|
101 | public static <V> Map<String, V> readMap(File file, Class<V> clazz) { | |||
|
|
102 | return read(file, new TypeReference<>() { | |||
|
|
103 | }); | |||
|
|
104 | } | |||
|
|
105 | ||||
| 90 | public static <T> T updateValue(T target, Object source) { |
|
106 | public static <T> T updateValue(T target, Object source) { | |
| 91 | try { |
|
107 | try { | |
| 92 | return jsonMapper().updateValue(target, source); |
|
108 | return jsonMapper().updateValue(target, source); | |
| 93 | } catch (JsonMappingException e) { |
|
109 | } catch (JsonMappingException e) { | |
| 94 | throw new SerializationException(e); |
|
110 | throw new SerializationException(e); | |
| 95 | } |
|
111 | } | |
| 96 | } |
|
112 | } | |
| 97 |
|
113 | |||
| 98 | public static <T> T convertValue(Object source, Class<T> clazz) { |
|
114 | public static <T> T convertValue(Object source, Class<T> clazz) { | |
| 99 | var mapper = jsonMapper(); |
|
115 | var mapper = jsonMapper(); | |
| 100 | var buf = mapper.getSerializerProvider().bufferForValueConversion(jsonMapper()); |
|
116 | var buf = mapper.getSerializerProvider().bufferForValueConversion(jsonMapper()); | |
| 101 |
|
117 | |||
| 102 | try { |
|
118 | try { | |
| 103 | mapper.writeValue(buf, source); |
|
119 | mapper.writeValue(buf, source); | |
| 104 | return mapper.readValue(buf.asParser(), clazz); |
|
120 | return mapper.readValue(buf.asParser(), clazz); | |
| 105 | } catch (IOException e) { |
|
121 | } catch (IOException e) { | |
| 106 | throw new SerializationException(e); |
|
122 | throw new SerializationException(e); | |
| 107 | } |
|
123 | } | |
| 108 | } |
|
124 | } | |
| 109 |
|
125 | |||
| 110 | public static class SerializationException extends RuntimeException { |
|
126 | public static class SerializationException extends RuntimeException { | |
| 111 | public SerializationException() { |
|
127 | public SerializationException() { | |
| 112 | } |
|
128 | } | |
| 113 |
|
129 | |||
| 114 | public SerializationException(String message) { |
|
130 | public SerializationException(String message) { | |
| 115 | super(message); |
|
131 | super(message); | |
| 116 | } |
|
132 | } | |
| 117 |
|
133 | |||
| 118 | public SerializationException(Throwable cause) { |
|
134 | public SerializationException(Throwable cause) { | |
| 119 | super(cause); |
|
135 | super(cause); | |
| 120 | } |
|
136 | } | |
| 121 |
|
137 | |||
| 122 | public SerializationException(String message, Throwable cause) { |
|
138 | public SerializationException(String message, Throwable cause) { | |
| 123 | super(message, cause); |
|
139 | super(message, cause); | |
| 124 | } |
|
140 | } | |
| 125 | } |
|
141 | } | |
| 126 | } |
|
142 | } | |
| @@ -1,32 +1,31 | |||||
| 1 | package org.implab.gradle.common.utils; |
|
1 | package org.implab.gradle.common.utils; | |
| 2 |
|
2 | |||
| 3 | import groovy.lang.Closure; |
|
3 | import groovy.lang.Closure; | |
| 4 |
|
4 | |||
| 5 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 6 | import org.gradle.api.Action; |
|
6 | import org.gradle.api.Action; | |
| 7 |
|
7 | |||
| 8 | @NonNullByDefault |
|
8 | @NonNullByDefault | |
| 9 | public final class Closures { |
|
9 | public final class Closures { | |
| 10 | private Closures() { |
|
10 | private Closures() { | |
| 11 | } |
|
11 | } | |
| 12 |
|
12 | |||
| 13 | /** |
|
13 | /** | |
| 14 | * Wraps {@link Action} around the specified closure. The parameter |
|
14 | * Wraps {@link Action} around the specified closure. The parameter | |
| 15 | * of the action will be used as delegate in the specified closure. |
|
15 | * of the action will be used as delegate in the specified closure. | |
| 16 | * |
|
16 | * | |
| 17 | * @param <T> The type of the action parameter |
|
17 | * @param <T> The type of the action parameter | |
| 18 | * @param closure The closure |
|
18 | * @param closure The closure | |
| 19 | * @return |
|
19 | * @return | |
| 20 | */ |
|
20 | */ | |
| 21 | public static <T> Action<T> action(Closure<?> closure) { |
|
21 | public static <T> Action<T> action(Closure<?> closure) { | |
| 22 | return arg -> apply(closure, arg); |
|
22 | return arg -> apply(closure, arg); | |
| 23 | } |
|
23 | } | |
| 24 |
|
24 | |||
| 25 | public static void apply(Closure<?> action, Object target) { |
|
25 | public static void apply(Closure<?> action, Object target) { | |
| 26 | var c = (Closure<?>)action.clone(); |
|
26 | var c = (Closure<?>)action.clone(); | |
| 27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); |
|
27 | c.setResolveStrategy(Closure.DELEGATE_FIRST); | |
| 28 | c.setDelegate(target); |
|
28 | c.setDelegate(target); | |
| 29 | c.call(target); |
|
29 | c.call(target); | |
| 30 |
|
||||
| 31 | } |
|
30 | } | |
| 32 | } |
|
31 | } | |
| @@ -1,35 +1,33 | |||||
| 1 | package org.implab.gradle.common.utils; |
|
1 | package org.implab.gradle.common.utils; | |
| 2 |
|
2 | |||
| 3 | import java.io.File; |
|
3 | import java.io.File; | |
| 4 | import java.util.Optional; |
|
4 | import java.util.Optional; | |
| 5 |
|
5 | |||
| 6 | import org.implab.gradle.common.utils.os.SystemResolver; |
|
6 | import org.implab.gradle.common.utils.os.SystemResolver; | |
| 7 |
|
7 | |||
| 8 | public interface OperatingSystem { |
|
8 | public interface OperatingSystem { | |
| 9 |
|
9 | |||
| 10 | public static OperatingSystem CURRENT = SystemResolver.current(); |
|
|||
| 11 |
|
||||
| 12 | public static final String WINDOWS_FAMILY = "windows"; |
|
10 | public static final String WINDOWS_FAMILY = "windows"; | |
| 13 |
|
11 | |||
| 14 | public static final String LINUX_FAMILY = "linux"; |
|
12 | public static final String LINUX_FAMILY = "linux"; | |
| 15 |
|
13 | |||
| 16 | public static final String FREE_BSD_FAMILY = "freebsd"; |
|
14 | public static final String FREE_BSD_FAMILY = "freebsd"; | |
| 17 |
|
15 | |||
| 18 | public static final String MAC_OS_FAMILY = "os x"; |
|
16 | public static final String MAC_OS_FAMILY = "os x"; | |
| 19 |
|
17 | |||
| 20 | public static final String UNKNOWN_FAMILY = "unknown"; |
|
18 | public static final String UNKNOWN_FAMILY = "unknown"; | |
| 21 |
|
19 | |||
| 22 | String family(); |
|
20 | String family(); | |
| 23 |
|
21 | |||
| 24 | String name(); |
|
22 | String name(); | |
| 25 |
|
23 | |||
| 26 | String version(); |
|
24 | String version(); | |
| 27 |
|
25 | |||
| 28 | Optional<File> which(String cmd); |
|
26 | Optional<File> which(String cmd); | |
| 29 |
|
27 | |||
| 30 | Optional<File> which(String cmd, Iterable<? extends File> paths); |
|
28 | Optional<File> which(String cmd, Iterable<? extends File> paths); | |
| 31 |
|
29 | |||
| 32 | public static OperatingSystem current() { |
|
30 | public static OperatingSystem current() { | |
| 33 | return CURRENT; |
|
31 | return SystemResolver.current(); | |
| 34 | } |
|
32 | } | |
| 35 | } |
|
33 | } | |
| @@ -1,51 +1,69 | |||||
| 1 | package org.implab.gradle.common.utils; |
|
1 | package org.implab.gradle.common.utils; | |
| 2 |
|
2 | |||
| 3 | import java.util.HashMap; |
|
3 | import java.util.HashMap; | |
| 4 | import java.util.Map; |
|
4 | import java.util.Map; | |
| 5 | import java.util.function.Function; |
|
5 | import java.util.function.Function; | |
| 6 |
|
||||
| 7 | import org.gradle.api.Action; |
|
6 | import org.gradle.api.Action; | |
| 8 | import org.gradle.api.provider.ListProperty; |
|
7 | import org.gradle.api.provider.ListProperty; | |
| 9 | import org.gradle.api.provider.MapProperty; |
|
8 | import org.gradle.api.provider.MapProperty; | |
| 10 | import org.gradle.api.provider.Provider; |
|
9 | import org.gradle.api.provider.Provider; | |
| 11 |
|
10 | |||
| 12 | public final class Properties { |
|
11 | public final class Properties { | |
| 13 | private Properties() { |
|
12 | private Properties() { | |
| 14 | } |
|
13 | } | |
| 15 |
|
14 | |||
| 16 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { |
|
15 | public static <K> void mergeMap(MapProperty<K, Object> property, Map<K, Object> map) { | |
| 17 | mergeMap(property, map, Function.identity()); |
|
16 | mergeMap(property, map, Function.identity()); | |
| 18 | } |
|
17 | } | |
| 19 |
|
18 | |||
| 20 |
public static <K, V> void |
|
19 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, | |
|
|
20 | Function<Object, ? extends V> mapper) { | |||
|
|
21 | if (value instanceof Provider<?>) | |||
|
|
22 | property.put(key, ((Provider<?>) value).map(mapper::apply)); | |||
|
|
23 | else | |||
|
|
24 | property.put(key, mapper.apply(value)); | |||
|
|
25 | } | |||
|
|
26 | ||||
|
|
27 | public static <K> void putMapEntry(MapProperty<K, Object> property, K key, Object value) { | |||
|
|
28 | putMapEntry(property, key, value, Function.identity()); | |||
|
|
29 | } | |||
|
|
30 | ||||
|
|
31 | public static <K, V> void putMapEntry(MapProperty<K, V> property, K key, Object value, Class<V> valueType) { | |||
|
|
32 | putMapEntry(property, key, value, valueType::cast); | |||
|
|
33 | } | |||
|
|
34 | ||||
|
|
35 | public static <K, V> void mergeMap(MapProperty<K, V> property, Map<K, ?> map, | |||
|
|
36 | Function<Object, ? extends V> mapper) { | |||
| 21 | map.forEach((k, v) -> { |
|
37 | map.forEach((k, v) -> { | |
| 22 | if (v instanceof Provider<?>) |
|
38 | if (v instanceof Provider<?>) | |
| 23 | property.put(k, ((Provider<?>) v).map(mapper::apply)); |
|
39 | property.put(k, ((Provider<?>) v).map(mapper::apply)); | |
| 24 | else |
|
40 | else | |
| 25 | property.put(k, mapper.apply(v)); |
|
41 | property.put(k, mapper.apply(v)); | |
| 26 | }); |
|
42 | }); | |
| 27 | } |
|
43 | } | |
| 28 |
|
44 | |||
| 29 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { |
|
45 | public static void mergeList(ListProperty<Object> property, Iterable<Object> values) { | |
| 30 | mergeList(property, values, Function.identity()); |
|
46 | mergeList(property, values, Function.identity()); | |
| 31 | } |
|
47 | } | |
| 32 |
|
48 | |||
| 33 |
public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, |
|
49 | public static <V> void mergeList(ListProperty<V> property, Iterable<Object> values, | |
|
|
50 | Function<Object, ? extends V> mapper) { | |||
| 34 | values.forEach(v -> { |
|
51 | values.forEach(v -> { | |
| 35 | if (v instanceof Provider<?>) |
|
52 | if (v instanceof Provider<?>) | |
| 36 | property.add(((Provider<?>) v).map(mapper::apply)); |
|
53 | property.add(((Provider<?>) v).map(mapper::apply)); | |
| 37 | else |
|
54 | else | |
| 38 | property.add(mapper.apply(v)); |
|
55 | property.add(mapper.apply(v)); | |
| 39 | }); |
|
56 | }); | |
| 40 | } |
|
57 | } | |
| 41 |
|
58 | |||
| 42 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<Map<K, Object>> configure) { |
|
59 | public static <K> void configureMap(MapProperty<K, Object> prop, Action<? super Map<K, Object>> configure) { | |
| 43 | configureMap(prop, configure, Function.identity()); |
|
60 | configureMap(prop, configure, Function.identity()); | |
| 44 | } |
|
61 | } | |
| 45 |
|
62 | |||
| 46 |
public static <K, V> void configureMap(MapProperty<K, V> prop, Action<Map<K, Object>> configure, |
|
63 | public static <K, V> void configureMap(MapProperty<K, V> prop, Action<? super Map<K, Object>> configure, | |
|
|
64 | Function<Object, V> mapper) { | |||
| 47 | var map = new HashMap<K, Object>(); |
|
65 | var map = new HashMap<K, Object>(); | |
| 48 | configure.execute(map); |
|
66 | configure.execute(map); | |
| 49 | mergeMap(prop, map, mapper); |
|
67 | mergeMap(prop, map, mapper); | |
| 50 | } |
|
68 | } | |
| 51 | } |
|
69 | } | |
| @@ -1,83 +1,98 | |||||
| 1 | package org.implab.gradle.common.utils; |
|
1 | package org.implab.gradle.common.utils; | |
| 2 |
|
2 | |||
| 3 | import java.text.MessageFormat; |
|
3 | import java.text.MessageFormat; | |
| 4 | import java.util.Iterator; |
|
4 | import java.util.Iterator; | |
| 5 | import java.util.Spliterators; |
|
5 | import java.util.Spliterators; | |
| 6 | import java.util.Optional; |
|
6 | import java.util.Optional; | |
| 7 | import java.util.Set; |
|
|||
| 8 | import java.util.function.Function; |
|
|||
| 9 | import java.util.function.Supplier; |
|
7 | import java.util.function.Supplier; | |
| 10 | import java.util.stream.Collectors; |
|
|||
| 11 | import java.util.stream.Stream; |
|
8 | import java.util.stream.Stream; | |
| 12 | import java.util.stream.StreamSupport; |
|
9 | import java.util.stream.StreamSupport; | |
| 13 |
|
10 | |||
| 14 | import org.gradle.api.provider.Provider; |
|
11 | import org.gradle.api.provider.Provider; | |
| 15 |
|
12 | |||
| 16 | public final class Values { |
|
13 | public final class Values { | |
| 17 |
|
14 | |||
| 18 | private Values() { |
|
15 | private Values() { | |
| 19 | } |
|
16 | } | |
| 20 |
|
17 | |||
| 21 | /** |
|
18 | /** | |
| 22 | * Converts the supplied value to a string. |
|
19 | * Converts the supplied value to a string. | |
| 23 | */ |
|
20 | */ | |
| 24 | public static String toString(Object value) { |
|
21 | public static String toString(Object value) { | |
| 25 | if (value == null) { |
|
22 | if (value == null) { | |
| 26 | return null; |
|
23 | return null; | |
| 27 | } else if (value instanceof String string) { |
|
24 | } else if (value instanceof String string) { | |
| 28 | return string; |
|
25 | return string; | |
| 29 | } else if (value instanceof Provider<?> provider) { |
|
26 | } else if (value instanceof Provider<?> provider) { | |
| 30 | return toString(provider.getOrNull()); |
|
27 | return toString(provider.getOrNull()); | |
| 31 | } else if (value instanceof Supplier<?> supplier) { |
|
28 | } else if (value instanceof Supplier<?> supplier) { | |
| 32 | return toString(supplier.get()); |
|
29 | return toString(supplier.get()); | |
| 33 | } else { |
|
30 | } else { | |
| 34 | return value.toString(); |
|
31 | return value.toString(); | |
| 35 | } |
|
32 | } | |
| 36 | } |
|
33 | } | |
| 37 |
|
34 | |||
| 38 | public static <T> Stream<T> stream(Iterator<T> remaining) { |
|
35 | public static <T> Stream<T> stream(Iterator<T> remaining) { | |
| 39 | return StreamSupport.stream( |
|
36 | return StreamSupport.stream( | |
| 40 | Spliterators.spliteratorUnknownSize(remaining, 0), |
|
37 | Spliterators.spliteratorUnknownSize(remaining, 0), | |
| 41 | false); |
|
38 | false); | |
| 42 | } |
|
39 | } | |
| 43 |
|
40 | |||
| 44 | public static <T> Optional<T> take(Iterator<T> iterator) { |
|
41 | public static <T> Optional<T> take(Iterator<T> iterator) { | |
| 45 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); |
|
42 | return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); | |
| 46 | } |
|
43 | } | |
| 47 |
|
44 | |||
| 48 | public static <T> Iterable<T> iterable(T[] values) { |
|
45 | public static <T> Iterable<T> iterable(T[] values) { | |
| 49 | return () -> new ArrayIterator<>(values); |
|
46 | return () -> new ArrayIterator<>(values); | |
| 50 | } |
|
47 | } | |
| 51 |
|
48 | |||
| 52 | public static <T> Optional<T> optional(Provider<T> provider) { |
|
49 | public static <T> Optional<T> optional(Provider<T> provider) { | |
| 53 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); |
|
50 | return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); | |
| 54 | } |
|
51 | } | |
| 55 |
|
52 | |||
| 56 | public static <T> T required(Provider<T> provider, String providerName) { |
|
53 | public static <T> T required(Provider<T> provider, String providerName) { | |
| 57 | if (!provider.isPresent()) |
|
54 | if (!provider.isPresent()) | |
| 58 | throw new IllegalStateException( |
|
55 | throw new IllegalStateException( | |
| 59 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); |
|
56 | MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); | |
| 60 | return provider.get(); |
|
57 | return provider.get(); | |
| 61 | } |
|
58 | } | |
| 62 |
|
59 | |||
|
|
60 | public static boolean parseBoolean(Object value) { | |||
|
|
61 | if (value instanceof Boolean) { | |||
|
|
62 | return (Boolean) value; | |||
|
|
63 | } else { | |||
|
|
64 | var text = toString(value); | |||
|
|
65 | switch (text != null ? text.toLowerCase() : "") { | |||
|
|
66 | case "true", "yes", "1" -> { | |||
|
|
67 | return true; | |||
|
|
68 | } | |||
|
|
69 | case "false", "no", "0", "" -> { | |||
|
|
70 | return false; | |||
|
|
71 | } | |||
|
|
72 | default -> throw new IllegalArgumentException( | |||
|
|
73 | MessageFormat.format("Cannot parse boolean value from ''{0}''", text)); | |||
|
|
74 | } | |||
|
|
75 | } | |||
|
|
76 | } | |||
|
|
77 | ||||
| 63 | private static class ArrayIterator<T> implements Iterator<T> { |
|
78 | private static class ArrayIterator<T> implements Iterator<T> { | |
| 64 | private final T[] data; |
|
79 | private final T[] data; | |
| 65 |
|
80 | |||
| 66 | private int pos = 0; |
|
81 | private int pos = 0; | |
| 67 |
|
82 | |||
| 68 | ArrayIterator(T[] data) { |
|
83 | ArrayIterator(T[] data) { | |
| 69 | this.data = data; |
|
84 | this.data = data; | |
| 70 | } |
|
85 | } | |
| 71 |
|
86 | |||
| 72 | @Override |
|
87 | @Override | |
| 73 | public boolean hasNext() { |
|
88 | public boolean hasNext() { | |
| 74 | return pos < data.length; |
|
89 | return pos < data.length; | |
| 75 | } |
|
90 | } | |
| 76 |
|
91 | |||
| 77 | @Override |
|
92 | @Override | |
| 78 | public T next() { |
|
93 | public T next() { | |
| 79 | return data[pos++]; |
|
94 | return data[pos++]; | |
| 80 | } |
|
95 | } | |
| 81 | } |
|
96 | } | |
| 82 |
|
97 | |||
| 83 | } |
|
98 | } | |
| @@ -1,29 +1,37 | |||||
| 1 | package org.implab.gradle.common.utils.os; |
|
1 | package org.implab.gradle.common.utils.os; | |
| 2 |
|
2 | |||
|
|
3 | import org.implab.gradle.common.utils.LazyValue; | |||
| 3 | import org.implab.gradle.common.utils.OperatingSystem; |
|
4 | import org.implab.gradle.common.utils.OperatingSystem; | |
| 4 |
|
5 | |||
| 5 | public class SystemResolver { |
|
6 | public class SystemResolver { | |
| 6 | public static OperatingSystem current() { |
|
7 | ||
|
|
8 | private final static LazyValue<OperatingSystem> current = new LazyValue<>(SystemResolver::resolveCurrent); | |||
|
|
9 | ||||
|
|
10 | private static OperatingSystem resolveCurrent() { | |||
| 7 | var osName = System.getProperty("os.name"); |
|
11 | var osName = System.getProperty("os.name"); | |
| 8 | var osVersion = System.getProperty("os.version"); |
|
12 | var osVersion = System.getProperty("os.version"); | |
| 9 |
|
13 | |||
| 10 | return forName(osName, osVersion); |
|
14 | return forName(osName, osVersion); | |
| 11 | } |
|
15 | } | |
| 12 |
|
16 | |||
|
|
17 | public static OperatingSystem current() { | |||
|
|
18 | return current.get(); | |||
|
|
19 | } | |||
|
|
20 | ||||
| 13 | public static OperatingSystem forName(String os, String version) { |
|
21 | public static OperatingSystem forName(String os, String version) { | |
| 14 | var osName = os.toLowerCase(); |
|
22 | var osName = os.toLowerCase(); | |
| 15 |
|
23 | |||
| 16 | if (osName.contains("windows")) { |
|
24 | if (osName.contains("windows")) { | |
| 17 | return new Windows(osName, version); |
|
25 | return new Windows(osName, version); | |
| 18 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { |
|
26 | } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { | |
| 19 | return new MacOs(osName, version); |
|
27 | return new MacOs(osName, version); | |
| 20 | } else if (osName.contains("linux")) { |
|
28 | } else if (osName.contains("linux")) { | |
| 21 | return new Linux(osName, version); |
|
29 | return new Linux(osName, version); | |
| 22 | } else if (osName.contains("freebsd")) { |
|
30 | } else if (osName.contains("freebsd")) { | |
| 23 | return new FreeBsd(osName, version); |
|
31 | return new FreeBsd(osName, version); | |
| 24 | } else { |
|
32 | } else { | |
| 25 | // Not strictly true |
|
33 | // Not strictly true | |
| 26 | return new GenericSystem(osName, version); |
|
34 | return new GenericSystem(osName, version); | |
| 27 | } |
|
35 | } | |
| 28 | } |
|
36 | } | |
| 29 | } |
|
37 | } | |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
