You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
8.7 KiB

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<Class<?>> 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<Class<?>> types = new HashSet<>();
types.add(Float.class);
types.add(Double.class);
return types.contains(o.getClass());
}
public static <K, V> void printMap(Map<K, V> 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<K, AtomicLong> to HashMap<K, Long>
*/
public static <K, V> Map<MultipleHMKeys, Long> atomicInt2StringAndInt(Map<K, V> map) {
Map m = new HashMap<MultipleHMKeys, Long>();
for (Map.Entry<K, V> e : map.entrySet()) {
m.put(e.getKey(), ((AtomicLong) e.getValue()).longValue());
}
return m;
}
public class ValueThenKeyComparator<K extends Comparable<? super K>,
V extends Comparable<? super V>>
implements Comparator<Map.Entry<K, V>> {
public int compare(Map.Entry<K, V> a, Map.Entry<K, V> 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 <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> 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<K, V> 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<K, V> 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 <K, V> void printMap(Map<K, Integer> 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<MultipleHMKeys, Long> 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 <K, V> boolean isMapEmpty(Map<K, V> 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<String> vl = new ArrayList((List<String>) 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;
}
}