]> git.argeo.org Git - lgpl/argeo-commons.git/blob - util/LangUtils.java
Prepare next development cycle
[lgpl/argeo-commons.git] / 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 /**
98 * Get a string property from this map, expecting to find it, or
99 * <code>null</code> if not found.
100 */
101 public static String get(Map<String, ?> map, String key) {
102 Object res = map.get(key);
103 if (res == null)
104 return null;
105 return res.toString();
106 }
107
108 /**
109 * Get a string property from this map, expecting to find it.
110 *
111 * @throws IllegalArgumentException if the key was not found
112 */
113 public static String getNotNull(Map<String, ?> map, String key) {
114 Object res = map.get(key);
115 if (res == null)
116 throw new IllegalArgumentException("Map " + map + " should contain key " + key);
117 return res.toString();
118 }
119
120 /**
121 * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
122 */
123 public static Iterable<String> keys(Dictionary<String, ?> props) {
124 assert props != null;
125 return new DictionaryKeys(props);
126 }
127
128 static String toJson(Dictionary<String, ?> props) {
129 return toJson(props, false);
130 }
131
132 static String toJson(Dictionary<String, ?> props, boolean pretty) {
133 StringBuilder sb = new StringBuilder();
134 sb.append('{');
135 if (pretty)
136 sb.append('\n');
137 Enumeration<String> keys = props.keys();
138 while (keys.hasMoreElements()) {
139 String key = keys.nextElement();
140 if (pretty)
141 sb.append(' ');
142 sb.append('\"').append(key).append('\"');
143 if (pretty)
144 sb.append(" : ");
145 else
146 sb.append(':');
147 sb.append('\"').append(props.get(key)).append('\"');
148 if (keys.hasMoreElements())
149 sb.append(", ");
150 if (pretty)
151 sb.append('\n');
152 }
153 sb.append('}');
154 return sb.toString();
155 }
156
157 static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
158 if (props == null)
159 throw new IllegalArgumentException("Props cannot be null");
160 Properties toStore = new Properties();
161 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
162 String key = keys.nextElement();
163 toStore.setProperty(key, props.get(key).toString());
164 }
165 try (OutputStream out = Files.newOutputStream(path)) {
166 toStore.store(out, null);
167 }
168 }
169
170 static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
171 throws IOException {
172 if (props == null)
173 throw new IllegalArgumentException("Props cannot be null");
174 Object dnValue = props.get(dnKey);
175 String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
176 LdapName dn;
177 try {
178 dn = new LdapName(dnStr);
179 } catch (InvalidNameException e) {
180 throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
181 }
182 if (dnValue == null)
183 throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
184 try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
185 writer.append("\ndn: ");
186 writer.append(dn.toString());
187 writer.append('\n');
188 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
189 String key = keys.nextElement();
190 Object value = props.get(key);
191 writer.append(key);
192 writer.append(": ");
193 // FIXME deal with binary and multiple values
194 writer.append(value.toString());
195 writer.append('\n');
196 }
197 }
198 }
199
200 static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
201 Properties toLoad = new Properties();
202 try (InputStream in = Files.newInputStream(path)) {
203 toLoad.load(in);
204 }
205 Dictionary<String, Object> res = new Hashtable<String, Object>();
206 for (Object key : toLoad.keySet())
207 res.put(key.toString(), toLoad.get(key));
208 return res;
209 }
210
211 /*
212 * COLLECTIONS
213 */
214 /**
215 * Convert a comma-separated separated {@link String} or a {@link String} array
216 * to a {@link List} of {@link String}, trimming them. Useful to quickly
217 * interpret OSGi services properties.
218 *
219 * @return a {@link List} containing the trimmed {@link String}s, or an empty
220 * {@link List} if the argument was <code>null</code>.
221 */
222 public static List<String> toStringList(Object value) {
223 List<String> values = new ArrayList<>();
224 if (value == null)
225 return values;
226 String[] arr;
227 if (value instanceof String) {
228 arr = ((String) value).split(",");
229 } else if (value instanceof String[]) {
230 arr = (String[]) value;
231 } else {
232 throw new IllegalArgumentException("Unsupported value type " + value.getClass());
233 }
234 for (String str : arr) {
235 values.add(str.trim());
236 }
237 return values;
238 }
239
240 /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */
241 public static int size(Iterable<?> iterable) {
242 if (iterable instanceof Collection)
243 return ((Collection<?>) iterable).size();
244
245 int size = 0;
246 for (Iterator<?> it = iterable.iterator(); it.hasNext(); size++)
247 it.next();
248 return size;
249 }
250
251 public static <T> T getAt(Iterable<T> iterable, int index) {
252 if (iterable instanceof List) {
253 List<T> lst = ((List<T>) iterable);
254 if (index >= lst.size())
255 throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")");
256 return lst.get(index);
257 }
258 int i = 0;
259 for (Iterator<T> it = iterable.iterator(); it.hasNext(); i++) {
260 if (i == index)
261 return it.next();
262 else
263 it.next();
264 }
265 throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
266 }
267
268 /*
269 * EXCEPTIONS
270 */
271 /**
272 * Chain the messages of all causes (one per line, <b>starts with a line
273 * return</b>) without all the stack
274 */
275 public static String chainCausesMessages(Throwable t) {
276 StringBuffer buf = new StringBuffer();
277 chainCauseMessage(buf, t);
278 return buf.toString();
279 }
280
281 /** Recursive chaining of messages */
282 private static void chainCauseMessage(StringBuffer buf, Throwable t) {
283 buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
284 if (t.getCause() != null)
285 chainCauseMessage(buf, t.getCause());
286 }
287
288 /*
289 * TIME
290 */
291 /** Formats time elapsed since start. */
292 public static String since(ZonedDateTime start) {
293 ZonedDateTime now = ZonedDateTime.now();
294 return duration(start, now);
295 }
296
297 /** Formats a duration. */
298 public static String duration(Temporal start, Temporal end) {
299 long count = ChronoUnit.DAYS.between(start, end);
300 if (count != 0)
301 return count > 1 ? count + " days" : count + " day";
302 count = ChronoUnit.HOURS.between(start, end);
303 if (count != 0)
304 return count > 1 ? count + " hours" : count + " hours";
305 count = ChronoUnit.MINUTES.between(start, end);
306 if (count != 0)
307 return count > 1 ? count + " minutes" : count + " minute";
308 count = ChronoUnit.SECONDS.between(start, end);
309 return count > 1 ? count + " seconds" : count + " second";
310 }
311
312 /** Singleton constructor. */
313 private LangUtils() {
314
315 }
316
317 }