Skip to content

Commit

Permalink
Cleanup processor and parser (#6)
Browse files Browse the repository at this point in the history
* Simplify parser and processor logic

* Fix style

* Fix nested direct fxml file

* Fix tests

* Use separate object for code values

* Expand expression tests
  • Loading branch information
Sheikah45 authored Jun 3, 2024
1 parent 79fe37a commit 2901c85
Show file tree
Hide file tree
Showing 76 changed files with 2,988 additions and 1,630 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.sheikah45.fx2j.parser;

import io.github.sheikah45.fx2j.parser.attribute.AssignableAttribute;
import io.github.sheikah45.fx2j.parser.attribute.ControllerAttribute;
import io.github.sheikah45.fx2j.parser.attribute.DefaultNameSpaceAttribute;
import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute;
Expand All @@ -8,11 +9,13 @@
import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute;
import io.github.sheikah45.fx2j.parser.attribute.NameSpaceAttribute;
import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute;
import io.github.sheikah45.fx2j.parser.element.AssignableElement;
import io.github.sheikah45.fx2j.parser.element.ClassInstanceElement;
import io.github.sheikah45.fx2j.parser.element.ConstantElement;
import io.github.sheikah45.fx2j.parser.element.CopyElement;
import io.github.sheikah45.fx2j.parser.element.DeclarationElement;
import io.github.sheikah45.fx2j.parser.element.DefineElement;
import io.github.sheikah45.fx2j.parser.element.ElementContent;
import io.github.sheikah45.fx2j.parser.element.FactoryElement;
import io.github.sheikah45.fx2j.parser.element.FxmlElement;
import io.github.sheikah45.fx2j.parser.element.IncludeElement;
Expand All @@ -24,7 +27,6 @@
import io.github.sheikah45.fx2j.parser.element.ScriptSource;
import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement;
import io.github.sheikah45.fx2j.parser.element.ValueElement;
import io.github.sheikah45.fx2j.parser.property.Concrete;
import io.github.sheikah45.fx2j.parser.property.Expression;
import io.github.sheikah45.fx2j.parser.property.Handler;
import io.github.sheikah45.fx2j.parser.property.Value;
Expand Down Expand Up @@ -69,14 +71,15 @@ public static FxmlComponents readFxml(Path filePath) {
}
}

private static ClassInstanceElement.Content createContent(Element element) {
List<FxmlAttribute> attributes = createAttributes(element);
List<FxmlElement> children = createChildren(element);
return new ClassInstanceElement.Content(attributes, children, retrieveInnerValue(element));
private static ElementContent<?, ?> createContent(Element element) {
List<FxmlAttribute> fxmlAttributes = createFxmlAttributes(element);
List<FxmlElement> fxmlElements = createFxmlElements(element);
return new ElementContent<>(fxmlAttributes, fxmlElements, retrieveInnerValue(element));
}

private static Value.Single retrieveInnerValue(Element element) {
return createPropertyValue(retrieveInnerText(element));
private static Value retrieveInnerValue(Element element) {
String innerText = retrieveInnerText(element);
return innerText.isBlank() ? new Value.Empty() : createPropertyValue(innerText);
}

private static String retrieveInnerText(Element element) {
Expand All @@ -96,39 +99,7 @@ private static String retrieveInnerText(Element element) {
.orElse("");
}

private static Value createPropertyValue(Element element) {
List<Value.Single> values = new ArrayList<>();

createAttributes(element).stream().map(attribute -> {
if (!(attribute instanceof FxmlAttribute.CommonAttribute commonAttribute)) {
throw new ParseException("property attribute contains a non common attribute");
}

return commonAttribute;
}).map(Concrete.Attribute::new).forEach(values::add);

createChildren(element)
.stream()
.map(Concrete.Element::new)
.forEach(values::add);

Value.Single innerValue = retrieveInnerValue(element);
if (!(innerValue instanceof Concrete.Empty)) {
values.add(innerValue);
}

if (values.isEmpty()) {
return new Concrete.Empty();
}

if (values.size() == 1) {
return values.getFirst();
}

return new Value.Multi(values);
}

private static List<FxmlAttribute> createAttributes(Element element) {
private static List<FxmlAttribute> createFxmlAttributes(Element element) {
NamedNodeMap attributesNodeMap = element.getAttributes();
int attrLength = attributesNodeMap.getLength();
return IntStream.range(0, attrLength)
Expand All @@ -139,7 +110,7 @@ private static List<FxmlAttribute> createAttributes(Element element) {
.toList();
}

private static List<FxmlElement> createChildren(Element element) {
private static List<FxmlElement> createFxmlElements(Element element) {
NodeList childNodes = element.getChildNodes();
int childrenLength = childNodes.getLength();

Expand All @@ -162,12 +133,44 @@ private static FxmlElement createFxmlElement(Element element) {
case String tag -> {
int separatorIndex = tag.lastIndexOf(".");
if (Character.isLowerCase(tag.charAt(separatorIndex + 1))) {
ElementContent<?, ?> content = createContent(element);
List<AssignableElement> assignableElements = content.elements().stream().map(fxmlElement -> {
if (!(fxmlElement instanceof AssignableElement assignable)) {
throw new ParseException(
"A property element cannot contain unassignable values");
}

return assignable;
}).toList();
List<AssignableAttribute> assignableAttributes = content.attributes()
.stream()
.map(fxmlAttribute -> {
if (!(fxmlAttribute instanceof AssignableAttribute assignable)) {
throw new ParseException(
"A property attribute cannot contain unassignable values");
}

return assignable;
})
.toList();

if (separatorIndex == -1) {
yield new InstancePropertyElement(tag, createPropertyValue(element));
yield new InstancePropertyElement(tag,
new ElementContent<>(assignableAttributes, assignableElements,
content.value()));
} else {
if (!content.attributes().isEmpty()) {
throw new ParseException("static property elements cannot have attributes");
}

if (!content.elements().isEmpty()) {
throw new ParseException("static property elements cannot have elements");
}

yield new StaticPropertyElement(tag.substring(0, separatorIndex),
tag.substring(separatorIndex + 1),
createPropertyValue(element));
new ElementContent<>(assignableAttributes, assignableElements,
content.value()));
}
} else {
yield createInstanceElement(element);
Expand All @@ -185,14 +188,14 @@ private static ClassInstanceElement createInstanceElement(Element element) {
throw new ParseException("Multiple initialization attributes specified: %s".formatted(element));
}

ClassInstanceElement.Content content = createContent(element);
ElementContent<?, ?> content = createContent(element);

if (factory != null) {
return new FactoryElement(className, factory, content);
}

if (value != null) {
return new ValueElement(className, createPropertyValue(value), content);
return new ValueElement(className, value, content);
}

if (constant != null) {
Expand All @@ -208,22 +211,50 @@ private static RootElement createRootElement(Element element) {
}

private static DefineElement createDefineElement(Element element) {
List<ClassInstanceElement> children = createChildren(element).stream().map(child -> {
if (!(child instanceof ClassInstanceElement classInstanceElement)) {
throw new ParseException("define element contains a non class instance element");
ElementContent<?, ?> content = createContent(element);

if (!content.attributes().isEmpty()) {
throw new ParseException("fx:define element cannot have attributes");
}

if (!(content.value() instanceof Value.Empty)) {
throw new ParseException("fx:define element cannot have an inner value");
}

List<ClassInstanceElement> instanceElements = content.elements().stream().map(fxmlElement -> {
if (!(fxmlElement instanceof ClassInstanceElement classInstanceElement)) {
throw new ParseException("fx:define element contains a non class instance element");
}

return classInstanceElement;
}).toList();

return new DefineElement(children);
return new DefineElement(instanceElements);
}

private static ScriptElement createScriptElement(Element element) {
return removeAndGetValueIfPresent(element, "source").map(Path::of).map(source -> {
Optional<String> charset = removeAndGetValueIfPresent(element, "charset");
return new ScriptElement(new ScriptSource.Reference(source, charset.map(Charset::forName).orElse(null)));
}).orElseGet(() -> new ScriptElement(new ScriptSource.Inline(retrieveInnerText(element))));
Path source = removeAndGetValueIfPresent(element, "source").map(Path::of).orElse(null);
Charset charset = removeAndGetValueIfPresent(element, "charset").map(Charset::forName)
.orElse(StandardCharsets.UTF_8);
ElementContent<?, ?> content = createContent(element);

ScriptSource scriptSource;
if (source == null) {
if (!content.elements().isEmpty() || !content.attributes().isEmpty()) {
throw new ParseException(
"fx:script with inline source cannot have any elements or attributes other than charset");
}
scriptSource = new ScriptSource.Inline(retrieveInnerText(element), charset);
} else {
if (!content.attributes().isEmpty() ||
!content.elements().isEmpty() || !(content.value() instanceof Value.Empty)) {
throw new ParseException(
"fx:script with reference source cannot have any elements, attributes other than charset and source, or an inner value");
}
scriptSource = new ScriptSource.Reference(source, charset);
}

return new ScriptElement(scriptSource);
}

private static CopyElement createCopyElement(Element element) {
Expand Down Expand Up @@ -274,16 +305,15 @@ yield new StaticPropertyAttribute(name.substring(0, separatorIndex),
};
}

private static Value.Single createPropertyValue(String value) {
private static Value createPropertyValue(String value) {
return switch (value) {
case String val when val.startsWith("@") -> new Concrete.Location(Path.of(val.substring(1)));
case String val when val.startsWith("%") -> new Concrete.Resource(val.substring(1));
case String val when val.startsWith("@") -> new Value.Location(Path.of(val.substring(1)));
case String val when val.startsWith("%") -> new Value.Resource(val.substring(1));
case String val when val.startsWith("${") && val.endsWith("}") ->
Expression.parse(val.substring(2, val.length() - 1));
case String val when val.startsWith("$") -> new Concrete.Reference(val.substring(1));
case String val when val.startsWith("\\") -> new Concrete.Literal(val.substring(1));
case String val when val.isBlank() -> new Concrete.Empty();
case String val -> new Concrete.Literal(val);
case String val when val.startsWith("$") -> new Value.Reference(val.substring(1));
case String val when val.startsWith("\\") -> new Value.Literal(val.substring(1));
case String val -> new Value.Literal(val);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public Expression visitAdditive(BindExpressionParser.AdditiveContext ctx) {
@Override
public Expression visitStringLiteral(BindExpressionParser.StringLiteralContext ctx) {
String text = ctx.getText();
return new Expression.Str(text.substring(1, text.length() - 1));
return new Expression.String(text.substring(1, text.length() - 1));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.sheikah45.fx2j.parser.attribute;

sealed public interface AssignableAttribute extends CommonAttribute
permits EventHandlerAttribute, InstancePropertyAttribute {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.sheikah45.fx2j.parser.attribute;

sealed public interface CommonAttribute extends FxmlAttribute
permits AssignableAttribute, StaticPropertyAttribute {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.github.sheikah45.fx2j.parser.utils.StringUtils;

public record ControllerAttribute(String className) implements FxmlAttribute.SpecialAttribute {
public record ControllerAttribute(String className) implements SpecialAttribute {
public ControllerAttribute {
if (StringUtils.isNullOrBlank(className)) {
throw new IllegalArgumentException("className cannot be blank or null");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package io.github.sheikah45.fx2j.parser.attribute;

import io.github.sheikah45.fx2j.parser.utils.StringUtils;

import java.net.URI;
import java.util.Objects;

public record DefaultNameSpaceAttribute(URI location) implements FxmlAttribute.SpecialAttribute {
public record DefaultNameSpaceAttribute(URI location) implements SpecialAttribute {
public DefaultNameSpaceAttribute {
Objects.requireNonNull(location, "location cannot be null");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.Objects;

public record EventHandlerAttribute(String eventName, Handler handler) implements FxmlProperty.EventHandler,
FxmlAttribute.CommonAttribute {
AssignableAttribute {
public EventHandlerAttribute {
Objects.requireNonNull(eventName, "eventName cannot be null");
Objects.requireNonNull(handler, "handler cannot be null");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
package io.github.sheikah45.fx2j.parser.attribute;

public sealed interface FxmlAttribute {

sealed interface SpecialAttribute extends FxmlAttribute
permits ControllerAttribute, DefaultNameSpaceAttribute, IdAttribute, NameSpaceAttribute {}

sealed interface CommonAttribute extends FxmlAttribute
permits EventHandlerAttribute, InstancePropertyAttribute, StaticPropertyAttribute {}

}
public sealed interface FxmlAttribute permits CommonAttribute, SpecialAttribute {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.github.sheikah45.fx2j.parser.utils.StringUtils;

public record IdAttribute(String value) implements FxmlAttribute.SpecialAttribute {
public record IdAttribute(String value) implements SpecialAttribute {
public IdAttribute {
if (StringUtils.isNullOrBlank(value)) {
throw new IllegalArgumentException("id cannot be blank or null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

import java.util.Objects;

public record InstancePropertyAttribute(String property, Value.Single value) implements FxmlProperty.Instance,
FxmlAttribute.CommonAttribute {
public record InstancePropertyAttribute(String propertyName, Value value) implements FxmlProperty.Instance,
AssignableAttribute {
public InstancePropertyAttribute {
if (StringUtils.isNullOrBlank(property)) {
throw new IllegalArgumentException("property cannot be blank or null");
if (StringUtils.isNullOrBlank(propertyName)) {
throw new IllegalArgumentException("propertyName cannot be blank or null");
}
Objects.requireNonNull(value, "value cannot be null");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.net.URI;
import java.util.Objects;

public record NameSpaceAttribute(String namespace, URI location) implements FxmlAttribute.SpecialAttribute {
public record NameSpaceAttribute(String namespace, URI location) implements SpecialAttribute {
public NameSpaceAttribute {
Objects.requireNonNull(location, "location cannot be null");
if (StringUtils.isNullOrBlank(namespace)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.sheikah45.fx2j.parser.attribute;

sealed public interface SpecialAttribute extends FxmlAttribute
permits ControllerAttribute, DefaultNameSpaceAttribute, IdAttribute, NameSpaceAttribute {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

import java.util.Objects;

public record StaticPropertyAttribute(String className, String property, Value.Single value)
implements FxmlAttribute.CommonAttribute, FxmlProperty.Static {
public record StaticPropertyAttribute(String className, String property, Value value)
implements CommonAttribute, FxmlProperty.Static {
public StaticPropertyAttribute {
if (StringUtils.isNullOrBlank(className)) {
throw new IllegalArgumentException("className cannot be blank or null");
}
if (StringUtils.isNullOrBlank(property)) {
throw new IllegalArgumentException("property cannot be blank or null");
throw new IllegalArgumentException("propertyName cannot be blank or null");
}
Objects.requireNonNull(value, "value cannot be null");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.github.sheikah45.fx2j.parser.element;

sealed public interface AssignableElement extends FxmlElement permits ClassInstanceElement, InstancePropertyElement {}
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
package io.github.sheikah45.fx2j.parser.element;

import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute;
import io.github.sheikah45.fx2j.parser.property.Value;

import java.util.List;
import java.util.Objects;

sealed public interface ClassInstanceElement extends FxmlElement
sealed public interface ClassInstanceElement extends AssignableElement
permits CopyElement, DeclarationElement, IncludeElement, ReferenceElement {
Content content();
ElementContent<?, ?> content();

record Content(List<FxmlAttribute> attributes, List<FxmlElement> children, Value.Single body) {
public Content {
Objects.requireNonNull(attributes, "attributes cannot be null");
Objects.requireNonNull(children, "children cannot be null");
Objects.requireNonNull(body, "text cannot be null");
attributes = List.copyOf(attributes);
children = List.copyOf(children);
}
}
}
Loading

0 comments on commit 2901c85

Please sign in to comment.