import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Hashtable;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Utilities around Java basic features. */
public class LangUtils {
/*
* NON-API OSGi
return res;
}
+ /*
+ * MAP
+ */
+ /**
+ * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+ * null, but if the value is null, it returns an empty {@link Dictionary}.
+ */
+ public static Map<String, Object> map(String key, Object value) {
+ assert key != null;
+ HashMap<String, Object> props = new HashMap<>();
+ if (value != null)
+ props.put(key, value);
+ return props;
+ }
+
/*
* DICTIONARY
*/
/**
- * Creates a new {@link Dictionary} with one key-value pair (neither key not
- * value should be null)
+ * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+ * null, but if the value is null, it returns an empty {@link Dictionary}.
*/
- public static Dictionary<String, Object> init(String key, Object value) {
+ public static Dictionary<String, Object> dict(String key, Object value) {
assert key != null;
- assert value != null;
Hashtable<String, Object> props = new Hashtable<>();
- props.put(key, value);
+ if (value != null)
+ props.put(key, value);
return props;
}
+ /** @deprecated Use {@link #dict(String, Object)} instead. */
+ @Deprecated
+ public static Dictionary<String, Object> dico(String key, Object value) {
+ return dict(key, value);
+ }
+
+ /** Converts a {@link Dictionary} to a {@link Map} of strings. */
+ public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
+ if (properties == null) {
+ return null;
+ }
+ Map<String, String> res = new HashMap<>(properties.size());
+ Enumeration<String> keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ res.put(key, properties.get(key).toString());
+ }
+ return res;
+ }
+
+ /**
+ * Get a string property from this map, expecting to find it, or
+ * <code>null</code> if not found.
+ */
+ public static String get(Map<String, ?> map, String key) {
+ Object res = map.get(key);
+ if (res == null)
+ return null;
+ return res.toString();
+ }
+
+ /**
+ * Get a string property from this map, expecting to find it.
+ *
+ * @throws IllegalArgumentException if the key was not found
+ */
+ public static String getNotNull(Map<String, ?> map, String key) {
+ Object res = map.get(key);
+ if (res == null)
+ throw new IllegalArgumentException("Map " + map + " should contain key " + key);
+ return res.toString();
+ }
+
/**
* Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
*/
return new DictionaryKeys(props);
}
- public static String toJson(Dictionary<String, ?> props) {
+ static String toJson(Dictionary<String, ?> props) {
return toJson(props, false);
}
- public static String toJson(Dictionary<String, ?> props, boolean pretty) {
+ static String toJson(Dictionary<String, ?> props, boolean pretty) {
StringBuilder sb = new StringBuilder();
sb.append('{');
if (pretty)
return sb.toString();
}
- public static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
+ static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
if (props == null)
throw new IllegalArgumentException("Props cannot be null");
Properties toStore = new Properties();
}
}
- public static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
+ static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
+ throws IOException {
+ if (props == null)
+ throw new IllegalArgumentException("Props cannot be null");
+ Object dnValue = props.get(dnKey);
+ String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
+ LdapName dn;
+ try {
+ dn = new LdapName(dnStr);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
+ }
+ if (dnValue == null)
+ throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
+ try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+ writer.append("\ndn: ");
+ writer.append(dn.toString());
+ writer.append('\n');
+ for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ Object value = props.get(key);
+ writer.append(key);
+ writer.append(": ");
+ // FIXME deal with binary and multiple values
+ writer.append(value.toString());
+ writer.append('\n');
+ }
+ }
+ }
+
+ static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
Properties toLoad = new Properties();
try (InputStream in = Files.newInputStream(path)) {
toLoad.load(in);
return res;
}
+ /*
+ * COLLECTIONS
+ */
+ /**
+ * Convert a comma-separated separated {@link String} or a {@link String} array
+ * to a {@link List} of {@link String}, trimming them. Useful to quickly
+ * interpret OSGi services properties.
+ *
+ * @return a {@link List} containing the trimmed {@link String}s, or an empty
+ * {@link List} if the argument was <code>null</code>.
+ */
+ public static List<String> toStringList(Object value) {
+ List<String> values = new ArrayList<>();
+ if (value == null)
+ return values;
+ String[] arr;
+ if (value instanceof String) {
+ arr = ((String) value).split(",");
+ } else if (value instanceof String[]) {
+ arr = (String[]) value;
+ } else {
+ throw new IllegalArgumentException("Unsupported value type " + value.getClass());
+ }
+ for (String str : arr) {
+ values.add(str.trim());
+ }
+ return values;
+ }
+
+ /*
+ * EXCEPTIONS
+ */
+ /**
+ * Chain the messages of all causes (one per line, <b>starts with a line
+ * return</b>) without all the stack
+ */
+ public static String chainCausesMessages(Throwable t) {
+ StringBuffer buf = new StringBuffer();
+ chainCauseMessage(buf, t);
+ return buf.toString();
+ }
+
+ /** Recursive chaining of messages */
+ private static void chainCauseMessage(StringBuffer buf, Throwable t) {
+ buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
+ if (t.getCause() != null)
+ chainCauseMessage(buf, t.getCause());
+ }
+
+ /*
+ * TIME
+ */
+ /** Formats time elapsed since start. */
+ public static String since(ZonedDateTime start) {
+ ZonedDateTime now = ZonedDateTime.now();
+ return duration(start, now);
+ }
+
+ /** Formats a duration. */
+ public static String duration(Temporal start, Temporal end) {
+ long count = ChronoUnit.DAYS.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " days" : count + " day";
+ count = ChronoUnit.HOURS.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " hours" : count + " hours";
+ count = ChronoUnit.MINUTES.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " minutes" : count + " minute";
+ count = ChronoUnit.SECONDS.between(start, end);
+ return count > 1 ? count + " seconds" : count + " second";
+ }
+
/** Singleton constructor. */
private LangUtils() {