package org.argeo.cms.util; 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.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; /** Utilities around Java basic features. */ public class LangUtils { /* * OBJECTS and STRINGS */ /** * Whether this {@link String} is null, empty, or only white * spaces. */ public static boolean isEmpty(String str) { return str == null || "".equals(str.strip()); } /* * DICTIONARY */ /** * 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 dict(String key, Object value) { assert key != null; Hashtable props = new Hashtable<>(); if (value != null) props.put(key, value); return props; } /** @deprecated Use {@link #dict(String, Object)} instead. */ @Deprecated public static Dictionary dico(String key, Object value) { return dict(key, value); } /** Converts a {@link Dictionary} to a {@link Map} of strings. */ public static Map dictToStringMap(Dictionary properties) { if (properties == null) { return null; } Map res = new HashMap<>(properties.size()); Enumeration keys = properties.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); res.put(key, properties.get(key).toString()); } return res; } /** Converts a {@link Dictionary} to a {@link Map}. */ public static Map dictToMap(Dictionary properties) { if (properties == null) { return null; } Map res = new HashMap<>(properties.size()); Enumeration keys = properties.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); res.put(key, properties.get(key)); } return res; } /** * Get a string property from this map, expecting to find it, or * null if not found. */ public static String get(Map 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 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}. */ public static Iterable keys(Dictionary props) { assert props != null; return new DictionaryKeys(props); } static String toJson(Dictionary props) { return toJson(props, false); } static String toJson(Dictionary props, boolean pretty) { StringBuilder sb = new StringBuilder(); sb.append('{'); if (pretty) sb.append('\n'); Enumeration keys = props.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); if (pretty) sb.append(' '); sb.append('\"').append(key).append('\"'); if (pretty) sb.append(" : "); else sb.append(':'); sb.append('\"').append(props.get(key)).append('\"'); if (keys.hasMoreElements()) sb.append(", "); if (pretty) sb.append('\n'); } sb.append('}'); return sb.toString(); } static void storeAsProperties(Dictionary props, Path path) throws IOException { if (props == null) throw new IllegalArgumentException("Props cannot be null"); Properties toStore = new Properties(); for (Enumeration keys = props.keys(); keys.hasMoreElements();) { String key = keys.nextElement(); toStore.setProperty(key, props.get(key).toString()); } try (OutputStream out = Files.newOutputStream(path)) { toStore.store(out, null); } } static void appendAsLdif(String dnBase, String dnKey, Dictionary 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 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 loadFromProperties(Path path) throws IOException { Properties toLoad = new Properties(); try (InputStream in = Files.newInputStream(path)) { toLoad.load(in); } Dictionary res = new Hashtable(); for (Object key : toLoad.keySet()) res.put(key.toString(), toLoad.get(key)); 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 null. */ public static List toStringList(Object value) { List 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; } /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */ public static int size(Iterable iterable) { if (iterable instanceof Collection) return ((Collection) iterable).size(); int size = 0; for (Iterator it = iterable.iterator(); it.hasNext(); size++) it.next(); return size; } public static T getAt(Iterable iterable, int index) { if (iterable instanceof List) { List lst = ((List) iterable); if (index >= lst.size()) throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")"); return lst.get(index); } int i = 0; for (Iterator it = iterable.iterator(); it.hasNext(); i++) { if (i == index) return it.next(); else it.next(); } throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")"); } public static > Map sortByValue(Map map) { return sortByValue(map, false); } public static > Map sortByValue(Map map, boolean descending) { List> list = new ArrayList<>(map.entrySet()); list.sort(Entry.comparingByValue()); if (descending) Collections.reverse(list); Map result = new LinkedHashMap<>(); for (Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } /* * EXCEPTIONS */ /** * Chain the messages of all causes (one per line, starts with a line * return) 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"; } /* * NON-API OSGi */ /** * Returns an array with the names of the provided classes. Useful when * registering services with multiple interfaces in OSGi. */ public static String[] names(Class... clzz) { String[] res = new String[clzz.length]; for (int i = 0; i < clzz.length; i++) res[i] = clzz[i].getName(); return res; } /** Singleton constructor. */ private LangUtils() { } }