package org.ovirt.engine.core.utils.vdshooks;
import java.util.HashMap;
import java.util.Map;
/**
* Parsers a hooks string to a map of script directories/events to a map of script names to a
* map of script properties.
* The string is in format of a "toString" invocation on java.util.map object - for
* example "{before_vm_hibernate={myscript.sh={md5=5d9c4609cd936e80bac8c9ef7b27ea73},
* 01_log={md5=f388923356e84c2b4149572a44fde2b4}},
* after_vm_migrate_destination={myscript.sh={md5=677da3bdd8fbd16d4b8917a9fe0f6f89}},
* after_vm_cont={01_log={md5=f388923356e84c2b4149572a44fde2b4}}}"
*
*
*/
public class VdsHooksParser {
public enum ParsingState {
START_MAP,
KEY,
VALUE
};
public static class ParsingResult {
private Map<String, Object> map;
private int endIndex;
public ParsingResult(Map<String, Object> map, int endIndex) {
this.map = map;
this.endIndex = endIndex;
}
@Override
public String toString() {
return map.toString();
}
public Map<String, Object> getMap() {
return map;
}
public int getEndIndex() {
return endIndex;
}
}
protected static ParsingResult parseMap(char[] chars, int startIndex) {
if (chars[startIndex] != '{') {
return null;
}
int endIndex = -1;
StringBuilder keyBuilder = new StringBuilder();
Object value = null;
Map<String, Object> resultMap = new HashMap<>();
ParsingState state = ParsingState.START_MAP;
// Goes over all the characters starting at one position after the opening "{"
for (int counter = startIndex + 1; counter < chars.length; counter++) {
char current = chars[counter];
if (current == ' ') {
// Ignore spaces
continue;
}
// If a closing "}" was found, break from the loop (in order to return the parsing result
if (endIndex != -1) {
break;
}
switch (state) {
case START_MAP:
// If the character is not "}" - then this is a character of a key in the map,
// Change to "parsing key" state
if (current != '}') {
state = ParsingState.KEY;
keyBuilder = new StringBuilder();
handleKey(keyBuilder, current);
} else {
endIndex = counter;
}
break;
case KEY:
// If the character is = - it means the character is the start of a value which is either a recursive
// map or a string
// Switch to "parsing value" state
if (current == '=') {
state = ParsingState.VALUE;
if (chars[counter + 1] != '{') {
value = new StringBuilder();
}
} else { // Otherwise - continue to parse the key
handleKey(keyBuilder, current);
}
break;
case VALUE:
// If the character is "{" - this means this is the start of a recursive (inner) map
if (current == '{') {
// Parse the inner map, and advance the counter to the end of the map, so its characters will not be
// re-parsed
value = parseMap(chars, counter);
counter = ((ParsingResult) value).getEndIndex();
} else if (current == ',') {
// If the character is "," it means that this is the beginning of a next pair of key and value -
// return to "parsing key" state
value = putValueInMap(keyBuilder, value, resultMap);
keyBuilder = new StringBuilder();
state = ParsingState.KEY;
} else if (current == '}') {
// If the character is "}" this is the closing of the map - change the value of endIndex not to be
// -1
endIndex = counter;
value = putValueInMap(keyBuilder, value, resultMap);
} else {
handleValue((StringBuilder) value, current);
}
break;
}
}
// At this point, the endIndex is not -1, return the parsing result
ParsingResult result = new ParsingResult(resultMap, endIndex);
return result;
}
private static Object putValueInMap(StringBuilder keyBuilder, Object value, Map<String, Object> resultMap) {
if (value instanceof ParsingResult) {
// Place the map that belongs to the parsing result returned from
// the recursive call
value = ((ParsingResult) value).getMap();
} else {
// value is StringBuilder, convert it to String
value = value.toString();
}
resultMap.put(keyBuilder.toString(), value);
return value;
}
private static void handleValue(StringBuilder valueBuilder, char current) {
valueBuilder.append(current);
}
private static void handleKey(StringBuilder keyBuilder, char current) {
keyBuilder.append(current);
}
public static Map<String, Object> parseHooks(String hooksStr) {
char[] chars = new char[hooksStr.length()];
hooksStr.getChars(0, hooksStr.length(), chars, 0);
ParsingResult parsingResult = parseMap(chars, 0);
return parsingResult.getMap();
}
}