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