Skip to content

Commit

Permalink
implement conditional block
Browse files Browse the repository at this point in the history
Signed-off-by: tvallin <thibault.vallin@oracle.com>
  • Loading branch information
tvallin committed Sep 22, 2023
1 parent d010f93 commit d0c7421
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ private static final class ReaderImpl implements SimpleXMLParser.Reader {
private String qName;
private Map<String, Value> attrs;
private LinkedList<Context> stack;
private LinkedList<String> expressions;
private LinkedList<Condition.Builder> conditions;
private Context ctx;
private Script.Builder scriptBuilder;

Expand All @@ -188,6 +190,8 @@ private static final class ReaderImpl implements SimpleXMLParser.Reader {
Script read(InputStream is, Path path) throws IOException {
this.path = Objects.requireNonNull(path, "path is null");
stack = new LinkedList<>();
expressions = new LinkedList<>();
conditions = new LinkedList<>();
parser = SimpleXMLParser.create(is, this);
parser.parse();
if (scriptBuilder == null) {
Expand All @@ -198,7 +202,7 @@ Script read(InputStream is, Path path) throws IOException {

@Override
public void startElement(String qName, Map<String, String> attrs) {
this.qName = qName;
this.qName = qName.replace("-", "");
this.attrs = Maps.mapValue(attrs, DynamicValue::create);
Location location = Location.of(path, parser.lineNumber(), parser.charNumber());
info = BuilderInfo.of(loader, path, location);
Expand Down Expand Up @@ -241,6 +245,11 @@ public void endElement(String name) {

void processElement() {
switch (qName) {
case "if":
case "else-if":
case "else":
processCondition();
break;
case "directory":
case "help":
stack.push(new Context(ctx.state, new ValueBuilder(ctx.builder, qName)));
Expand Down Expand Up @@ -338,6 +347,26 @@ void processValidation() {
}
}

void processCondition() {
Block.Kind kind = blockKind();
String expression;
switch (kind) {
case IF:
case ELSEIF:
expression = processXmlEscapes(attrs.get("expr").asString());
expressions.push(expression);
conditions.add(Condition.builder(info).expression(expression));
break;
case ELSE:
expression = expressions.pop();
conditions.add(Condition.builder(info).expression(String.format("!(%s)", expression)));
break;
default:
throw new XMLReaderException(String.format(
"Invalid input block: %s. { element=%s }", kind, qName));
}
}

void processPreset() {
Block.Builder builder;
Block.Kind kind = blockKind();
Expand Down Expand Up @@ -477,7 +506,11 @@ void addChild(State nextState, Node.Builder<? extends Node, ?> builder) {
String expr = processXmlEscapes(ifExpr.asString());
ctx.builder.addChild(Condition.builder(info).expression(expr).then(builder));
} else {
ctx.builder.addChild(builder);
if (!conditions.isEmpty()) {
ctx.builder.addChild(conditions.pop().then(builder));
} else {
ctx.builder.addChild(builder);
}
}
stack.push(new Context(nextState, builder));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,21 @@ public enum Kind {
* Invoke.
*/
INVOKE,

/**
* If.
*/
IF,

/**
* Else.
*/
ELSE,

/**
* Else-if.
*/
ELSEIF
}

/**
Expand Down
91 changes: 91 additions & 0 deletions archetype/engine-v2/src/main/schema/archetype.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,92 @@
</xs:attribute>
</xs:complexType>

<xs:attributeGroup name="condition-group">
<xs:attribute type="xs:string" name="expr">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Expression to guard nested blocks.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>

<xs:group name="directive-condition-group">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Condition for nested blocks.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="if" type="a:directive-if-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else-if" type="a:directive-if-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else" type="a:directive-if-type" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

<xs:complexType name="directive-if-type">
<xs:group ref="a:directive-group"/>
<xs:attribute name="expr" type="xs:string"/>
</xs:complexType>

<xs:group name="condition-map-group">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Condition for nested blocks.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="if" type="a:map-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else-if" type="a:map-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else" type="a:map-type" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

<xs:group name="condition-list-group">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Condition for nested blocks.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="if" type="a:list-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else-if" type="a:list-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else" type="a:list-type" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

<xs:group name="condition-model-group">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Condition for nested blocks.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="if" type="a:model-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else-if" type="a:model-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else" type="a:model-type" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

<xs:group name="condition-output-group">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
<xs:documentation source="description">
Condition for nested blocks.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="if" type="a:output-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else-if" type="a:output-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="else" type="a:output-type" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

<xs:complexType name="glob-type">
<xs:annotation>
<xs:documentation source="version">2.0</xs:documentation>
Expand Down Expand Up @@ -404,6 +490,7 @@
<xs:element name="value" type="a:keyed-value-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="list" type="a:keyed-list-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="map" type="a:keyed-map-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:group ref="a:condition-map-group" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attributeGroup ref="a:if-group"/>
<xs:attributeGroup ref="a:order-group"/>
Expand All @@ -428,6 +515,7 @@
<xs:element name="value" type="a:value-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="list" type="a:list-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="map" type="a:map-type" minOccurs="0" maxOccurs="unbounded"/>
<xs:group ref="a:condition-list-group" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attributeGroup ref="a:if-group"/>
<xs:attributeGroup ref="a:order-group"/>
Expand Down Expand Up @@ -788,6 +876,7 @@
<xs:element name="inputs" type="a:input-types"/>
<xs:element name="step" type="a:step-type"/>
<xs:element name="validations" type="a:validations-type"/>
<xs:group ref="a:directive-condition-group"/>
</xs:choice>
</xs:sequence>
</xs:group>
Expand Down Expand Up @@ -1044,6 +1133,7 @@
<xs:element name="value" type="a:keyed-value-type"/>
<xs:element name="list" type="a:keyed-list-type"/>
<xs:element name="map" type="a:keyed-map-type"/>
<xs:group ref="a:condition-model-group"/>
</xs:choice>
<xs:attributeGroup ref="a:if-group"/>
</xs:complexType>
Expand Down Expand Up @@ -1314,6 +1404,7 @@
<xs:attributeGroup ref="a:if-group"/>
</xs:complexType>
</xs:element>
<xs:group ref="condition-output-group"/>
</xs:choice>
<xs:choice minOccurs="0">
<xs:element name="model" type="a:model-type"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
import io.helidon.build.archetype.engine.v2.context.Context;
import io.helidon.build.archetype.engine.v2.context.ContextValue;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static io.helidon.build.archetype.engine.v2.TestHelper.load;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

/**
Expand Down Expand Up @@ -143,6 +145,24 @@ void testInvocationCondition() {
assertThat(values.size(), is(0));
}

@Test
void testConditionalBlock() {
Script script = load("controller/conditional-block.xml");
Context context = Context.create();

Controller.walk(script, context);

assertThat(context.getValue("success1"), is(notNullValue()));
assertThat(context.getValue("success2"), is(notNullValue()));
assertThat(context.getValue("success3"), is(notNullValue()));
assertThat(context.getValue("outside1"), is(notNullValue()));
assertThat(context.getValue("outside2"), is(notNullValue()));
assertThat(context.getValue("outside3"), is(notNullValue()));
assertThat(context.getValue("failure1"), is(nullValue()));
assertThat(context.getValue("failure2"), is(nullValue()));
assertThat(context.getValue("failure3"), is(nullValue()));
}

private static List<String> modelValues(Block block, Context context) {
List<String> values = new LinkedList<>();
Controller.walk(new Model.Visitor<>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,4 +727,36 @@ public VisitResult visitValue(Model.Value value, Void arg) {
}, script);
assertThat(colors, contains("yellow", "green", "red", "blue"));
}

@Test
void testConditionalBlock() {
Script script = load("loader/conditional-block.xml");
int[] successes = new int[]{0};
int[] failures = new int[]{0};
final String success = "success";
final String failure = "failure";
walk(new VisitorAdapter<>(null, null, null, null) {
@Override
public VisitResult visitPreset(Preset preset, Void arg) {
String value = preset.value().asString();
String path = preset.path();
switch (value) {
case success:
assertThat(path, is(success));
successes[0]++;
break;
case failure:
assertThat(path, is(failure));
failures[0]++;
break;
default:
Assertions.fail("Unexpected value: " + value);
}
return VisitResult.CONTINUE;
}
}, script);

assertThat(successes[0], is(3));
assertThat(failures[0], is(3));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2023 Oracle and/or its affiliates.
Licensed 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
http://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.
-->
<archetype-script xmlns="https://helidon.io/archetype/2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://helidon.io/archetype/2.0 file:/archetype.xsd">

<if expr="true">
<presets>
<text path="success1">success</text>
</presets>
</if>
<presets>
<text path="outside1">success</text>
</presets>
<if expr="false">
<presets>
<text path="failure1" if="false">failure</text>
</presets>
</if>
<else-if expr="true">
<presets>
<text path="success2">success</text>
</presets>
</else-if>
<presets>
<text path="outside2">success</text>
</presets>
<if expr="false">
<presets>
<text path="failure2" if="false">failure</text>
</presets>
</if>
<else-if expr="false">
<presets>
<text path="failure3" if="false">failure</text>
</presets>
</else-if>
<else>
<presets>
<text path="success3">success</text>
</presets>
</else>
<presets>
<text path="outside3">success</text>
</presets>
</archetype-script>
Loading

0 comments on commit d0c7421

Please sign in to comment.