]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/LangUtils.java
Improve ACR, introduce migration from JCR.
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / LangUtils.java
1 package org.argeo.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.Dictionary;
16 import java.util.Enumeration;
17 import java.util.HashMap;
18 import java.util.Hashtable;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Properties;
23
24 import javax.naming.InvalidNameException;
25 import javax.naming.ldap.LdapName;
26
27 /** Utilities around Java basic features. */
28 public class LangUtils {
29 /*
30 * NON-API OSGi
31 */
32 /**
33 * Returns an array with the names of the provided classes. Useful when
34 * registering services with multiple interfaces in OSGi.
35 */
36 public static String[] names(Class<?>... clzz) {
37 String[] res = new String[clzz.length];
38 for (int i = 0; i < clzz.length; i++)
39 res[i] = clzz[i].getName();
40 return res;
41 }
42
43 // /*
44 // * MAP
45 // */
46 // /**
47 // * Creates a new {@link Map} with one key-value pair. Key should not be null,
48 // * but if the value is null, it returns an empty {@link Map}.
49 // *
50 // * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead.
51 // */
52 // @Deprecated
53 // public static Map<String, Object> map(String key, Object value) {
54 // assert key != null;
55 // HashMap<String, Object> props = new HashMap<>();
56 // if (value != null)
57 // props.put(key, value);
58 // return props;
59 // }
60
61 /*
62 * DICTIONARY
63 */
64
65 /**
66 * Creates a new {@link Dictionary} with one key-value pair. Key should not be
67 * null, but if the value is null, it returns an empty {@link Dictionary}.
68 */
69 public static Dictionary<String, Object> dict(String key, Object value) {
70 assert key != null;
71 Hashtable<String, Object> props = new Hashtable<>();
72 if (value != null)
73 props.put(key, value);
74 return props;
75 }
76
77 /** @deprecated Use {@link #dict(String, Object)} instead. */
78 @Deprecated
79 public static Dictionary<String, Object> dico(String key, Object value) {
80 return dict(key, value);
81 }
82
83 /** Converts a {@link Dictionary} to a {@link Map} of strings. */
84 public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
85 if (properties == null) {
86 return null;
87 }
88 Map<String, String> res = new HashMap<>(properties.size());
89 Enumeration<String> keys = properties.keys();
90 while (keys.hasMoreElements()) {
91 String key = keys.nextElement();
92 res.put(key, properties.get(key).toString());
93 }
94 return res;
95 }
96
97 /** Converts a {@link Dictionary} to a {@link Map}. */
98 public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
99 if (properties == null) {
100 return null;
101 }
102 Map<String, Object> res = new HashMap<>(properties.size());
103 Enumeration<String> keys = properties.keys();
104 while (keys.hasMoreElements()) {
105 String key = keys.nextElement();
106 res.put(key, properties.get(key));
107 }
108 return res;
109 }
110
111 /**
112 * Get a string property from this map, expecting to find it, or
113 * <code>null</code> if not found.
114 */
115 public static String get(Map<String, ?> map, String key) {
116 Object res = map.get(key);
117 if (res == null)
118 return null;
119 return res.toString();
120 }
121
122 /**
123 * Get a string property from this map, expecting to find it.
124 *
125 * @throws IllegalArgumentException if the key was not found
126 */
127 public static String getNotNull(Map<String, ?> map, String key) {
128 Object res = map.get(key);
129 if (res == null)
130 throw new IllegalArgumentException("Map " + map + " should contain key " + key);
131 return res.toString();
132 }
133
134 /**
135 * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
136 */
137 public static Iterable<String> keys(Dictionary<String, ?> props) {
138 assert props != null;
139 return new DictionaryKeys(props);
140 }
141
142 static String toJson(Dictionary<String, ?> props) {
143 return toJson(props, false);
144 }
145
146 static String toJson(Dictionary<String, ?> props, boolean pretty) {
147 StringBuilder sb = new StringBuilder();
148 sb.append('{');
149 if (pretty)
150 sb.append('\n');
151 Enumeration<String> keys = props.keys();
152 while (keys.hasMoreElements()) {
153 String key = keys.nextElement();
154 if (pretty)
155 sb.append(' ');
156 sb.append('\"').append(key).append('\"');
157 if (pretty)
158 sb.append(" : ");
159 else
160 sb.append(':');
161 sb.append('\"').append(props.get(key)).append('\"');
162 if (keys.hasMoreElements())
163 sb.append(", ");
164 if (pretty)
165 sb.append('\n');
166 }
167 sb.append('}');
168 return sb.toString();
169 }
170
171 static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
172 if (props == null)
173 throw new IllegalArgumentException("Props cannot be null");
174 Properties toStore = new Properties();
175 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
176 String key = keys.nextElement();
177 toStore.setProperty(key, props.get(key).toString());
178 }
179 try (OutputStream out = Files.newOutputStream(path)) {
180 toStore.store(out, null);
181 }
182 }
183
184 static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
185 throws IOException {
186 if (props == null)
187 throw new IllegalArgumentException("Props cannot be null");
188 Object dnValue = props.get(dnKey);
189 String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
190 LdapName dn;
191 try {
192 dn = new LdapName(dnStr);
193 } catch (InvalidNameException e) {
194 throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
195 }
196 if (dnValue == null)
197 throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
198 try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
199 writer.append("\ndn: ");
200 writer.append(dn.toString());
201 writer.append('\n');
202 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
203 String key = keys.nextElement();
204 Object value = props.get(key);
205 writer.append(key);
206 writer.append(": ");
207 // FIXME deal with binary and multiple values
208 writer.append(value.toString());
209 writer.append('\n');
210 }
211 }
212 }
213
214 static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
215 Properties toLoad = new Properties();
216 try (InputStream in = Files.newInputStream(path)) {
217 toLoad.load(in);
218 }
219 Dictionary<String, Object> res = new Hashtable<String, Object>();
220 for (Object key : toLoad.keySet())
221 res.put(key.toString(), toLoad.get(key));
222 return res;
223 }
224
225 /*
226 * COLLECTIONS
227 */
228 /**
229 * Convert a comma-separated separated {@link String} or a {@link String} array
230 * to a {@link List} of {@link String}, trimming them. Useful to quickly
231 * interpret OSGi services properties.
232 *
233 * @return a {@link List} containing the trimmed {@link String}s, or an empty
234 * {@link List} if the argument was <code>null</code>.
235 */
236 public static List<String> toStringList(Object value) {
237 List<String> values = new ArrayList<>();
238 if (value == null)
239 return values;
240 String[] arr;
241 if (value instanceof String) {
242 arr = ((String) value).split(",");
243 } else if (value instanceof String[]) {
244 arr = (String[]) value;
245 } else {
246 throw new IllegalArgumentException("Unsupported value type " + value.getClass());
247 }
248 for (String str : arr) {
249 values.add(str.trim());
250 }
251 return values;
252 }
253
254 /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */
255 public static int size(Iterable<?> iterable) {
256 if (iterable instanceof Collection)
257 return ((Collection<?>) iterable).size();
258
259 int size = 0;
260 for (Iterator<?> it = iterable.iterator(); it.hasNext(); size++)
261 it.next();
262 return size;
263 }
264
265 public static <T> T getAt(Iterable<T> iterable, int index) {
266 if (iterable instanceof List) {
267 List<T> lst = ((List<T>) iterable);
268 if (index >= lst.size())
269 throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")");
270 return lst.get(index);
271 }
272 int i = 0;
273 for (Iterator<T> it = iterable.iterator(); it.hasNext(); i++) {
274 if (i == index)
275 return it.next();
276 else
277 it.next();
278 }
279 throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
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 /** Singleton constructor. */
327 private LangUtils() {
328
329 }
330
331 }