From 95577d4de1010edb62dcf9ae90619e592e52842e Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Thu, 30 Apr 2020 09:42:38 +0200 Subject: [PATCH] axiom: Introduced base parser and stream APIs Signed-off-by: Tony Tkacik --- infra/axiom/pom.xml | 81 ++++++++++++++ .../com/evolveum/axiom/lang/antlr/Axiom.g4 | 45 ++++++++ .../evolveum/axiom/api/AxiomIdentifier.java | 68 ++++++++++++ .../axiom/lang/api/AxiomBuiltInProperty.java | 29 +++++ .../lang/api/AxiomBuiltInSimpleType.java | 20 ++++ .../lang/api/AxiomPropertyDefinition.java | 10 ++ .../axiom/lang/api/AxiomStatement.java | 16 +++ .../api/AxiomStatementStreamListener.java | 11 ++ .../axiom/lang/api/AxiomTypeDefinition.java | 5 + .../axiom/lang/impl/AxiomAntlrAdapter.java | 60 +++++++++++ .../axiom/lang/impl/AxiomAntlrVisitor.java | 101 ++++++++++++++++++ .../axiom/lang/impl/AxiomErrorListener.java | 48 +++++++++ .../lang/impl/AxiomIdentifierResolver.java | 19 ++++ .../axiom/lang/impl/AxiomModelInfo.java | 10 ++ .../axiom/lang/impl/AxiomStatementImpl.java | 85 +++++++++++++++ .../axiom/lang/impl/AxiomStatementSource.java | 73 +++++++++++++ .../impl/AxiomStatementStreamBuilder.java | 63 +++++++++++ .../axiom/lang/impl/AxiomSyntaxException.java | 61 +++++++++++ .../axiom/lang/test/TestAxiomParser.java | 36 +++++++ .../test/resources/axiom-self-described.axiom | 63 +++++++++++ .../src/test/resources/model-header.axiom | 16 +++ infra/pom.xml | 1 + 22 files changed, 921 insertions(+) create mode 100644 infra/axiom/pom.xml create mode 100644 infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInProperty.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInSimpleType.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomPropertyDefinition.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatement.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatementStreamListener.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrAdapter.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java create mode 100644 infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java create mode 100644 infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java create mode 100644 infra/axiom/src/test/resources/axiom-self-described.axiom create mode 100644 infra/axiom/src/test/resources/model-header.axiom diff --git a/infra/axiom/pom.xml b/infra/axiom/pom.xml new file mode 100644 index 00000000000..8a297e0e81b --- /dev/null +++ b/infra/axiom/pom.xml @@ -0,0 +1,81 @@ + + + + + 4.0.0 + + + infra + com.evolveum.midpoint.infra + 4.2-SNAPSHOT + + + com.evolveum.axiom + axiom + + Axiom + + + true + + + + + org.jetbrains + annotations + + + com.google.guava + guava + + + org.antlr + antlr4-runtime + 4.8-1 + + + + org.testng + testng + test + + + com.evolveum.midpoint.tools + test-ng + test + + + org.assertj + assertj-core + test + + + + + + + + org.antlr + antlr4-maven-plugin + 4.8-1 + + + + antlr4 + + + + + true + true + + + + + diff --git a/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 new file mode 100644 index 00000000000..bee08241596 --- /dev/null +++ b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 @@ -0,0 +1,45 @@ +grammar Axiom; + +SEMICOLON : ';'; +LEFT_BRACE : '{'; +RIGHT_BRACE : '}'; +COLON : ':'; +PLUS : '+'; +LINE_COMMENT : [ \n\r\t]* ('//' (~[\r\n]*)) [ \n\r\t]* -> skip; +SEP: [ \n\r\t]+; +IDENTIFIER : [a-zA-Z_/][a-zA-Z0-9_\-./]*; + +fragment SQOUTE : '\''; +fragment DQOUTE : '"'; + + +//fragment SUB_STRING : ('"' (ESC | ~["])*? '"') | ('\'' (ESC | ~['])* '\''); +//fragment ESC : '\\' (["\\/bfnrt] | UNICODE); +//fragment UNICODE : 'u' HEX HEX HEX HEX; +//fragment HEX : [0-9a-fA-F] ; +//STRING: ((~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '"' | '\'' | '}' | '/' | '+')~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '}' )* ) | SUB_STRING ); + +fragment ESC : '\\'; + +STRING_SINGLEQUOTE: SQOUTE ((ESC SQOUTE) | ~[\n'])* SQOUTE; +STRING_DOUBLEQUOTE: DQOUTE ((ESC DQOUTE) | ~[\n"])* DQOUTE; +//STRING_MULTILINE: '"""' (ESC | ~('"""'))* '"""'; + +statement : SEP* identifier SEP* (argument)? SEP* (SEMICOLON | LEFT_BRACE SEP* (statement)* SEP* RIGHT_BRACE SEP*) SEP*; + +identifier : (prefix COLON)? localIdentifier; +prefix : IDENTIFIER; +localIdentifier : IDENTIFIER; + + +// argument : STRING (SEP* PLUS SEP* STRING)* | IDENTIFIER; +argument : identifier | string; +string : singleQuoteString | doubleQuoteString | multilineString; + + +singleQuoteString : STRING_SINGLEQUOTE; +doubleQuoteString : STRING_DOUBLEQUOTE; +multilineString: '"""\n' (~('"""'))*'"""'; + + + diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java new file mode 100644 index 00000000000..d9a3fcf3637 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java @@ -0,0 +1,68 @@ +package com.evolveum.axiom.api; + +import java.util.Objects; + +import com.google.common.base.Preconditions; + +public class AxiomIdentifier { + + public static final String AXIOM_NAMESPACE = "https://ns.evolveum.com/axiom"; + private final String namespace; + private final String localName; + + public AxiomIdentifier(String namespace, String localName) { + this.namespace = Preconditions.checkNotNull(namespace, "namespace"); + this.localName = Preconditions.checkNotNull(localName, "localName"); + } + + public String getNamespace() { + return namespace; + } + + public String getLocalName() { + return localName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((localName == null) ? 0 : localName.hashCode()); + result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof AxiomIdentifier)) + return false; + AxiomIdentifier other = (AxiomIdentifier) obj; + if (localName == null) { + if (other.localName != null) + return false; + } else if (!localName.equals(other.localName)) + return false; + if (namespace == null) { + if (other.namespace != null) + return false; + } else if (!namespace.equals(other.namespace)) + return false; + return true; + } + + @Override + public String toString() { + return localName; + } + + public static AxiomIdentifier axiom(String identifier) { + return new AxiomIdentifier(AXIOM_NAMESPACE, identifier); + } + + public static AxiomIdentifier from(String namespace, String localName) { + return new AxiomIdentifier(namespace, localName); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInProperty.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInProperty.java new file mode 100644 index 00000000000..e4d59a47c13 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInProperty.java @@ -0,0 +1,29 @@ +package com.evolveum.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; + +class AxiomBuiltInProperty implements AxiomPropertyDefinition { + + private final AxiomIdentifier argument; + private final AxiomTypeDefinition type; + + public AxiomBuiltInProperty(AxiomIdentifier argument, AxiomTypeDefinition type) { + this.argument = argument; + this.type = type; + } + + @Override + public AxiomIdentifier getIdentifier() { + return argument; + } + + @Override + public AxiomTypeDefinition getType() { + return type; + } + + @Override + public boolean required() { + return true; + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInSimpleType.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInSimpleType.java new file mode 100644 index 00000000000..f50d0873fcf --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltInSimpleType.java @@ -0,0 +1,20 @@ +package com.evolveum.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public enum AxiomBuiltInSimpleType implements AxiomTypeDefinition { + + IDENTIFIER("identifier"), + STRING("string"), + SEMANTIC_VERSION("SemanticVersion"); + + private final AxiomIdentifier identifier; + + AxiomBuiltInSimpleType(String identifier) { + this.identifier = AxiomIdentifier.axiom(identifier); + } + + public AxiomIdentifier getIdentifier() { + return identifier; + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomPropertyDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomPropertyDefinition.java new file mode 100644 index 00000000000..6dca7a5b644 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomPropertyDefinition.java @@ -0,0 +1,10 @@ +package com.evolveum.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomPropertyDefinition { + + AxiomIdentifier getIdentifier(); + AxiomTypeDefinition getType(); + boolean required(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatement.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatement.java new file mode 100644 index 00000000000..ae856787a7a --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatement.java @@ -0,0 +1,16 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Collection; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomStatement { + + AxiomIdentifier keyword(); + V value(); + + Collection> children(); + Collection> children(AxiomIdentifier type); + + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatementStreamListener.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatementStreamListener.java new file mode 100644 index 00000000000..296b32d45f9 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomStatementStreamListener.java @@ -0,0 +1,11 @@ +package com.evolveum.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomStatementStreamListener { + + void startStatement(AxiomIdentifier statement); + void argument(AxiomIdentifier identifier); + void argument(String identifier); + void endStatement(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java new file mode 100644 index 00000000000..a0dcc8e0ec8 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java @@ -0,0 +1,5 @@ +package com.evolveum.axiom.lang.api; + +public interface AxiomTypeDefinition { + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrAdapter.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrAdapter.java new file mode 100644 index 00000000000..34e71d82d47 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrAdapter.java @@ -0,0 +1,60 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.antlr.AxiomBaseListener; +import com.evolveum.axiom.lang.antlr.AxiomParser.ArgumentContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.DoubleQuoteStringContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.IdentifierContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.StatementContext; +import com.evolveum.axiom.lang.api.AxiomStatementStreamListener; + +public class AxiomAntlrAdapter extends AxiomBaseListener { + + private final AxiomIdentifierResolver statements; + private final AxiomStatementStreamListener delegate; + + + + public AxiomAntlrAdapter(AxiomIdentifierResolver statements, AxiomStatementStreamListener delegate) { + this.statements = statements; + this.delegate = delegate; + } + + + + @Override + public void enterStatement(StatementContext ctx) { + AxiomIdentifier identifier = statementIdentifier(ctx.identifier()); + delegate.startStatement(identifier); + super.enterStatement(ctx); + } + + @Override + public void enterArgument(ArgumentContext ctx) { + if (ctx.identifier() != null) { + enterArgument(ctx.identifier()); + } else { + enterStringArgument(ctx.identifier()); + } + + + + super.enterArgument(ctx); + } + + private void enterStringArgument(IdentifierContext identifier) { + // TODO Auto-generated method stub + + } + + private void enterArgument(IdentifierContext identifier) { + delegate.argument(statementIdentifier(identifier)); + } + + private AxiomIdentifier statementIdentifier(IdentifierContext identifier) { + String prefix = identifier.prefix().getText(); + String localName = identifier.localIdentifier().getText(); + return statements.resolveStatementIdentifier(prefix,localName); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java new file mode 100644 index 00000000000..ab5e0ad8ee5 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java @@ -0,0 +1,101 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; +import java.util.Set; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.antlr.AxiomBaseVisitor; +import com.evolveum.axiom.lang.antlr.AxiomParser.ArgumentContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.IdentifierContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.PrefixContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.StatementContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.StringContext; +import com.evolveum.axiom.lang.api.AxiomStatementStreamListener; +import com.google.common.base.Strings; + +public class AxiomAntlrVisitor extends AxiomBaseVisitor { + + private final AxiomIdentifierResolver statements; + private final AxiomStatementStreamListener delegate; + private final Optional> limit; + + public AxiomAntlrVisitor(AxiomIdentifierResolver statements, AxiomStatementStreamListener delegate, + Set limit) { + this.statements = statements; + this.delegate = delegate; + this.limit = Optional.ofNullable(limit); + } + + private AxiomIdentifier statementIdentifier(IdentifierContext identifier) { + String prefix = nullableText(identifier.prefix()); + String localName = identifier.localIdentifier().getText(); + return statements.resolveStatementIdentifier(prefix,localName); + } + + private String nullableText(ParserRuleContext prefix) { + return prefix != null ? prefix.getText() : null; + } + + @Override + public T visitStatement(StatementContext ctx) { + AxiomIdentifier identifier = statementIdentifier(ctx.identifier()); + if(canEmit(identifier)) { + delegate.startStatement(identifier); + T ret = super.visitStatement(ctx); + delegate.endStatement(); + return ret; + } + return defaultResult(); + } + + private boolean canEmit(AxiomIdentifier identifier) { + if (limit.isPresent()) { + return limit.get().contains(identifier); + } + return true; + } + + @Override + public T visitArgument(ArgumentContext ctx) { + if (ctx.identifier() != null) { + delegate.argument(convert(ctx.identifier())); + } else { + delegate.argument(convert(ctx.string())); + } + return defaultResult(); + } + + private AxiomIdentifier convert(IdentifierContext argument) { + return statementIdentifier(argument); + } + + private String convert(StringContext string) { + if(string.singleQuoteString() != null) { + return convertSingleQuote(string.singleQuoteString().getText()); + } + if(string.doubleQuoteString() != null) { + return covertDoubleQuote(string.doubleQuoteString().getText()); + } + return convertMultiline(string.multilineString().getText()); + } + + + private String convertSingleQuote(String text) { + int stop = text.length(); + return text.substring(1, stop - 1); + } + + private String covertDoubleQuote(String text) { + int stop = text.length(); + return text.substring(1, stop - 1); + } + + private String convertMultiline(String text) { + return text.replace("\"\"\"", ""); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java new file mode 100644 index 00000000000..a6046fe451e --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java @@ -0,0 +1,48 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.dfa.DFA; + +public class AxiomErrorListener extends BaseErrorListener { + + private final String source; + private final List exceptions = new ArrayList<>(); + + public AxiomErrorListener(String source) { + this.source = source; + } + + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + // TODO Auto-generated method stub + + } + + public void validate() throws AxiomSyntaxException { + if (exceptions.isEmpty()) { + return; + } + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (AxiomSyntaxException e : exceptions) { + if (first) { + first = false; + } else { + sb.append('\n'); + } + + sb.append(e.getFormattedMessage()); + } + throw new AxiomSyntaxException(source, 0, 0, sb.toString()); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java new file mode 100644 index 00000000000..f6f7da04201 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java @@ -0,0 +1,19 @@ +package com.evolveum.axiom.lang.impl; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomIdentifierResolver { + + final AxiomIdentifierResolver AXIOM_DEFAULT_NAMESPACE = defaultNamespace(AxiomIdentifier.AXIOM_NAMESPACE); + + AxiomIdentifier resolveStatementIdentifier(@NotNull String prefix, @NotNull String localName); + + + + static AxiomIdentifierResolver defaultNamespace(String namespace) { + return (prefix, localName) -> prefix == null ? AxiomIdentifier.from(namespace, localName) : null; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java new file mode 100644 index 00000000000..9b7ca459215 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java @@ -0,0 +1,10 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomModelInfo { + + String getModelName(); + String getNamespace(); + String getDescription(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java new file mode 100644 index 00000000000..2b7146df71c --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java @@ -0,0 +1,85 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomStatement; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; + +public class AxiomStatementImpl implements AxiomStatement { + + private final AxiomIdentifier keyword; + private final V value; + private final List> children; + private final Multimap> keywordMap; + + public AxiomStatementImpl(AxiomIdentifier keyword, V value, List> children, + Multimap> keywordMap) { + super(); + this.keyword = keyword; + this.value = value; + this.children = ImmutableList.copyOf(children); + this.keywordMap = ImmutableMultimap.copyOf(keywordMap); + } + + @Override + public Collection> children(AxiomIdentifier type) { + return keywordMap.get(type); + } + + @Override + public Collection> children() { + return children; + } + + @Override + public AxiomIdentifier keyword() { + return keyword; + } + + @Override + public V value() { + return value; + } + + public static class Builder { + + private final AxiomIdentifier keyword; + private V value; + private List> children = new ArrayList<>(); + private Multimap> keywordMap = ArrayListMultimap.create(); + + public Builder(AxiomIdentifier keyword) { + this.keyword = keyword; + } + + public Builder setValue(V value) { + this.value = value; + return this; + } + + Builder add(AxiomStatement statement) { + children.add(statement); + keywordMap.put(statement.keyword(), statement); + return this; + } + + AxiomStatement build() { + return new AxiomStatementImpl<>(keyword, value, children, keywordMap); + } + + @Override + public String toString() { + return "Builder(" + keyword + ")"; + } + } + + + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java new file mode 100644 index 00000000000..c61aeed42f3 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java @@ -0,0 +1,73 @@ +package com.evolveum.axiom.lang.impl; + +import java.beans.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import java.util.Set; + +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.TokenStream; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.antlr.AxiomLexer; +import com.evolveum.axiom.lang.antlr.AxiomParser; +import com.evolveum.axiom.lang.antlr.AxiomParser.StatementContext; +import com.evolveum.axiom.lang.api.AxiomStatementStreamListener; + +public class AxiomStatementSource implements AxiomModelInfo { + + private final StatementContext root; + + public static AxiomStatementSource from(InputStream stream) throws IOException, AxiomSyntaxException { + return from(null, CharStreams.fromStream(stream)); + } + + public static AxiomStatementSource from(String sourceName, CharStream stream) throws AxiomSyntaxException { + + AxiomLexer lexer = new AxiomLexer(stream); + AxiomParser parser = new AxiomParser(new CommonTokenStream(lexer)); + + lexer.removeErrorListeners(); + parser.removeErrorListeners(); + AxiomErrorListener errorListener = new AxiomErrorListener(sourceName); + parser.addErrorListener(errorListener); + StatementContext statement = parser.statement(); + errorListener.validate(); + return new AxiomStatementSource(statement); + } + + private AxiomStatementSource(StatementContext statement) { + this.root = statement; + } + + @Override + public String getModelName() { + return root.argument().identifier().localIdentifier().getText(); + } + + @Override + public String getNamespace() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getDescription() { + // TODO Auto-generated method stub + return null; + } + + public void stream(AxiomIdentifierResolver resolver, AxiomStatementStreamListener listener) { + stream(resolver, listener, Optional.empty()); + } + + private void stream(AxiomIdentifierResolver resolver, AxiomStatementStreamListener listener, + Optional> emitOnly) { + AxiomAntlrVisitor visitor = new AxiomAntlrVisitor<>(resolver, listener, emitOnly.orElse(null)); + visitor.visit(root); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java new file mode 100644 index 00000000000..a5ba3f42742 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java @@ -0,0 +1,63 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomStatement; +import com.evolveum.axiom.lang.api.AxiomStatementStreamListener; +import com.evolveum.axiom.lang.impl.AxiomStatementImpl.Builder; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + + +public class AxiomStatementStreamBuilder implements AxiomStatementStreamListener { + + + private static final AxiomIdentifier ROOT = AxiomIdentifier.from("root","root"); + + private final Deque queue = new LinkedList<>(); + private final AxiomStatementImpl.Builder result; + + public AxiomStatementStreamBuilder() { + result = createBuilder(ROOT); + queue.offerFirst(result); + } + + public static AxiomStatementStreamBuilder create() { + return new AxiomStatementStreamBuilder(); + } + + @Override + public void argument(AxiomIdentifier identifier) { + queue.peek().setValue(identifier); + } + + @Override + public void argument(String identifier) { + queue.peek().setValue(identifier); + } + + @Override + public void startStatement(AxiomIdentifier statement) { + queue.offerFirst(createBuilder(statement)); + } + + private Builder createBuilder(AxiomIdentifier statement) { + return new AxiomStatementImpl.Builder<>(statement); + } + + @Override + public void endStatement() { + Builder current = queue.poll(); + Builder parent = queue.peek(); + parent.add(current.build()); + } + + public AxiomStatement result() { + AxiomStatement resultHolder = result.build(); + return Iterables.getOnlyElement(resultHolder.children()); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java new file mode 100644 index 00000000000..c25fe2304e8 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java @@ -0,0 +1,61 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; + +import org.jetbrains.annotations.Nullable; + +public class AxiomSyntaxException extends Exception { + + + private final String source; + private final int line; + private final int charPositionInLine; + + public AxiomSyntaxException(@Nullable final String source, final int line, + final int charPositionInLine, final String message) { + this(source, line, charPositionInLine, message, null); + } + + public AxiomSyntaxException(@Nullable final String source, final int line, + final int charPositionInLine, final String message, @Nullable final Throwable cause) { + super(message, cause); + this.source = source; + this.line = line; + this.charPositionInLine = charPositionInLine; + } + + public final Optional getSource() { + return Optional.ofNullable(source); + } + + public final int getLine() { + return line; + } + + public final int getCharPositionInLine() { + return charPositionInLine; + } + + public String getFormattedMessage() { + final StringBuilder sb = new StringBuilder(getMessage()); + if (source != null) { + sb.append(" in source "); + sb.append(source); + } + if (line != 0) { + sb.append(" on line "); + sb.append(line); + if (charPositionInLine != 0) { + sb.append(" character "); + sb.append(charPositionInLine); + } + } + return sb.toString(); + } + + @Override + public String toString() { + return this.getClass().getName() + ": " + getFormattedMessage(); + } + +} diff --git a/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java b/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java new file mode 100644 index 00000000000..21cf18310b7 --- /dev/null +++ b/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java @@ -0,0 +1,36 @@ +package com.evolveum.axiom.lang.test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.FileInputStream; +import java.io.IOException; + +import org.testng.annotations.Test; + +import com.evolveum.axiom.lang.api.AxiomStatement; +import com.evolveum.axiom.lang.impl.AxiomIdentifierResolver; +import com.evolveum.axiom.lang.impl.AxiomStatementSource; +import com.evolveum.axiom.lang.impl.AxiomStatementStreamBuilder; +import com.evolveum.axiom.lang.impl.AxiomSyntaxException; +import com.evolveum.midpoint.tools.testng.AbstractUnitTest; + +public class TestAxiomParser extends AbstractUnitTest { + + private static String COMMON_DIR_PATH = "src/test/resources/"; + private static String NAME = "model-header"; + + + @Test + public void moduleHeaderTest() throws IOException, AxiomSyntaxException { + AxiomStatementSource statementSource = AxiomStatementSource.from(new FileInputStream(COMMON_DIR_PATH + NAME +".axiom")); + assertNotNull(statementSource); + assertEquals(statementSource.getModelName(), NAME); + + AxiomStatementStreamBuilder builder = AxiomStatementStreamBuilder.create(); + + statementSource.stream(AxiomIdentifierResolver.AXIOM_DEFAULT_NAMESPACE, builder); + AxiomStatement root = builder.result(); + assertNotNull(root); + } +} diff --git a/infra/axiom/src/test/resources/axiom-self-described.axiom b/infra/axiom/src/test/resources/axiom-self-described.axiom new file mode 100644 index 00000000000..fb57f95719d --- /dev/null +++ b/infra/axiom/src/test/resources/axiom-self-described.axiom @@ -0,0 +1,63 @@ +model axiom { + + statement statement { + type Statement; + argument identifier; + description """ + Declares statement. + """; + } + + complexType Statement { + property identifier { + type builtin:Identifier; + } + property type { + type builtin:TypeReference; + } + property argument { + type builtin:ItemPath; + } + property description { + type builtin:DocumentationString; + } + } + + statement complexType { + type ComplexType; + argument identifier; + } + + complexType ComplexType { + property identifier { + type builtin:Identifier; + } + property property { + type Property; + } + } + + complexType Property { + property identifier { + type builtin:Identifier; + } + property type { + type builtin:TypeReference; + } + property required { + type builtin:boolean; + } + } + + statement model { + type Model; + argument name; + } + + statement complexType { + property identifier { + type builtin:Identifier; + } + } + +} \ No newline at end of file diff --git a/infra/axiom/src/test/resources/model-header.axiom b/infra/axiom/src/test/resources/model-header.axiom new file mode 100644 index 00000000000..de6efb06c99 --- /dev/null +++ b/infra/axiom/src/test/resources/model-header.axiom @@ -0,0 +1,16 @@ +model model-header { + + description """ + This is showcase of baseline Axiom model header. + + Standard model header should contain: + - description (optional) + - namespace (required) + - version (required) + - prefix (optional + """; + + namespace "https://ns.evolveum.com/example/axiom/model-header"; + version "0.0.1"; + +} \ No newline at end of file diff --git a/infra/pom.xml b/infra/pom.xml index f4ae2546837..367caf9f695 100644 --- a/infra/pom.xml +++ b/infra/pom.xml @@ -21,6 +21,7 @@ midPoint Infrastructure + axiom util schema schema-pure-jaxb