diff options
Diffstat (limited to 'bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java')
-rw-r--r-- | bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java new file mode 100644 index 00000000000..16858808a58 --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java @@ -0,0 +1,283 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.osgi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Tony Vaagenes + * @author ollivir + */ +public class ExportPackageParser { + public static List<ExportPackages.Export> parseExports(String exportAttribute) { + ParsingContext p = new ParsingContext(exportAttribute.trim()); + + List<ExportPackages.Export> exports = parseExportPackage(p); + if (exports.isEmpty()) { + p.fail("Expected a list of exports"); + } else if (p.atEnd() == false) { + p.fail("Exports not fully processed"); + } + return exports; + } + + private static class ParsingContext { + private enum State { + Invalid, WantMore, End + } + + private CharSequence input; + private int pos; + private State state; + private int length; + private char ch; + + private ParsingContext(CharSequence input) { + this.input = input; + this.pos = 0; + } + + private Optional<String> read(Consumer<ParsingContext> rule) { + StringBuilder ret = new StringBuilder(); + + parse: while (true) { + if (input.length() < pos + 1) { + break; + } + ch = input.charAt(pos); + state = State.WantMore; + length = ret.length(); + rule.accept(this); + + switch (state) { + case Invalid: + if (ret.length() == 0) { + break parse; + } else { + String printable = Character.isISOControl(ch) ? "#" + Integer.toString((int) ch) + : "[" + Character.toString(ch) + "]"; + pos++; + fail("Character " + printable + " was not acceptable"); + } + break; + case WantMore: + ret.append(ch); + pos++; + break; + case End: + break parse; + } + } + + if (ret.length() == 0) { + return Optional.empty(); + } else { + return Optional.of(ret.toString()); + } + } + + private Optional<String> regexp(Pattern pattern) { + Matcher matcher = pattern.matcher(input); + matcher.region(pos, input.length()); + if (matcher.lookingAt()) { + String value = matcher.group(); + pos += value.length(); + return Optional.of(value); + } else { + return Optional.empty(); + } + } + + private Optional<String> exactly(String string) { + if (input.length() - pos < string.length()) { + return Optional.empty(); + } + if (input.subSequence(pos, pos + string.length()).equals(string)) { + pos += string.length(); + return Optional.of(string); + } + return Optional.empty(); + } + + private boolean atEnd() { + return pos == input.length(); + } + + private void invalid() { + this.state = State.Invalid; + } + + private void end() { + this.state = State.End; + } + + private void fail(String message) { + throw new RuntimeException("Failed parsing Export-Package: " + message + " at position " + pos); + } + } + + /* ident = ? a valid Java identifier ? */ + private static Optional<String> parseIdent(ParsingContext p) { + Optional<String> ident = p.read(ctx -> { + if (ctx.length == 0) { + if (Character.isJavaIdentifierStart(ctx.ch) == false) { + ctx.invalid(); + } + } else { + if (Character.isJavaIdentifierPart(ctx.ch) == false) { + ctx.end(); + } + } + }); + return ident; + } + + /* stringLiteral = ? sequence of any character except double quotes, control characters or backslash, + a backslash followed by another backslash, a single or double quote, or one of the letters b,f,n,r or t + a backslash followed by u followed by four hexadecimal digits ? */ + private static Pattern STRING_LITERAL_PATTERN = Pattern + .compile("\"" + "(?:[^\"\\p{Cntrl}\\\\]|\\\\[\\\\'\"bfnrt]|\\\\u[0-9a-fA-F]{4})+" + "\""); + + private static Optional<String> parseStringLiteral(ParsingContext p) { + return p.regexp(STRING_LITERAL_PATTERN).map(quoted -> quoted.substring(1, quoted.length() - 1)); + } + + /* extended = { \p{Alnum} | '_' | '-' | '.' }+ */ + private static Pattern EXTENDED_PATTERN = Pattern.compile("[\\p{Alnum}_.-]+"); + + private static Optional<String> parseExtended(ParsingContext p) { + return p.regexp(EXTENDED_PATTERN); + } + + /* argument = extended | stringLiteral | ? failure ? */ + private static String parseArgument(ParsingContext p) { + Optional<String> argument = parseExtended(p); + if (argument.isPresent() == false) { + argument = parseStringLiteral(p); + } + if (argument.isPresent() == false) { + p.fail("Expected an extended token or a string literal"); + } + return argument.get(); + } + + /* + * parameter = ( directive | attribute ) + * directive = extended, ':=', argument + * attribute = extended, '=', argument + */ + private static Pattern DIRECTIVE_OR_ATTRIBUTE_SEPARATOR_PATTERN = Pattern.compile("\\s*:?=\\s*"); + + private static Optional<ExportPackages.Parameter> parseParameter(ParsingContext p) { + int backtrack = p.pos; + Optional<String> ext = parseExtended(p); + if (ext.isPresent()) { + Optional<String> sep = p.regexp(DIRECTIVE_OR_ATTRIBUTE_SEPARATOR_PATTERN); + if (sep.isPresent() == false) { + p.pos = backtrack; + return Optional.empty(); + } + String argument = parseArgument(p); + return Optional.of(new ExportPackages.Parameter(ext.get(), argument)); + } else { + return Optional.empty(); + } + } + + /* parameters = parameter, { ';' parameter } */ + private static Pattern PARAMETER_SEPARATOR_PATTERN = Pattern.compile("\\s*;\\s*"); + + private static List<ExportPackages.Parameter> parseParameters(ParsingContext p) { + List<ExportPackages.Parameter> params = new ArrayList<>(); + boolean wantMore = true; + do { + Optional<ExportPackages.Parameter> param = parseParameter(p); + if (param.isPresent()) { + params.add(param.get()); + wantMore = p.regexp(PARAMETER_SEPARATOR_PATTERN).isPresent(); + } else { + wantMore = false; + } + } while (wantMore); + + return params; + } + + /* packageName = ident, { '.', ident } */ + private static Optional<String> parsePackageName(ParsingContext p) { + StringBuilder ret = new StringBuilder(); + + boolean wantMore = true; + do { + Optional<String> ident = parseIdent(p); + if (ident.isPresent()) { + ret.append(ident.get()); + Optional<String> separator = p.exactly("."); + if (separator.isPresent()) { + ret.append(separator.get()); + wantMore = true; + } else { + wantMore = false; + } + } else { + wantMore = false; + } + } while (wantMore); + + if (ret.length() > 0) { + return Optional.of(ret.toString()); + } else { + return Optional.empty(); + } + } + + /* export = packageName, [ ';', ( parameters | export ) ] */ + private static ExportPackages.Export parseExport(ParsingContext p) { + List<String> exports = new ArrayList<>(); + + boolean wantMore = true; + do { + if (exports.isEmpty() == false) { // second+ iteration + List<ExportPackages.Parameter> params = parseParameters(p); + if (params.isEmpty() == false) { + return new ExportPackages.Export(exports, params); + } + } + + Optional<String> packageName = parsePackageName(p); + if (packageName.isPresent()) { + exports.add(packageName.get()); + } else { + p.fail(exports.isEmpty() ? "Expected a package name" : "Expected either a package name or a parameter list"); + } + + wantMore = p.regexp(PARAMETER_SEPARATOR_PATTERN).isPresent(); + } while (wantMore); + + return new ExportPackages.Export(exports, new ArrayList<>()); + } + + /* exportPackage = export, { ',', export } */ + private static Pattern EXPORT_SEPARATOR_PATTERN = Pattern.compile("\\s*,\\s*"); + + private static List<ExportPackages.Export> parseExportPackage(ParsingContext p) { + List<ExportPackages.Export> exports = new ArrayList<>(); + + boolean wantMore = true; + do { + ExportPackages.Export export = parseExport(p); + if (export.getPackageNames().isEmpty()) { + wantMore = false; + } else { + exports.add(export); + wantMore = p.regexp(EXPORT_SEPARATOR_PATTERN).isPresent(); + } + } while (wantMore); + + return exports; + } +} |