package util; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.MessageFormat; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import data.MultipleHMKeys; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import data.Settings; import gui.GUIController; import gui.ValidationUtil; public class Util { public final static Logger logger = LogManager.getLogger(Util.class); public static String toReadableTime(long time) { long hours = time(TimeUnit.HOURS, time); long minutes = time(TimeUnit.MINUTES, time) - TimeUnit.HOURS.toMinutes(hours); long seconds = time(TimeUnit.SECONDS, time) - TimeUnit.HOURS.toSeconds(hours) - TimeUnit.MINUTES.toSeconds(minutes); long milliseconds = time(TimeUnit.MILLISECONDS, time) - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes) - TimeUnit.SECONDS.toMillis(seconds); long microseconds = time(TimeUnit.MICROSECONDS, time) - TimeUnit.HOURS.toMicros(hours) - TimeUnit.MINUTES.toMicros(minutes) - TimeUnit.SECONDS.toMicros(seconds) - TimeUnit.MILLISECONDS.toMicros(milliseconds); long nanoseconds = time(TimeUnit.NANOSECONDS, time) - TimeUnit.HOURS.toNanos(hours) - TimeUnit.MINUTES.toNanos(minutes) - TimeUnit.SECONDS.toNanos(seconds) - TimeUnit.MILLISECONDS.toNanos(milliseconds) - TimeUnit.MICROSECONDS.toNanos(microseconds); return String.format("%d h, %d min, %d s, %d ms, %d µs, %d ns", hours, minutes, seconds, milliseconds, microseconds, nanoseconds); } private static long time(TimeUnit unit, long t) { return unit.convert(t, TimeUnit.NANOSECONDS); } /** * Converts a number to a more readable format. * 12345 -> 12.345 * 12345,678 -> 12.345,67 * * @param o byte, double, float, int,long, short * * @return number formatted with thousands separator and 2 decimal places (floats) */ private static String formatNumberReadable(Object o) { if (isInstanceOfInteger(o)) return String.format("%,d", o); else if (isInstanceOfFloat(o)) return String.format("%,.2f", o); else return "- invalid input format -"; } public static String formatNumberAsPercent(Object o, String punctuation) { if(punctuation.equals("punctuation.COMMA")) { return MessageFormat.format("{0,number,#.### %}", o).replace('.', ','); } else { return MessageFormat.format("{0,number,#.### %}", o); } } public static String formatNumberForExport(Object o, String punctuation) { if(punctuation.equals("punctuation.COMMA")) { return MessageFormat.format("{0,number,#.##}", o).replace('.', ','); } else { return MessageFormat.format("{0,number,#.##}", o); } } public static String formatNumberForLongExport(Object o, String punctuation) { if(punctuation.equals("punctuation.COMMA")) { return MessageFormat.format("{0,number,#.########}", o).replace('.', ','); } else { return MessageFormat.format("{0,number,#.########}", o); } } private static boolean isInstanceOfInteger(Object o) { Set> types = new HashSet<>(); types.add(Byte.class); types.add(Short.class); types.add(Integer.class); types.add(Long.class); return types.contains(o.getClass()); } private static boolean isInstanceOfFloat(Object o) { Set> types = new HashSet<>(); types.add(Float.class); types.add(Double.class); return types.contains(o.getClass()); } public static void printMap(Map map) { System.out.println("\nkey: value"); map.forEach((k, v) -> System.out.print(String.format("%s:\t %,8d%n", k, v))); System.out.println(); } /** * Generic map converter -> since AtomicLongs aren't as comparable. * Converts ConcurrentHashMap to HashMap */ public static Map atomicInt2StringAndInt(Map map) { Map m = new HashMap(); for (Map.Entry e : map.entrySet()) { m.put(e.getKey(), ((AtomicLong) e.getValue()).longValue()); } return m; } public class ValueThenKeyComparator, V extends Comparable> implements Comparator> { public int compare(Map.Entry a, Map.Entry b) { int cmp1 = a.getValue().compareTo(b.getValue()); if (cmp1 != 0) { return cmp1; } else { return a.getKey().compareTo(b.getKey()); } } } /** * Sorts a map in a descending order by value. */ public static > Map sortByValue(Map map, int limit) { /* sorted() in itself is O(1), since it's an intermediate operation that doesn't consume the stream, but simply adds an operation to the pipeline. Once the stream is consumed by a terminal operation, the sort happens and either - it doesn't do anything (O(1)) because the stream knows that the elements are already sorted (because they come from a SortedSet, for example) - or the stream is not parallel, and it delegates to Arrays.sort() (O(n log n)) - or the stream is parallel, and it delegates to Arrays.parallelSort() (O(n log n)) As of JDK 8, the main sorting algorithm which is also used in standard stream API implementation for sequential sorting is TimSort. Its worst case is O(n log n), but it works incredibly fast (with O(n) and quite small constant) if data is presorted (in forward or reverse direction) or partially presorted (for example, if you concatenate two sorted lists and sort them again). */ // if limit is set to 0 or less, we take that to mean no limit at all if (limit <= 0) { limit = map.size(); } TimeWatch watch = TimeWatch.start(); // sort by alphabet Map alphaSorted = new LinkedHashMap<>(); map.entrySet().stream().sorted((a, b) -> ((MultipleHMKeys)a.getKey()).compareTo((MultipleHMKeys)b.getKey())).limit(limit) .forEachOrdered(e -> alphaSorted.put(e.getKey(), e.getValue())); Map result = new LinkedHashMap<>(); alphaSorted.entrySet().stream().sorted((a, b) -> (int) ((Long) b.getValue() - (Long) a.getValue())).limit(limit) .forEachOrdered(e -> result.put(e.getKey(), e.getValue())); if (Settings.PRINT_LOG) { System.out.println(String.format("Elapsed time for sorting %s items: %s", formatNumberReadable(result.size()), watch.toFullTime())); } return result; } public static void printMap(Map map, String title, int number_of_words) { System.out.println(String.format("\n%s\n------------\nkey: value\tpercent", title)); map.forEach((k, v) -> System.out.println(String.format("%s:\t %s\t %s%%", k, Util.formatNumberReadable(v), Util.formatNumberReadable((double) v / number_of_words * 100)))); System.out.println(); } static long mapSumFrequencies(Map map) { long sum = 0; for (long value : map.values()) { sum += value; } return sum; } /** * Used for passing optional integer values for sorting. */ public static int getValidInt(int... i) { if (i == null || i.length < 1 || i[0] <= 0) { return 0; } else { return i[0]; } } /** * Check whether a map is empty. It also considers an edge case where map's keys are lists to check if those lists are empty. */ public static boolean isMapEmpty(Map map) { if (map.isEmpty()) { // default return true; } // otherwise check if keys map to values that are empty for (V v : map.values()) { // todo: generalize to all collections if/when needed ArrayList vl = new ArrayList((List) v); if (!vl.isEmpty()) { return false; } } return true; } /** * Returns the location of the main class if possible, otherwise null */ public static File getWorkingDirectory() { // get location of the currently executing class String path = GUIController.class.getProtectionDomain().getCodeSource().getLocation().getPath(); logger.info("working dir path: ", path); String decodedPath = null; try { decodedPath = URLDecoder.decode(path, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.error("decoding: ", e); // e.printStackTrace(); } if (decodedPath != null) { File workingDirectory = new File(decodedPath); // in case it's a file (class is packaged inside a jar), select its parent folder workingDirectory = workingDirectory.isFile() ? workingDirectory.getParentFile() : workingDirectory; if (ValidationUtil.isReadableDirectory(workingDirectory)) { logger.info("working dir is ok: ", workingDirectory.getAbsolutePath()); return workingDirectory; } } logger.info("working dir returing null"); return null; } }