##// END OF EJS Templates
Removed JsonDelegate, ShellSpecMixin, added JsonObjectSpec, JsonArraySpec, SemVersion. Added WriteJson task and sources {} dsl plugin.
cin -
r22:5a3f85261d98 default
parent child
Show More
@@ -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;
49 }
58 }
59
60
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;
65 }
74 }
75 }
@@ -1,126 +1,142
1 package org.implab.gradle.common.utils;
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<File> fileWriter(Object value) {
42 public static Consumer<Object> fileWriter(File file) {
41 return file -> {
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 mergeMap(MapProperty<K, V> property, Map<K, ?> map, Function<Object,? extends V> mapper) {
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, Function<Object, ? extends V> mapper) {
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, Function<Object, V> mapper) {
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