package net.sourceforge.seqware.common.util.maptools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.DatatypeConverter;
import net.sourceforge.seqware.common.model.WorkflowRunParam;
import net.sourceforge.seqware.common.util.Log;
import static net.sourceforge.seqware.common.util.Rethrow.rethrow;
import org.apache.commons.io.IOUtils;
/**
* <p>
* MapTools class.
* </p>
*
* @author boconnor
* @version $Id: $Id
*/
public class MapTools {
/**
* <p>
* ini2Map.
* </p>
*
* @param iniFile
* a {@link java.lang.String} object.
* @param hm
* a {@link java.util.Map} object.
*/
public static void ini2Map(String iniFile, Map<String, String> hm) {
ini2Map(iniFile, hm, false);
}
/**
* This is a little different than the ini2Map since it allows us to read ini files where key-value annotations on the key-values appear
* in comments on the previous line above the current such as:
*
* key=bam_inputs:type=file:display=T:display_name=BAM Input(s):file_meta_type=application/bam
*
* or
*
* key=run_ends:type=pulldown:display=T:display_name=Single or Paired Ends:pulldown_items=Single End|1;Paired End|2
*
* The extra information is used by the Portal and Web Service to display interfaces that let people call the workflows correctly. This
* code doesn't know about the contents of the metadata key-values other than they are ":" delimited with key=value.
*
* @param iniFile
* a {@link java.lang.String} object.
* @param hm
* a HashMap to be filled with key and details as key-values in a nested HashMap.
*/
public static void ini2RichMap(String iniFile, Map<String, Map<String, String>> hm) {
HashMap<String, String> detailsMap = null;
try {
BufferedReader br = new BufferedReader(new FileReader(new File(iniFile)));
String line;
while ((line = br.readLine()) != null) {
// this deals with key value annotations
if (line.startsWith("#") && line.matches("^#\\s*key=.*$")) {
detailsMap = new HashMap<>();
line = line.replaceAll("#\\s*", "");
String[] kvs = line.split(":");
for (String pair : kvs) {
String[] kv = pair.split("=");
if (kv.length == 2)
detailsMap.put(kv[0], kv[1]);
else
detailsMap.put(kv[0], "");
}
// this deals with keys
} else if (isLineMatchesKeyValue(line)) {
String[] kv = line.split("\\s*=\\s*");
if (detailsMap == null || !kv[0].equals(detailsMap.get("key"))) {
detailsMap = new HashMap<>();
detailsMap.put("key", kv[0]);
}
if (kv.length == 1) {
detailsMap.put("default_value", "");
} else {
detailsMap.put("default_value", kv[1]);
}
hm.put(kv[0], detailsMap);
}
}
} catch (Exception e) {
throw rethrow(e);
}
}
/**
* <p>
* ini2Map.
* </p>
*
* @param iniFile
* a {@link java.lang.String} object.
* @param hm
* a {@link java.util.Map} object.
* @param keyToUpper
* a boolean.
*/
public static void ini2Map(String iniFile, Map<String, String> hm, boolean keyToUpper) {
// Load config ini from disk
FileInputStream iStream = null;
try {
iStream = new FileInputStream(iniFile);
ini2Map(iStream, hm, keyToUpper);
} catch (Exception e) {
throw rethrow(e);
} finally {
IOUtils.closeQuietly(iStream);
}
}
/**
* <p>
* ini2Map.
* </p>
*
* @param iniFile
* a {@link java.io.InputStream} object.
* @param hm
* a {@link java.util.Map} object.
* @param keyToUpper
* a boolean.
* @throws java.io.IOException
* if any.
*/
public static void ini2Map(InputStream iniFile, Map<String, String> hm, boolean keyToUpper) throws IOException {
// Load config ini from stream
Properties config = new Properties();
config.load(iniFile);
// Convert to hashmap
Enumeration en = config.propertyNames();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
if (keyToUpper) {
hm.put(key.toUpperCase(), config.getProperty(key));
} else {
hm.put(key, config.getProperty(key));
}
}
}
/**
* Method to getValues all "--key=value" or "--key value" parameters and add them to hashmap
*
* @param args
* an array of {@link java.lang.String} objects.
* @param hm
* a {@link java.util.Map} object.
*/
public static void cli2Map(String[] args, Map<String, String> hm) {
if (args == null || hm == null) {
Log.info("cliMap input NULL");
return;
}
// Parse command line arguments for --key=value
String currKey = "";
for (String arg : args) {
Log.info("CURR KEY: " + arg);
// If it starts with --, try to split on =
if (arg.startsWith("--")) {
String[] split = arg.split("\\=");
// If had =, turn key into args
if (split.length == 2) {
// Strip starting --
hm.put(split[0].substring(2), split[1]);
currKey = "";
} else {
Log.info("FOUND KEY " + currKey);
currKey = arg.substring(2);
}
} else {
if (!"".equals(currKey)) {
Log.info("PUTTING KEY VALUE " + currKey + " " + arg);
hm.put(currKey, arg);
}
}
}
}
/*
* Iterate through a Map and change all Strings to ints where possible
*/
/**
* <p>
* mapString2Int.
* </p>
*
* @param map
* a {@link java.util.Map} object.
* @return a {@link java.util.Map} object.
*/
public static Map mapString2Int(Map map) {
Map<String, Object> result = new HashMap<>();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
// Try to convert string to number
try {
Long number = Long.parseLong((String) map.get(key));
result.put(key, number);
} catch (NumberFormatException e) {
// Ignore exception, caust just means it is a string
result.put(key, map.get(key));
}
}
return (result);
}
public static final String VAR_RANDOM = "sqw.random";
public static final String VAR_DATE = "sqw.date";
public static final String VAR_DATETIME = "sqw.datetime";
public static final String VAR_TIMESTAMP = "sqw.timestamp";
public static final String VAR_UUID = "sqw.uuid";
public static final String VAR_BUNDLE_DIR = "sqw.bundle-dir";
public static final String LEGACY_VAR_RANDOM = "random";
public static final String LEGACY_VAR_DATE = "date";
public static final String LEGACY_VAR_BUNDLE_DIR = "workflow_bundle_dir";
public static final String VAR_WORKFLOW_VERSION = "sqw.bundle-seqware-version";
public static void provideBundleVersion(Map<String, String> m, String bundleVersion) {
m.put(VAR_WORKFLOW_VERSION, bundleVersion);
}
public static void provideBundleDir(Map<String, String> m, String bundleDir) {
m.put(VAR_BUNDLE_DIR, bundleDir);
m.put(LEGACY_VAR_BUNDLE_DIR, bundleDir);
}
public static Map<String, String> providedMap(String bundleDir, String bundleSeqwareVersion) {
Map<String, String> m = new HashMap<>();
provideBundleDir(m, bundleDir);
provideBundleVersion(m, bundleSeqwareVersion);
return m;
}
public static String generatedValue(String key) {
if (key.equals(VAR_RANDOM)) return String.valueOf(new Random().nextInt(Integer.MAX_VALUE));
if (key.equals(VAR_DATE)) return DatatypeConverter.printDate(Calendar.getInstance());
if (key.equals(VAR_DATETIME)) return DatatypeConverter.printDateTime(Calendar.getInstance());
if (key.equals(VAR_TIMESTAMP)) return String.valueOf(System.currentTimeMillis());
if (key.equals(VAR_UUID)) return UUID.randomUUID().toString();
if (key.equals(LEGACY_VAR_RANDOM)) {
Log.warn(String.format("Variable '%s' is deprecated. Please use '%s' instead.", LEGACY_VAR_RANDOM, VAR_RANDOM));
return String.valueOf(new Random().nextInt(Integer.MAX_VALUE));
}
if (key.equals(LEGACY_VAR_DATE)) {
Log.warn(String.format("Variable '%s' is deprecated. Please use '%s' instead.", LEGACY_VAR_DATE, VAR_DATE));
return DatatypeConverter.printDate(Calendar.getInstance());
}
return null;
}
private static final Pattern VAR = Pattern.compile("\\$\\{([^\\}]*)\\}");
public static Map<String, String> expandVariables(Map<String, String> raw) {
return expandVariables(raw, null);
}
public static Map<String, String> expandVariables(Map<String, String> raw, Map<String, String> provided) {
return expandVariables(raw, provided, false);
}
public static Map<String, String> expandVariables(Map<String, String> raw, Map<String, String> provided, boolean allowMissingVars) {
raw = new HashMap<>(raw); // don't mess with someone else's data structure
Map<String, String> exp = new HashMap<>();
int prevCount;
do {
prevCount = raw.size();
Iterator<Map.Entry<String, String>> iter = raw.entrySet().iterator();
entries: while (iter.hasNext()) {
Map.Entry<String, String> e = iter.next();
Matcher m = VAR.matcher(e.getValue());
if (m.find()) {
// this value has variables
StringBuffer sb = new StringBuffer();
do {
String key = m.group(1);
String val = exp.get(key);
if (val == null && provided != null) val = provided.get(key);
if (val == null) val = generatedValue(key);
if (val != null) {
// found substitution, replace and then look for more
m.appendReplacement(sb, val);
} else {
// we don't yet have all the substitutions, skip this entry for now
continue entries;
}
} while (m.find());
// done substituting the variables
m.appendTail(sb);
exp.put(e.getKey(), sb.toString());
iter.remove();
} else {
// no variables, move the entry
exp.put(e.getKey(), e.getValue());
iter.remove();
}
}
// exit when nothing left to convert or no incremental improvement
} while (0 < raw.size() && raw.size() < prevCount);
if (!allowMissingVars && raw.size() > 0) {
StringBuilder sb = new StringBuilder("Could not satisfy variable substitution:");
for (Map.Entry<String, String> e : raw.entrySet()) {
sb.append("\n");
sb.append(e.getKey());
sb.append("=");
sb.append(e.getValue());
}
throw new RuntimeException(sb.toString());
}
return exp;
}
/**
* <p>
* iniString2Map.
* </p>
*
* @param iniString
* a {@link java.lang.String} object.
* @return a {@link java.util.Map} object.
*/
public static Map<String, String> iniString2Map(String iniString) {
Map<String, String> result = new HashMap<>();
String[] lines = iniString.split("\n");
for (String line : lines) {
if (isLineMatchesKeyValue(line)) {
// seqware-1911 allow for second = in value
String[] kv = line.split("\\s*=\\s*", 2);
if (kv.length == 2) {
result.put(kv[0], kv[1]);
} else if (kv.length == 1) {
result.put(kv[0], "");
} else {
System.err.println("Found a line I couldn't parse: " + line);
}
}
}
return (result);
}
private static boolean isLineMatchesKeyValue(String line) {
return !line.startsWith("#") && line.matches("\\S+\\s*=[^=]*");
}
/**
* Convert what looks like a map of ini key-value pairs into a SortedSet of WorkflowRunParams
*
* @param map
* @return
* @throws NumberFormatException
*/
public static SortedSet<WorkflowRunParam> createWorkflowRunParameters(HashMap<String, String> map) throws NumberFormatException {
SortedSet<WorkflowRunParam> runParams = new TreeSet<>();
for (String str : map.keySet()) {
if (map.get(str) != null) {
Log.info(str + " " + map.get(str));
if (str.equals(ReservedIniKeys.PARENT_UNDERSCORE_ACCESSIONS.getKey())
|| str.equals(ReservedIniKeys.PARENT_ACCESSION.getKey())
|| str.equals(ReservedIniKeys.PARENT_DASH_ACCESSIONS.getKey())) {
String[] pAcc = map.get(str).split(",");
for (String parent : pAcc) {
WorkflowRunParam wrp = new WorkflowRunParam();
wrp.setKey(str);
wrp.setValue(parent);
wrp.setParentProcessingAccession(Integer.parseInt(parent));
wrp.setType("text");
runParams.add(wrp);
}
} else {
if (str.trim().isEmpty() && map.get(str).trim().isEmpty()) {
continue;
}
WorkflowRunParam wrp = new WorkflowRunParam();
wrp.setKey(str);
wrp.setValue(map.get(str));
wrp.setType("text");
runParams.add(wrp);
}
} else {
Log.info("Null: " + str + " " + map.get(str));
}
}
return runParams;
}
}