blob: 6ad84dc06365a007dcaab66abf97e5a3a66fb398 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ivy.osgi.core;
import java.io.PrintStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Parse a header of a manifest. The manifest header is composed with the following rules:
*
* <pre>
* header ::= header-element (',' header-element)*
* header-element ::= values (';' (attribute | directive) )*
* values ::= value (';' value)*
* value ::= &lt;any string value that does not have ';' or ','&gt;
* attribute ::= key '=' value
* directive ::= key '=' value
* key ::= token
* value ::= token | quoted-string | double-quoted-string
* </pre>
*/
public class ManifestHeaderValue {
private List<ManifestHeaderElement> elements = new ArrayList<>();
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<>();
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 StringBuilder buffer = new StringBuilder();
/**
* 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 if something goes wrong
*/
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() {
StringBuilder sb = new StringBuilder();
for (ManifestHeaderElement element : elements) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(element.toString());
}
return sb.toString();
}
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('^');
}
}