summaryrefslogtreecommitdiff
path: root/enigma/src/main/java/cuchaz
diff options
context:
space:
mode:
authorGravatar 2xsaiko2020-08-04 20:42:39 +0200
committerGravatar GitHub2020-08-04 14:42:39 -0400
commit75a3442f9ff38222606a1e24753d4a57da1e8c0a (patch)
tree5b0e12fb055d15fcb31b0fd07ae4cf9512e0758e /enigma/src/main/java/cuchaz
parentRevamp About dialog (diff)
downloadenigma-fork-75a3442f9ff38222606a1e24753d4a57da1e8c0a.tar.gz
enigma-fork-75a3442f9ff38222606a1e24753d4a57da1e8c0a.tar.xz
enigma-fork-75a3442f9ff38222606a1e24753d4a57da1e8c0a.zip
Configuration stuff (#301)
* Begin writing new config system * Make config work * Save window size and position * Add editor font chooser * Use *.ini for windows and mac instead of *rc * Allow for changing language without having to restart the program * Save selected directory in file dialogs * Make dialog visible after moving it to the correct position * Don't change theme on the fly since it's broken * Remove unused gui parameter * Use xdg-open to open URLs on Linux since Desktop.browse doesn't work, at least not on my PC * Fix default proposed highlight color * Multi font selection dialog thingy * Remember network options * Make font selection dialog actually work * Collapse general actions ("OK", "Cancel", ..) into one translation * Localize font dialog * Use enum name when saving colors for consistency with currently selected theme * Save size of split panes * Import old config * Add test & fix some parts of the config serializer * TranslationChangeListener/TranslationUtil -> LanguageChangeListener/LanguageUtil
Diffstat (limited to 'enigma/src/main/java/cuchaz')
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java97
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java40
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java183
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java303
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/Os.java33
6 files changed, 667 insertions, 0 deletions
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java
new file mode 100644
index 0000000..cb9cbc2
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java
@@ -0,0 +1,97 @@
1package cuchaz.enigma.config;
2
3import java.io.IOException;
4import java.nio.charset.StandardCharsets;
5import java.nio.file.Files;
6import java.nio.file.Path;
7import java.util.Deque;
8import java.util.LinkedList;
9
10public class ConfigContainer {
11
12 private Path configPath;
13 private boolean existsOnDisk;
14
15 private final ConfigSection root = new ConfigSection();
16
17 public ConfigSection data() {
18 return this.root;
19 }
20
21 public void save() {
22 if (this.configPath == null) throw new IllegalStateException("File has no config path set!");
23 try {
24 Files.createDirectories(this.configPath.getParent());
25 Files.write(this.configPath, this.serialize().getBytes(StandardCharsets.UTF_8));
26 this.existsOnDisk = true;
27 } catch (IOException e) {
28 e.printStackTrace();
29 }
30 }
31
32 public void saveAs(Path path) {
33 this.configPath = path;
34 this.save();
35 }
36
37 public boolean existsOnDisk() {
38 return this.existsOnDisk;
39 }
40
41 public String serialize() {
42 return ConfigSerializer.structureToString(this.root);
43 }
44
45 public static ConfigContainer create() {
46 return new ConfigContainer();
47 }
48
49 public static ConfigContainer getOrCreate(String name) {
50 return ConfigContainer.getOrCreate(ConfigPaths.getConfigFilePath(name));
51 }
52
53 public static ConfigContainer getOrCreate(Path path) {
54 ConfigContainer cc = null;
55 try {
56 if (Files.exists(path)) {
57 String s = String.join("\n", Files.readAllLines(path));
58 cc = ConfigContainer.parse(s);
59 cc.existsOnDisk = true;
60 }
61 } catch (IOException e) {
62 e.printStackTrace();
63 }
64
65 if (cc == null) {
66 cc = ConfigContainer.create();
67 }
68
69 cc.configPath = path;
70 return cc;
71 }
72
73 public static ConfigContainer parse(String source) {
74 ConfigContainer cc = ConfigContainer.create();
75 Deque<ConfigSection> stack = new LinkedList<>();
76 stack.push(cc.root);
77 ConfigSerializer.parse(source, new ConfigStructureVisitor() {
78 @Override
79 public void visitKeyValue(String key, String value) {
80 stack.peekLast().setString(key, value);
81 }
82
83 @Override
84 public void visitSection(String section) {
85 stack.add(stack.peekLast().section(section));
86 }
87
88 @Override
89 public void jumpToRootSection() {
90 stack.clear();
91 stack.push(cc.root);
92 }
93 });
94 return cc;
95 }
96
97}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java
new file mode 100644
index 0000000..6e668f8
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.config;
2
3import java.nio.file.Path;
4import java.nio.file.Paths;
5
6import cuchaz.enigma.utils.Os;
7
8public class ConfigPaths {
9
10 public static Path getConfigFilePath(String name) {
11 String fileName = Os.getOs() == Os.LINUX ? String.format("%src", name) : String.format("%s.ini", name);
12 return getConfigPathRoot().resolve(fileName);
13 }
14
15 public static Path getConfigPathRoot() {
16 switch (Os.getOs()) {
17 case LINUX:
18 String configHome = System.getenv("XDG_CONFIG_HOME");
19 if (configHome == null) {
20 return getUserHomeUnix().resolve(".config");
21 }
22 return Paths.get(configHome);
23 case MAC:
24 return getUserHomeUnix().resolve("Library").resolve("Preferences");
25 case WINDOWS:
26 return Paths.get(System.getenv("LOCALAPPDATA"));
27 default:
28 return Paths.get(System.getProperty("user.dir"));
29 }
30 }
31
32 private static Path getUserHomeUnix() {
33 String userHome = System.getenv("HOME");
34 if (userHome == null) {
35 userHome = System.getProperty("user.dir");
36 }
37 return Paths.get(userHome);
38 }
39
40}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java
new file mode 100644
index 0000000..3e7bf6d
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java
@@ -0,0 +1,183 @@
1package cuchaz.enigma.config;
2
3import java.util.*;
4import java.util.function.Function;
5
6public class ConfigSection {
7
8 private final Map<String, String> values;
9 private final Map<String, ConfigSection> sections;
10
11 private ConfigSection(Map<String, String> values, Map<String, ConfigSection> sections) {
12 this.values = values;
13 this.sections = sections;
14 }
15
16 public ConfigSection() {
17 this(new HashMap<>(), new HashMap<>());
18 }
19
20 public ConfigSection section(String name) {
21 return this.sections.computeIfAbsent(name, _s -> new ConfigSection());
22 }
23
24 public Map<String, String> values() {
25 return Collections.unmodifiableMap(this.values);
26 }
27
28 public Map<String, ConfigSection> sections() {
29 return Collections.unmodifiableMap(this.sections);
30 }
31
32 public boolean remove(String key) {
33 return this.values.remove(key) != null;
34 }
35
36 public boolean removeSection(String key) {
37 return this.sections.remove(key) != null;
38 }
39
40 public Optional<String> getString(String key) {
41 return Optional.ofNullable(this.values.get(key));
42 }
43
44 public void setString(String key, String value) {
45 this.values.put(key, value);
46 }
47
48 public String setIfAbsentString(String key, String value) {
49 this.values.putIfAbsent(key, value);
50 return this.values.get(key);
51 }
52
53 public Optional<Boolean> getBool(String key) {
54 return ConfigSerializer.parseBool(this.values.get(key));
55 }
56
57 public boolean setIfAbsentBool(String key, boolean value) {
58 return this.getBool(key).orElseGet(() -> {
59 this.setBool(key, value);
60 return value;
61 });
62 }
63
64 public void setBool(String key, boolean value) {
65 this.values.put(key, Boolean.toString(value));
66 }
67
68 public OptionalInt getInt(String key) {
69 return ConfigSerializer.parseInt(this.values.get(key));
70 }
71
72 public void setInt(String key, int value) {
73 this.values.put(key, Integer.toString(value));
74 }
75
76 public int setIfAbsentInt(String key, int value) {
77 return this.getInt(key).orElseGet(() -> {
78 this.setInt(key, value);
79 return value;
80 });
81 }
82
83 public OptionalDouble getDouble(String key) {
84 return ConfigSerializer.parseDouble(this.values.get(key));
85 }
86
87 public void setDouble(String key, double value) {
88 this.values.put(key, Double.toString(value));
89 }
90
91 public double setIfAbsentDouble(String key, double value) {
92 return this.getDouble(key).orElseGet(() -> {
93 this.setDouble(key, value);
94 return value;
95 });
96 }
97
98 public OptionalInt getRgbColor(String key) {
99 return ConfigSerializer.parseRgbColor(this.values.get(key));
100 }
101
102 public void setRgbColor(String key, int value) {
103 this.values.put(key, ConfigSerializer.rgbColorToString(value));
104 }
105
106 public int setIfAbsentRgbColor(String key, int value) {
107 return this.getRgbColor(key).orElseGet(() -> {
108 this.setRgbColor(key, value);
109 return value;
110 });
111 }
112
113 public Optional<String[]> getArray(String key) {
114 return ConfigSerializer.parseArray(this.values.get(key));
115 }
116
117 public void setArray(String key, String[] value) {
118 this.values.put(key, ConfigSerializer.arrayToString(value));
119 }
120
121 public String[] setIfAbsentArray(String key, String[] value) {
122 return this.getArray(key).orElseGet(() -> {
123 this.setArray(key, value);
124 return value;
125 });
126 }
127
128 public Optional<int[]> getIntArray(String key) {
129 return this.getArray(key).map(arr -> Arrays.stream(arr).mapToInt(s -> ConfigSerializer.parseInt(s).orElse(0)).toArray());
130 }
131
132 public void setIntArray(String key, int[] value) {
133 this.setArray(key, Arrays.stream(value).mapToObj(Integer::toString).toArray(String[]::new));
134 }
135
136 public int[] setIfAbsentIntArray(String key, int[] value) {
137 return this.getIntArray(key).orElseGet(() -> {
138 this.setIntArray(key, value);
139 return value;
140 });
141 }
142
143 public <T extends Enum<T>> Optional<T> getEnum(Function<String, T> byName, String key) {
144 return ConfigSerializer.parseEnum(byName, this.values.get(key));
145 }
146
147 public <T extends Enum<T>> void setEnum(String key, T value) {
148 this.values.put(key, value.name());
149 }
150
151 public <T extends Enum<T>> T setIfAbsentEnum(Function<String, T> byName, String key, T value) {
152 return this.getEnum(byName, key).orElseGet(() -> {
153 this.setEnum(key, value);
154 return value;
155 });
156 }
157
158 public ConfigSection copy() {
159 Map<String, ConfigSection> sections = new HashMap<>(this.sections);
160 sections.replaceAll((k, v) -> v.copy());
161 return new ConfigSection(new HashMap<>(this.values), sections);
162 }
163
164 @Override
165 public boolean equals(Object o) {
166 if (this == o) return true;
167 if (!(o instanceof ConfigSection)) return false;
168 ConfigSection that = (ConfigSection) o;
169 return values.equals(that.values) &&
170 sections.equals(that.sections);
171 }
172
173 @Override
174 public int hashCode() {
175 return Objects.hash(values, sections);
176 }
177
178 @Override
179 public String toString() {
180 return String.format("ConfigSection { values: %s, sections: %s }", values, sections);
181 }
182
183}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java
new file mode 100644
index 0000000..dccb585
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java
@@ -0,0 +1,303 @@
1package cuchaz.enigma.config;
2
3import java.util.*;
4import java.util.Map.Entry;
5import java.util.function.Function;
6import java.util.regex.Pattern;
7import java.util.stream.Collectors;
8
9public final class ConfigSerializer {
10
11 private static final Pattern FULL_RGB_COLOR = Pattern.compile("#[0-9A-Fa-f]{6}");
12 private static final Pattern MIN_RGB_COLOR = Pattern.compile("#[0-9A-Fa-f]{3}");
13
14 private static final int UNEXPECTED_TOKEN = -1;
15 private static final int NO_MATCH = -2;
16
17 public static void parse(String v, ConfigStructureVisitor visitor) {
18 String[] lines = v.split("\n");
19
20 // join escaped newlines
21 int len = lines.length;
22 for (int i = len - 2; i >= 0; i--) {
23 if (lines[i].endsWith("\\")) {
24 lines[i] = String.format("%s\n%s", lines[i], lines[i + 1]);
25 len -= 1;
26 }
27 }
28
29 // parse for real
30 for (int i = 0; i < len; i++) {
31 String line = lines[i];
32
33 // skip empty lines and comment lines
34 if (line.trim().isEmpty() || line.trim().startsWith(";")) continue;
35
36 int r;
37 boolean fail = (r = parseSectionLine(line, 0, visitor)) == NO_MATCH &&
38 (r = parseKeyValue(line, 0, visitor)) == NO_MATCH;
39 }
40 }
41
42 private static int parseSectionLine(String v, int idx, ConfigStructureVisitor visitor) {
43 if (v.startsWith("[")) {
44 List<String> path = new ArrayList<>();
45 while (idx < v.length() && v.charAt(idx) == '[') {
46 idx = parseSection(v, idx, path);
47 if (idx == UNEXPECTED_TOKEN) return UNEXPECTED_TOKEN;
48 }
49
50 if (!path.isEmpty()) {
51 visitor.jumpToRootSection();
52 for (String s : path) {
53 visitor.visitSection(s);
54 }
55 }
56
57 return v.length();
58 } else {
59 return NO_MATCH;
60 }
61 }
62
63 private static int parseSection(String v, int idx, List<String> path) {
64 idx += 1; // skip leading [
65 StringBuilder sb = new StringBuilder();
66 while (idx < v.length()) {
67 int nextCloseBracket = v.indexOf(']', idx);
68 int nextEscape = v.indexOf('\\', idx);
69 int next = optMin(nextCloseBracket, nextEscape);
70 if (next == -1) {
71 // unexpected
72 return UNEXPECTED_TOKEN;
73 } else if (next == nextCloseBracket) {
74 sb.append(v, idx, nextCloseBracket);
75 path.add(sb.toString());
76 return nextCloseBracket + 1;
77 } else if (next == nextEscape) {
78 sb.append(v, idx, nextEscape);
79 idx = parseEscape(v, nextEscape, sb);
80 }
81 }
82 return idx;
83 }
84
85 private static int parseKeyValue(String v, int idx, ConfigStructureVisitor visitor) {
86 StringBuilder sb = new StringBuilder();
87 String k = null;
88 while (idx < v.length()) {
89 int nextEq = v.indexOf('=', idx);
90 int nextEscape = v.indexOf('\\', idx);
91 int next = optMin(nextEq, nextEscape);
92 if (next == -1) {
93 break;
94 } else if (next == nextEq) {
95 sb.append(v, idx, nextEq);
96 k = sb.toString();
97 sb.delete(0, sb.length());
98 idx = nextEq + 1;
99 break;
100 } else if (next == nextEscape) {
101 sb.append(v, idx, nextEscape);
102 idx = parseEscape(v, nextEscape, sb);
103 }
104 }
105 while (idx < v.length()) {
106 int nextEscape = v.indexOf('\\', idx);
107 if (nextEscape != -1) {
108 sb.append(v, idx, nextEscape);
109 idx = parseEscape(v, nextEscape, sb);
110 } else {
111 break;
112 }
113 }
114 sb.append(v, idx, v.length());
115 if (k == null) return NO_MATCH;
116 visitor.visitKeyValue(k, sb.toString());
117 return idx;
118 }
119
120 private static int parseEscape(String v, int idx, StringBuilder sb) {
121 if (idx + 1 < v.length()) {
122 if (v.charAt(idx + 1) == 'u') {
123 if (idx + 5 < v.length()) {
124 String codePoint = v.substring(idx + 2, idx + 6);
125 try {
126 int c = Integer.parseUnsignedInt(codePoint, 16);
127 sb.append((char) c);
128 } catch (NumberFormatException ignored) {
129 }
130 idx = idx + 6;
131 }
132 } else if (v.charAt(idx + 1) == 'n') {
133 sb.append('\n');
134 idx = idx + 2;
135 } else {
136 sb.append(v.charAt(idx + 1));
137 idx = idx + 2;
138 }
139 } else {
140 idx = idx + 1;
141 }
142 return idx;
143 }
144
145 public static String structureToString(ConfigSection section) {
146 StringBuilder sb = new StringBuilder();
147 structureToString(section, sb, new ArrayList<>());
148 return sb.toString();
149 }
150
151 private static void structureToString(ConfigSection section, StringBuilder sb, List<String> pathStack) {
152 if (!section.values().isEmpty()) {
153 if (sb.length() > 0) sb.append('\n');
154 pathStack.forEach(n -> sb.append('[').append(escapeSection(n)).append(']'));
155 if (!pathStack.isEmpty()) sb.append('\n');
156 section.values().entrySet().stream()
157 .sorted(Entry.comparingByKey())
158 .forEach(e -> sb.append(escapeKey(e.getKey())).append('=').append(escapeValue(e.getValue())).append('\n'));
159 }
160
161 section.sections().entrySet().stream().sorted(Entry.comparingByKey()).forEach(e -> {
162 pathStack.add(e.getKey());
163 structureToString(e.getValue(), sb, pathStack);
164 pathStack.remove(pathStack.size() - 1);
165 });
166 }
167
168 private static String escapeSection(String s) {
169 return s
170 .replace("\\", "\\\\")
171 .replace("\n", "\\n")
172 .replace("]", "\\]")
173 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
174 }
175
176 private static String escapeKey(String s) {
177 return s
178 .replace("\\", "\\\\")
179 .replace("[", "\\[")
180 .replace("\n", "\\n")
181 .replace("=", "\\=")
182 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
183 }
184
185 private static String escapeValue(String s) {
186 return s
187 .replace("\\", "\\\\")
188 .replace("\n", "\\n")
189 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
190 }
191
192 public static Optional<Boolean> parseBool(String v) {
193 if (v == null) return Optional.empty();
194 switch (v) {
195 case "true":
196 return Optional.of(true);
197 case "false":
198 return Optional.of(false);
199 default:
200 return Optional.empty();
201 }
202 }
203
204 public static OptionalInt parseInt(String v) {
205 if (v == null) return OptionalInt.empty();
206 try {
207 return OptionalInt.of(Integer.parseInt(v));
208 } catch (NumberFormatException e) {
209 return OptionalInt.empty();
210 }
211 }
212
213 public static OptionalDouble parseDouble(String v) {
214 if (v == null) return OptionalDouble.empty();
215 try {
216 return OptionalDouble.of(Double.parseDouble(v));
217 } catch (NumberFormatException e) {
218 return OptionalDouble.empty();
219 }
220 }
221
222 public static OptionalInt parseRgbColor(String v) {
223 if (v == null) return OptionalInt.empty();
224 try {
225 if (FULL_RGB_COLOR.matcher(v).matches()) {
226 return OptionalInt.of(Integer.parseUnsignedInt(v.substring(1), 16));
227 } else if (MIN_RGB_COLOR.matcher(v).matches()) {
228 int result = Integer.parseUnsignedInt(v.substring(1), 16);
229 // change 0xABC to 0xAABBCC
230 result = (result & 0x00F) | (result & 0x0F0) << 4 | (result & 0xF00) << 8;
231 result = result | result << 4;
232 return OptionalInt.of(result);
233 } else {
234 return OptionalInt.empty();
235 }
236 } catch (NumberFormatException e) {
237 return OptionalInt.empty();
238 }
239 }
240
241 public static String rgbColorToString(int color) {
242 color = color & 0xFFFFFF;
243 boolean isShort = ((color & 0xF0F0F0) >> 4 ^ color & 0x0F0F0F) == 0;
244 if (isShort) {
245 int packed = color & 0x0F0F0F;
246 packed = packed & 0xF | packed >> 4;
247 packed = packed & 0xFF | (packed & ~0xFF) >> 4;
248 return String.format("#%03x", packed);
249 } else {
250 return String.format("#%06x", color);
251 }
252 }
253
254 public static Optional<String[]> parseArray(String v) {
255 if (v == null) return Optional.empty();
256 List<String> l = new ArrayList<>();
257 int idx = 0;
258 StringBuilder cur = new StringBuilder();
259 while (true) {
260 int nextSep = v.indexOf(',', idx);
261 int nextEsc = v.indexOf('\\', idx);
262 int next = optMin(nextSep, nextEsc);
263 if (next == -1) {
264 cur.append(v, idx, v.length());
265 l.add(cur.toString());
266 return Optional.of(l.toArray(new String[0]));
267 } else if (next == nextSep) {
268 cur.append(v, idx, nextSep);
269 l.add(cur.toString());
270 cur.delete(0, cur.length());
271 idx = nextSep + 1;
272 } else if (next == nextEsc) {
273 cur.append(v, idx, nextEsc);
274 if (nextEsc + 1 < v.length()) {
275 cur.append(v.charAt(nextEsc + 1));
276 }
277 idx = nextEsc + 2;
278 }
279 }
280 }
281
282 public static String arrayToString(String[] values) {
283 return Arrays.stream(values)
284 .map(s -> s.replace("\\", "\\\\").replace(",", "\\,"))
285 .collect(Collectors.joining(","));
286 }
287
288 public static <T extends Enum<T>> Optional<T> parseEnum(Function<String, T> byName, String v) {
289 if (v == null) return Optional.empty();
290 try {
291 return Optional.of(byName.apply(v));
292 } catch (IllegalArgumentException e) {
293 return Optional.empty();
294 }
295 }
296
297 private static int optMin(int v1, int v2) {
298 if (v1 == -1) return v2;
299 if (v2 == -1) return v1;
300 return Math.min(v1, v2);
301 }
302
303}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java
new file mode 100644
index 0000000..12d7ec4
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.config;
2
3public interface ConfigStructureVisitor {
4
5 void visitKeyValue(String key, String value);
6
7 void visitSection(String section);
8
9 void jumpToRootSection();
10
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Os.java b/enigma/src/main/java/cuchaz/enigma/utils/Os.java
new file mode 100644
index 0000000..b493c04
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/Os.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.utils;
2
3import java.util.Locale;
4
5public enum Os {
6 LINUX,
7 MAC,
8 SOLARIS,
9 WINDOWS,
10 OTHER;
11
12 private static Os os = null;
13
14 public static Os getOs() {
15 if (os == null) {
16 String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
17 if (osName.contains("mac") || osName.contains("darwin")) {
18 os = MAC;
19 } else if (osName.contains("win")) {
20 os = WINDOWS;
21 } else if (osName.contains("nix") || osName.contains("nux")
22 || osName.contains("aix")) {
23 os = LINUX;
24 } else if (osName.contains("sunos")) {
25 os = SOLARIS;
26 } else {
27 os = OTHER;
28 }
29 }
30 return os;
31 }
32
33} \ No newline at end of file