Multiple user referentials working with IPA.
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / LangUtils.java
index 8810d646335056a604791cec8a545b27931be1b9..1aee28c03c776d7438cc266e4842dee7e21af671 100644 (file)
@@ -7,14 +7,24 @@ 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.Dictionary;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Iterator;
+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
@@ -30,22 +40,97 @@ public class LangUtils {
                return res;
        }
 
+//     /*
+//      * MAP
+//      */
+//     /**
+//      * Creates a new {@link Map} with one key-value pair. Key should not be null,
+//      * but if the value is null, it returns an empty {@link Map}.
+//      * 
+//      * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead.
+//      */
+//     @Deprecated
+//     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;
+       }
+
+       /** Converts a {@link Dictionary} to a {@link Map}. */
+       public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
+               if (properties == null) {
+                       return null;
+               }
+               Map<String, Object> res = new HashMap<>(properties.size());
+               Enumeration<String> 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
+        * <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}.
         */
@@ -54,11 +139,11 @@ public class LangUtils {
                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)
@@ -83,7 +168,7 @@ public class LangUtils {
                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();
@@ -96,7 +181,7 @@ public class LangUtils {
                }
        }
 
-       public static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
+       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");
@@ -126,7 +211,7 @@ public class LangUtils {
                }
        }
 
-       public static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
+       static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
                Properties toLoad = new Properties();
                try (InputStream in = Files.newInputStream(path)) {
                        toLoad.load(in);
@@ -136,7 +221,64 @@ public class LangUtils {
                        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 <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;
+       }
+
+       /** 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> T getAt(Iterable<T> iterable, int index) {
+               if (iterable instanceof List) {
+                       List<T> lst = ((List<T>) 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<T> 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 + ")");
+       }
+
        /*
         * EXCEPTIONS
         */
@@ -152,12 +294,35 @@ public class LangUtils {
 
        /** Recursive chaining of messages */
        private static void chainCauseMessage(StringBuffer buf, Throwable t) {
-               buf.append('\n').append(' ').append(t.getClass().getCanonicalName())
-                               .append(": ").append(t.getMessage());
+               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() {