/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.misc;
import org.jgrapht.DirectedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
/**
* Misc. Stuff I found useful.
* <p/>
* TODO split functionalities?
*
* @author Benedikt Huber <benedikt.huber@gmail.com>
*/
public class MiscUtils {
public interface Query<Arg> {
boolean query(Arg a);
}
public interface F1<Arg, Ret> {
Ret apply(Arg v);
}
public static<A,R> F1<A,R> const1(final R c) {
return new F1<A, R>() {
@Override
public R apply(A v) {
return c;
}
};
}
public interface F2<Arg1, Arg2, Ret> {
Ret apply(Arg1 v1, Arg2 v2);
}
public static <K, V> void addToSet(Map<K, Set<V>> map, K key, V val) {
Set<V> set = map.get(key);
if (set == null) {
set = new HashSet<V>();
map.put(key, set);
}
set.add(val);
}
public static <K, V> void addToList(Map<K, List<V>> map, K key, V val) {
List<V> set = map.get(key);
if (set == null) {
set = new ArrayList<V>();
map.put(key, set);
}
set.add(val);
}
/**
* Increment the counter for the given key, inserting the default if the key if it is not present
* @param counters a dictionary of counters
* @param key the key to modify
* @param startValue the start value for the counter if the key is not present
* @return the new value for the key
*/
public static<T> long increment(Map<T, Long> counters, T key, long startValue) {
return incrementBy(counters, key, 1, startValue);
}
/**
* Increment the counter by step for the given key, inserting the default if the key if it is not present
* @param counters a dictionary of counters
* @param key the key to modify
* @param step the amount to increment
* @param startValue the start value for the counter if the key is not present
* @return the new value for the key
*/
public static<T> long incrementBy(Map<T, Long> counters, T key, long step, long startValue) {
long val;
if(! counters.containsKey(key)) {
val = startValue + step;
} else {
val = counters.get(key) + step;
}
counters.put(key, val);
return val;
}
public static <T> T[] concat(T val, T[] vals) {
List<T> v = new ArrayList<T>(vals.length+1);
v.add(val);
v.addAll(Arrays.asList(vals));
return v.toArray(vals);
}
public static <T> T[] concat(T[] vals, T val) {
T[] v = Arrays.copyOf(vals, vals.length+1);
v[vals.length] = val;
return v;
}
/**
* partially sort the given collection by inserting the elements into buckets,
* indexed by the given priority function.
*
* @param values
* @param priority
* @return
*/
public static <K, V>
TreeMap<K, List<V>> partialSort(
Collection<V> values, F1<V, K> priority) {
TreeMap<K, List<V>> buckets = new TreeMap<K, List<V>>();
for (V v : values) {
K prio = priority.apply(v);
List<V> bucket = buckets.get(prio);
if (bucket == null) {
bucket = new ArrayList<V>();
buckets.put(prio, bucket);
}
bucket.add(v);
}
return buckets;
}
public static int bytesToWords(int by) {
return ((by + 3) / 4);
}
/**
* @param strs A collection of objects to join
* @param sep A seperator string
* @return The concatenated sequence of the given strings, interspersed with the seperator.
*/
public static String joinStrings(Iterable<?> strs, String sep) {
StringBuilder b = new StringBuilder("");
Iterator<?> i = strs.iterator();
if (!i.hasNext()) return "";
b.append(i.next());
while (i.hasNext()) {
b.append(sep);
b.append(i.next());
}
return b.toString();
}
public static String joinStrings(Object[] entries, String sep) {
return joinStrings(Arrays.asList(entries), sep);
}
/**
* Remove problematic characters from a method name
* Note that fully qualified methods might become non-unique,
* so use an additional unique identifier if you need unique names.
*
* @param str
* @return
*/
public static String sanitizeFileName(String str) {
StringBuffer sanitized = new StringBuffer(str.length());
for (int i = 0; i < str.length(); i++) {
if (Character.isLetterOrDigit(str.charAt(i)) || str.charAt(i) == '.') {
sanitized.append(str.charAt(i));
} else {
sanitized.append('_');
}
}
return sanitized.toString();
}
/**
* Escape non-alpha numeric characters
* q -> qq
* . -> qd
* , -> qD
* / -> qs
* \ -> qS
* ' ' -> _
* _ -> q_
* ( -> qp
* ) -> qP
* x -> q$(chr x)
*
* @param s string to encode
* @return encoded string
*/
public static String qEncode(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case 'q':
sb.append("qq");
break;
case '.':
sb.append("qd");
break;
case ',':
sb.append("qD");
break;
case '/':
sb.append("qs");
break;
case '\\':
sb.append("qS");
break;
case ' ':
sb.append("_");
break;
case '_':
sb.append("q_");
break;
case '(':
sb.append("qp");
break;
case ')':
sb.append("qP");
break;
default:
if (Character.isJavaIdentifierPart(c)) {
sb.append(c);
} else {
sb.append('q');
sb.append((int) (c));
}
break;
}
}
return sb.toString();
}
/**
* Pretty print the given map
*
* @param out out stream
* @param map the map to print
* @param fill minimal length of the key, filled with whitespace
*/
public static <K, V>
void printMap(PrintStream out, Map<K, V> map, int fill, int indent) {
final String formatString = "%"+((indent>0) ? indent:"")+"s"+"%" + ((fill>0) ? fill:"") + "s ==> %s";
printMap(out, map, new F2<K, V, String>() {
public String apply(K v1, V v2) {
return String.format(formatString, "", v1, v2);
}
});
}
public static <K, V>
void printMap(PrintStream out,
Map<? extends K, ? extends V> map,
F2<K, V, String> printer) {
for (Entry<? extends K, ? extends V> entry : map.entrySet()) {
out.println(printer.apply(entry.getKey(), entry.getValue()));
}
}
public static <V, E>
List<V> topologicalOrder(DirectedGraph<V, E> acyclicGraph) {
List<V> topoList = new ArrayList<V>();
if (acyclicGraph.vertexSet().size() > 0) {
TopologicalOrderIterator<V, E> topo = new TopologicalOrderIterator<V, E>(acyclicGraph);
while (topo.hasNext()) {
topoList.add(topo.next());
}
}
return topoList;
}
public static <V, E>
List<V> reverseTopologicalOrder(DirectedGraph<V, E> acyclicGraph) {
List<V> revTopo = topologicalOrder(acyclicGraph);
Collections.reverse(revTopo);
return revTopo;
}
/**
* Inserts spaces in front of a string.
*
* @param len the desired total length
* @param val the string
* @return the prepadded string
*/
public static String prepad(String val, int len) {
StringBuffer sb = new StringBuffer();
for (int i = len; i > val.length(); i--) {
sb.append(" ");
}
sb.append(val);
return sb.toString();
}
/**
* Inserts spaces behind a string.
*
* @param len the desired total length
* @param val the string
* @return the prepadded string
*/
public static String postpad(String val, int len) {
StringBuffer sb = new StringBuffer();
sb.append(val);
for (int i = len; i > val.length(); i--) {
sb.append(" ");
}
return sb.toString();
}
/**
* Return n repetitions of a string, which is usually a single character.
*
* @param val the string
* @param n the repetitions
* @return the repeated string
*/
public static String repeat(String val, int n) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; i++) {
sb.append(val);
}
return sb.toString();
}
/**
* @param entries a collection of things
* @param max maximum number of entries to print
* @return a string representation of this collection with up to max entries.
*/
public static String toString(Collection<?> entries, int max) {
StringBuffer sb = new StringBuffer("[");
int cnt = Math.min(entries.size(), max);
Iterator<?> it = entries.iterator();
for (int i = 0; i < cnt; i++) {
if (i > 0) sb.append(",");
sb.append(it.next().toString());
}
if (cnt < entries.size()) {
sb.append(",...");
}
sb.append("]");
return sb.toString();
}
public static <T> boolean inArray(T[] array, T element) {
for (T i : array) {
if (i == null) {
if (element == null) return true;
} else if (i.equals(element)) {
return true;
}
}
return false;
}
/**
* Returns the path of one File relative to another.
*
* @param target the target directory
* @param base the base directory
* @return target's path relative to the base directory
*/
public static File getRelativeFile(File target, File base)
{
// This method is from
// http://stackoverflow.com/questions/204784/how-to-construct-a-relative-path-in-java-from-two-absolute-paths-or-urls
String[] baseComponents;
String[] targetComponents;
try {
baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));
} catch (IOException e) {
throw new AppInfoError("Error resolving canonical path", e);
}
// skip common components
int index = 0;
for (; index < targetComponents.length && index < baseComponents.length; ++index)
{
if (!targetComponents[index].equals(baseComponents[index]))
break;
}
StringBuilder result = new StringBuilder();
if (index != baseComponents.length)
{
// backtrack to base directory
for (int i = index; i < baseComponents.length; ++i)
result.append("..").append(File.separator);
}
for (; index < targetComponents.length; ++index)
result.append(targetComponents[index]).append(File.separator);
if (!target.getPath().endsWith("/") && !target.getPath().endsWith("\\"))
{
// remove final path separator
result.delete(result.length() - File.separator.length(), result.length());
}
return new File(result.toString());
}
public static void serialize(File outFile, Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream(outFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public static Object deSerialize(File inFile) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(inFile);
ObjectInputStream ois = new ObjectInputStream(fis);
return ois.readObject();
}
/**
* Group a list of objects by one of their attributes, updating
* an existing map from keys to object lists
*
* @param <T> the type of the objects
* @param <A> the type of the object attribute
* @param getAttribute function to extract the attribute from objects
* @param groupMap an existing map from attributes to all objects with this attribute.
* if null, a new {@code HashMap} will be created.
* @param objects
* @return the updated map
*/
public static <T,A>
Map<A, List<T>> group(
F1<T, A> getAttribute,
Map<A, List<T>> groupMap,
Iterable<T> objects
) {
if(groupMap == null) groupMap = new HashMap<A, List<T>>();
for(T e : objects) {
addToList(groupMap, getAttribute.apply(e), e);
}
return groupMap;
}
}