diff --git a/gradle.properties b/gradle.properties index bd16aa7..a34f56f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -gradle.version = 7.4.1 +gradle.version = 7.4.2 jwo.version=1.0-SNAPSHOT junitJupiter.version=5.8.2 lombok.version=1.18.22 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..41d9927 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00e33ed..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/net/woggioni/jwo/Application.java b/src/main/java/net/woggioni/jwo/Application.java new file mode 100644 index 0000000..1ec8797 --- /dev/null +++ b/src/main/java/net/woggioni/jwo/Application.java @@ -0,0 +1,117 @@ +package net.woggioni.jwo; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.stream.Stream; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +class Application { + + private static boolean validateConfigurationDirectory(Path candidate) { + try { + if (!Files.exists(candidate)) { + Files.createDirectories(candidate); + return true; + } else if (!Files.isDirectory(candidate)) { + log.debug("Configuration directory '{}' discarded because it is not a directory", candidate); + return false; + } else if (!Files.isWritable(candidate)) { + log.debug("Configuration directory '{}' discarded because it is not writable", candidate); + return false; + } else { + log.debug("Using configuration directory '{}'", candidate); + return true; + } + } catch (Exception ioe) { + log.debug( + String.format("configuration directory '%s' discarded: %s", candidate.toString(), ioe.getMessage()), + ioe + ); + return false; + } + } + + @SneakyThrows + private static Path selectCandidate(Stream candidates, String successMessage, String errorMessage) { + return candidates + .filter(Application::validateConfigurationDirectory) + .peek(p -> log.debug(successMessage, p)) + .findFirst() + .orElseThrow((Sup)() -> new FileNotFoundException(errorMessage)); + } + + @SneakyThrows + private static Path computeCacheDirectory(String appName, String jvmPropertyKey) { + Stream candidates; + if(OS.isUnix) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getProperty(jvmPropertyKey)).map(Paths::get), + Optional.ofNullable(System.getenv("XDG_CACHE_HOME")).map(prefix -> Paths.get(prefix, appName)), + Optional.ofNullable(System.getProperty("user.home")).map(prefix -> Paths.get(prefix, ".cache", appName)), + Optional.ofNullable(System.getProperty("user.home")).map(prefix -> Paths.get(prefix, "." + appName, "cache")) + ); + } else if(OS.isMac) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "Library", "Application support", appName, "cache")), + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "." + appName, "cache")) + ); + } else if(OS.isWindows) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getenv("LOCALAPPDATA")) + .map(prefix -> Paths.get(prefix, appName, "cache")), + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "Application Data", "Local Settings", "Application Data", appName, "cache"))); + } else { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "." + appName, "cache"))); + } + return selectCandidate(candidates, + "Using cache directory '{}'", + "Unable to find a usable cache directory"); + } + + + @SneakyThrows + private static Path computeConfigurationDirectory(String appName, String jvmPropertyKey) { + Stream candidates; + if(OS.isUnix) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getProperty(jvmPropertyKey)).map(Paths::get), + Optional.ofNullable(System.getenv("XDG_CONFIG_HOME")).map(prefix -> Paths.get(prefix, appName)), + Optional.ofNullable(System.getProperty("user.home")).map(prefix -> Paths.get(prefix, ".config", appName)), + Optional.ofNullable(System.getProperty("user.home")).map(prefix -> Paths.get(prefix, "." + appName, "config")) + ); + } else if(OS.isMac) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "Library", "Application support", appName, "config")), + Optional.ofNullable(System.getProperty("user.home")).map(prefix -> Paths.get(prefix, "." + appName, "config")) + ); + } else if(OS.isWindows) { + candidates = JWO.optional2Stream( + Optional.ofNullable(System.getenv("LOCALAPPDATA")) + .map(prefix -> Paths.get(prefix, appName, "config")), + Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "Application Data", "Local Settings", "Application Data", appName, "config"))); + } else { + candidates = JWO.optional2Stream(Optional.ofNullable(System.getProperty("user.home")) + .map(prefix -> Paths.get(prefix, "." + appName, "config"))); + } + return selectCandidate(candidates, + "Using configuration directory '{}'", + "Unable to find a usable configuration directory"); + } + +} diff --git a/src/main/java/net/woggioni/jwo/JWO.java b/src/main/java/net/woggioni/jwo/JWO.java index 229f9ed..1318321 100644 --- a/src/main/java/net/woggioni/jwo/JWO.java +++ b/src/main/java/net/woggioni/jwo/JWO.java @@ -554,6 +554,15 @@ public class JWO { }; } + @SafeVarargs + public static Stream optional2Stream(Optional...opts) { + return Arrays.stream(opts).filter(Optional::isPresent).map(Optional::get); + } + + public static Stream optional2Stream(Iterable> opts) { + return iterable2Stream(opts).filter(Optional::isPresent).map(Optional::get); + } + public static String decapitalize(String s, Locale locale) { if (!s.isEmpty() && !Character.isLowerCase(s.charAt(0))) return s.substring(0, 1).toLowerCase(locale) + s.substring(1); diff --git a/src/main/java/net/woggioni/jwo/OS.java b/src/main/java/net/woggioni/jwo/OS.java new file mode 100644 index 0000000..e1e02a9 --- /dev/null +++ b/src/main/java/net/woggioni/jwo/OS.java @@ -0,0 +1,43 @@ +package net.woggioni.jwo; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum OS { + WINDOWS("windows"), + MACOS("mac os x"), + LINUX("linux"), + SOLARIS("solaris"), + BSD("bsd"), + AIX("aix"), + HP_UX("hp-ux"), + UNIX("unix"), + POSIX("posix"), + VMS("vms"); + + private final String value; + + OS(String value) { + this.value = value; + } + + public static final OS current; + public static final boolean isUnix; + public static final boolean isWindows; + public static final boolean isMac; + + static { + OS currentOs = null; + String osName = System.getProperty("os.name").toLowerCase(); + for(OS os : values()) { + if(osName.startsWith(os.value)) { + currentOs = os; + } + } + if(currentOs == null) throw new IllegalArgumentException(String.format("Unrecognized OS '%s'", osName)); + current = currentOs; + isUnix = Stream.of(LINUX, SOLARIS, BSD, AIX, HP_UX).collect(Collectors.toSet()).contains(current); + isWindows = current == WINDOWS; + isMac = current == MACOS; + } +}