package org.ecgine.gradle; import java.io.PrintStream; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; public class ManifestHeaderValue { private List<ManifestHeaderElement> elements = new ArrayList<ManifestHeaderElement>(); ManifestHeaderValue() { // just for unit testing } public ManifestHeaderValue(String header) throws ParseException { if (header != null) { new ManifestHeaderParser(header).parse(); } } public List<ManifestHeaderElement> getElements() { return elements; } public String getSingleValue() { if (elements.isEmpty()) { return null; } List<String> values = getElements().iterator().next().getValues(); if (values.isEmpty()) { return null; } return values.iterator().next(); } public List<String> getValues() { if (elements.isEmpty()) { return Collections.emptyList(); } List<String> list = new ArrayList<String>(); for (ManifestHeaderElement element : getElements()) { list.addAll(element.getValues()); } return list; } void addElement(ManifestHeaderElement element) { this.elements.add(element); } class ManifestHeaderParser { /** * header to parse */ private final String header; /** * the length of the source */ private int length; /** * buffer */ private StringBuffer buffer = new StringBuffer(); /** * position in the source */ private int pos = 0; /** * last read character */ private char c; /** * the header element being build */ private ManifestHeaderElement elem = new ManifestHeaderElement(); /** * Once at true (at the first attribute parsed), only parameters are * allowed */ private boolean valuesParsed; /** * the last parsed parameter name */ private String paramName; /** * true if the last parsed parameter is a directive (assigned via :=) */ private boolean isDirective; /** * Default constructor * * @param header * the header to parse */ ManifestHeaderParser(String header) { this.header = header; this.length = header.length(); } /** * Do the parsing * * @throws ParseException */ void parse() throws ParseException { do { elem = new ManifestHeaderElement(); int posElement = pos; parseElement(); if (elem.getValues().isEmpty()) { error("No defined value", posElement); // try to recover: ignore that element continue; } addElement(elem); } while (pos < length); } private char readNext() { if (pos == length) { c = '\0'; } else { c = header.charAt(pos++); } return c; } private void error(String message) throws ParseException { error(message, pos - 1); } private void error(String message, int p) throws ParseException { // throw new ParseException(message, p); } private void parseElement() throws ParseException { valuesParsed = false; do { parseValueOrParameter(); } while (c == ';' && pos < length); } private void parseValueOrParameter() throws ParseException { // true if the value/parameter parsing has started, white spaces // skipped boolean start = false; do { switch (readNext()) { case '\0': break; case ';': case ',': endValue(); return; case ':': case '=': endParameterName(); parseSeparator(); parseParameterValue(); return; case ' ': case '\t': case '\n': case '\r': if (start) { buffer.append(c); } break; default: start = true; buffer.append(c); } } while (pos < length); endValue(); } private void endValue() throws ParseException { if (valuesParsed) { error("Early end of a parameter"); // try to recover: ignore it buffer.setLength(0); return; } if (buffer.length() == 0) { error("Empty value"); // try to recover: just ignore the error } elem.addValue(buffer.toString()); buffer.setLength(0); } private void endParameterName() throws ParseException { if (buffer.length() == 0) { error("Empty parameter name"); // try to recover: won't store the value paramName = null; } paramName = buffer.toString(); buffer.setLength(0); } private void parseSeparator() throws ParseException { if (c == '=') { isDirective = false; return; } if (readNext() != '=') { error("Expecting '='"); // try to recover: will ignore this parameter pos--; paramName = null; } isDirective = true; } private void parseParameterValue() throws ParseException { // true if the value parsing has started, white spaces skipped boolean start = false; // true if the value parsing is ended, then only white spaces are // allowed boolean end = false; boolean doubleQuoted = false; do { switch (readNext()) { case '\0': break; case ',': case ';': endParameterValue(); return; case '=': case ':': error("Illegal character '" + c + "' in parameter value of " + paramName); // try to recover: ignore that parameter paramName = null; break; case '\"': doubleQuoted = true; case '\'': if (end && paramName != null) { error("Expecting the end of a parameter value"); // try to recover: ignore that parameter paramName = null; } if (start) { // quote in the middle of the value, just add it as a // quote buffer.append(c); } else { start = true; appendQuoted(doubleQuoted); end = true; } break; case '\\': if (end && paramName != null) { error("Expecting the end of a parameter value"); // try to recover: ignore that parameter paramName = null; } start = true; appendEscaped(); break; case ' ': case '\t': case '\n': case '\r': if (start) { end = true; } break; default: if (end && paramName != null) { error("Expecting the end of a parameter value"); // try to recover: ignore that parameter paramName = null; } start = true; buffer.append(c); } } while (pos < length); endParameterValue(); } private void endParameterValue() throws ParseException { if (paramName == null) { // recovering from an incorrect parameter: skip the value return; } if (buffer.length() == 0) { error("Empty parameter value"); // try to recover: do not store the parameter return; } String value = buffer.toString(); if (isDirective) { elem.addDirective(paramName, value); } else { elem.addAttribute(paramName, value); } valuesParsed = true; buffer.setLength(0); } private void appendQuoted(boolean doubleQuoted) { do { switch (readNext()) { case '\0': break; case '\"': if (doubleQuoted) { return; } buffer.append(c); break; case '\'': if (!doubleQuoted) { return; } buffer.append(c); break; case '\\': break; default: buffer.append(c); } } while (pos < length); } private void appendEscaped() { if (pos < length) { buffer.append(readNext()); } else { buffer.append(c); } } } public boolean equals(Object obj) { if (!(obj instanceof ManifestHeaderValue)) { return false; } ManifestHeaderValue other = (ManifestHeaderValue) obj; if (other.elements.size() != elements.size()) { return false; } for (ManifestHeaderElement element : elements) { if (!other.elements.contains(element)) { return false; } } return true; } public String toString() { String string = ""; Iterator<ManifestHeaderElement> it = elements.iterator(); while (it.hasNext()) { string = string.concat(it.next().toString()); if (it.hasNext()) { string = string.concat(","); } } return string; } public static void writeParseException(PrintStream out, String source, ParseException e) { out.println(e.getMessage()); out.print(" " + source + "\n "); for (int i = 0; i < e.getErrorOffset(); i++) { out.print(' '); } out.println('^'); } }