]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/util/LangUtils.java
Merge tag 'v2.3.28' into testing
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / util / LangUtils.java
1 package org.argeo.cms.util;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.io.Writer;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9 import java.nio.file.StandardOpenOption;
10 import java.time.ZonedDateTime;
11 import java.time.temporal.ChronoUnit;
12 import java.time.temporal.Temporal;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Dictionary;
17 import java.util.Enumeration;
18 import java.util.HashMap;
19 import java.util.Hashtable;
20 import java.util.Iterator;
21 import java.util.LinkedHashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Properties;
26
27 import javax.naming.InvalidNameException;
28 import javax.naming.ldap.LdapName;
29
30 /** Utilities around Java basic features. */
31 public class LangUtils {
32 /*
33 * OBJECTS and STRINGS
34 */
35 /**
36 * Whether this {@link String} is <code>null</null>, empty, or only white
37 * spaces.
38 */
39 public static boolean isEmpty(String str) {
40 return str == null || "".equals(str.strip());
41 }
42
43 /*
44 * DICTIONARY
45 */
46
47 /**
48 * Creates a new {@link Dictionary} with one key-value pair. Key should not be
49 * null, but if the value is null, it returns an empty {@link Dictionary}.
50 */
51 public static Dictionary<String, Object> dict(String key, Object value) {
52 assert key != null;
53 Hashtable<String, Object> props = new Hashtable<>();
54 if (value != null)
55 props.put(key, value);
56 return props;
57 }
58
59 /** @deprecated Use {@link #dict(String, Object)} instead. */
60 @Deprecated
61 public static Dictionary<String, Object> dico(String key, Object value) {
62 return dict(key, value);
63 }
64
65 /** Converts a {@link Dictionary} to a {@link Map} of strings. */
66 public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
67 if (properties == null) {
68 return null;
69 }
70 Map<String, String> res = new HashMap<>(properties.size());
71 Enumeration<String> keys = properties.keys();
72 while (keys.hasMoreElements()) {
73 String key = keys.nextElement();
74 res.put(key, properties.get(key).toString());
75 }
76 return res;
77 }
78
79 /** Converts a {@link Dictionary} to a {@link Map}. */
80 public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
81 if (properties == null) {
82 return null;
83 }
84 Map<String, Object> res = new HashMap<>(properties.size());
85 Enumeration<String> keys = properties.keys();
86 while (keys.hasMoreElements()) {
87 String key = keys.nextElement();
88 res.put(key, properties.get(key));
89 }
90 return res;
91 }
92
93 /**
94 * Get a string property from this map, expecting to find it, or
95 * <code>null</code> if not found.
96 */
97 public static String get(Map<String, ?> map, String key) {
98 Object res = map.get(key);
99 if (res == null)
100 return null;
101 return res.toString();
102 }
103
104 /**
105 * Get a string property from this map, expecting to find it.
106 *
107 * @throws IllegalArgumentException if the key was not found
108 */
109 public static String getNotNull(Map<String, ?> map, String key) {
110 Object res = map.get(key);
111 if (res == null)
112 throw new IllegalArgumentException("Map " + map + " should contain key " + key);
113 return res.toString();
114 }
115
116 /**
117 * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
118 */
119 public static Iterable<String> keys(Dictionary<String, ?> props) {
120 assert props != null;
121 return new DictionaryKeys(props);
122 }
123
124 static String toJson(Dictionary<String, ?> props) {
125 return toJson(props, false);
126 }
127
128 static String toJson(Dictionary<String, ?> props, boolean pretty) {
129 StringBuilder sb = new StringBuilder();
130 sb.append('{');
131 if (pretty)
132 sb.append('\n');
133 Enumeration<String> keys = props.keys();
134 while (keys.hasMoreElements()) {
135 String key = keys.nextElement();
136 if (pretty)
137 sb.append(' ');
138 sb.append('\"').append(key).append('\"');
139 if (pretty)
140 sb.append(" : ");
141 else
142 sb.append(':');
143 sb.append('\"').append(props.get(key)).append('\"');
144 if (keys.hasMoreElements())
145 sb.append(", ");
146 if (pretty)
147 sb.append('\n');
148 }
149 sb.append('}');
150 return sb.toString();
151 }
152
153 static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
154 if (props == null)
155 throw new IllegalArgumentException("Props cannot be null");
156 Properties toStore = new Properties();
157 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
158 String key = keys.nextElement();
159 toStore.setProperty(key, props.get(key).toString());
160 }
161 try (OutputStream out = Files.newOutputStream(path)) {
162 toStore.store(out, null);
163 }
164 }
165
166 static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
167 throws IOException {
168 if (props == null)
169 throw new IllegalArgumentException("Props cannot be null");
170 Object dnValue = props.get(dnKey);
171 String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
172 LdapName dn;
173 try {
174 dn = new LdapName(dnStr);
175 } catch (InvalidNameException e) {
176 throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
177 }
178 if (dnValue == null)
179 throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
180 try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
181 writer.append("\ndn: ");
182 writer.append(dn.toString());
183 writer.append('\n');
184 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
185 String key = keys.nextElement();
186 Object value = props.get(key);
187 writer.append(key);
188 writer.append(": ");
189 // FIXME deal with binary and multiple values
190 writer.append(value.toString());
191 writer.append('\n');
192 }
193 }
194 }
195
196 static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
197 Properties toLoad = new Properties();
198 try (InputStream in = Files.newInputStream(path)) {
199 toLoad.load(in);
200 }
201 Dictionary<String, Object> res = new Hashtable<String, Object>();
202 for (Object key : toLoad.keySet())
203 res.put(key.toString(), toLoad.get(key));
204 return res;
205 }
206
207 /*
208 * COLLECTIONS
209 */
210 /**
211 * Convert a comma-separated separated {@link String} or a {@link String} array
212 * to a {@link List} of {@link String}, trimming them. Useful to quickly
213 * interpret OSGi services properties.
214 *
215 * @return a {@link List} containing the trimmed {@link String}s, or an empty
216 * {@link List} if the argument was <code>null</code>.
217 */
218 public static List<String> toStringList(Object value) {
219 List<String> values = new ArrayList<>();
220 if (value == null)
221 return values;
222 String[] arr;
223 if (value instanceof String) {
224 arr = ((String) value).split(",");
225 } else if (value instanceof String[]) {
226 arr = (String[]) value;
227 } else {
228 throw new IllegalArgumentException("Unsupported value type " + value.getClass());
229 }
230 for (String str : arr) {
231 values.add(str.trim());
232 }
233 return values;
234 }
235
236 /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */
237 public static int size(Iterable<?> iterable) {
238 if (iterable instanceof Collection)
239 return ((Collection<?>) iterable).size();
240
241 int size = 0;
242 for (Iterator<?> it = iterable.iterator(); it.hasNext(); size++)
243 it.next();
244 return size;
245 }
246
247 public static <T> T getAt(Iterable<T> iterable, int index) {
248 if (iterable instanceof List) {
249 List<T> lst = ((List<T>) iterable);
250 if (index >= lst.size())
251 throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")");
252 return lst.get(index);
253 }
254 int i = 0;
255 for (Iterator<T> it = iterable.iterator(); it.hasNext(); i++) {
256 if (i == index)
257 return it.next();
258 else
259 it.next();
260 }
261 throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
262 }
263
264 public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
265 return sortByValue(map, false);
266 }
267
268 public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map, boolean descending) {
269 List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
270 list.sort(Entry.comparingByValue());
271 if (descending)
272 Collections.reverse(list);
273
274 Map<K, V> result = new LinkedHashMap<>();
275 for (Entry<K, V> entry : list) {
276 result.put(entry.getKey(), entry.getValue());
277 }
278
279 return result;
280 }
281
282 /*
283 * EXCEPTIONS
284 */
285 /**
286 * Chain the messages of all causes (one per line, <b>starts with a line
287 * return</b>) without all the stack
288 */
289 public static String chainCausesMessages(Throwable t) {
290 StringBuffer buf = new StringBuffer();
291 chainCauseMessage(buf, t);
292 return buf.toString();
293 }
294
295 /** Recursive chaining of messages */
296 private static void chainCauseMessage(StringBuffer buf, Throwable t) {
297 buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
298 if (t.getCause() != null)
299 chainCauseMessage(buf, t.getCause());
300 }
301
302 /*
303 * TIME
304 */
305 /** Formats time elapsed since start. */
306 public static String since(ZonedDateTime start) {
307 ZonedDateTime now = ZonedDateTime.now();
308 return duration(start, now);
309 }
310
311 /** Formats a duration. */
312 public static String duration(Temporal start, Temporal end) {
313 long count = ChronoUnit.DAYS.between(start, end);
314 if (count != 0)
315 return count > 1 ? count + " days" : count + " day";
316 count = ChronoUnit.HOURS.between(start, end);
317 if (count != 0)
318 return count > 1 ? count + " hours" : count + " hours";
319 count = ChronoUnit.MINUTES.between(start, end);
320 if (count != 0)
321 return count > 1 ? count + " minutes" : count + " minute";
322 count = ChronoUnit.SECONDS.between(start, end);
323 return count > 1 ? count + " seconds" : count + " second";
324 }
325
326 /*
327 * NON-API OSGi
328 */
329 /**
330 * Returns an array with the names of the provided classes. Useful when
331 * registering services with multiple interfaces in OSGi.
332 */
333 public static String[] names(Class<?>... clzz) {
334 String[] res = new String[clzz.length];
335 for (int i = 0; i < clzz.length; i++)
336 res[i] = clzz[i].getName();
337 return res;
338 }
339
340 /** Singleton constructor. */
341 private LangUtils() {
342
343 }
344
345 }