diff --git a/src/main/java/org/cqfn/astranaut/codegen/java/LiteralGenerator.java b/src/main/java/org/cqfn/astranaut/codegen/java/LiteralGenerator.java index 1b86a47f..fbf902e6 100644 --- a/src/main/java/org/cqfn/astranaut/codegen/java/LiteralGenerator.java +++ b/src/main/java/org/cqfn/astranaut/codegen/java/LiteralGenerator.java @@ -74,7 +74,7 @@ public void createSpecificEntitiesInNodeClass(final Klass klass) { getter.suppressWarning("PMD.BooleanGetMethodName"); } klass.addMethod(getter); - final Method list = new Method("List", "getChildrenList"); + final Method list = new Method(Strings.TYPE_NODE_LIST, "getChildrenList"); list.makePublic(); list.setBody("return Collections.emptyList();"); klass.addMethod(list); diff --git a/src/main/java/org/cqfn/astranaut/codegen/java/NameGenerator.java b/src/main/java/org/cqfn/astranaut/codegen/java/NameGenerator.java new file mode 100644 index 00000000..2936ac53 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/codegen/java/NameGenerator.java @@ -0,0 +1,80 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.codegen.java; + +import java.util.Arrays; +import java.util.List; + +/** + * Generates non-repeating names (numerals) that can be used as names for variables + * when the variable name is not known. + * @since 1.0.0 + */ +public class NameGenerator { + /** + * List of names. + */ + private static final List NAMES = Arrays.asList( + "first", + "second", + "third", + "fourth", + "fifth", + "sixth", + "seventh", + "eighth", + "ninth", + "tenth", + "eleventh", + "twelfth", + "thirteenth", + "fourteenth", + "fifteenth", + "sixteenth", + "seventeenth", + "eighteenth", + "nineteenth", + "twentieth" + ); + + /** + * Current index. + */ + private int current; + + /** + * Returns the next name. + * @return A name + */ + public String nextName() { + final String name; + if (this.current < NameGenerator.NAMES.size()) { + name = NameGenerator.NAMES.get(this.current); + this.current = this.current + 1; + } else { + throw new IllegalStateException("No more names available."); + } + return name; + } +} diff --git a/src/main/java/org/cqfn/astranaut/codegen/java/NonAbstractNodeGenerator.java b/src/main/java/org/cqfn/astranaut/codegen/java/NonAbstractNodeGenerator.java index 5b7c5bd1..a4f77c71 100644 --- a/src/main/java/org/cqfn/astranaut/codegen/java/NonAbstractNodeGenerator.java +++ b/src/main/java/org/cqfn/astranaut/codegen/java/NonAbstractNodeGenerator.java @@ -48,6 +48,11 @@ public abstract class NonAbstractNodeGenerator implements RuleGenerator { */ private boolean arraylist; + /** + * Flag indicating that a non-trivial validator has been generated. + */ + private boolean validator; + @Override public final Set createUnits(final Context context) { final String name = this.getRule().getName(); @@ -165,6 +170,13 @@ protected void needArrayListClass() { this.arraylist = true; } + /** + * Sets a flag indicating that a non-trivial validator has been generated. + */ + protected void hasNonTrivialValidator() { + this.validator = true; + } + /** * Fills the class describing the node with fields and methods. * @param klass Class describing the node @@ -304,10 +316,10 @@ private Klass createBuilderClass(final Context context) { klass.setImplementsList(Strings.TYPE_BUILDER); klass.setVersion(context.getVersion()); NonAbstractNodeGenerator.createFragmentFieldAndSetter(klass); + this.createSpecificEntitiesInBuilderClass(klass); this.createDataSetter(klass); this.createChildrenListSetter(klass); this.createValidator(klass); - this.createSpecificEntitiesInBuilderClass(klass); this.createNodeCreator(klass); return klass; } @@ -350,7 +362,7 @@ private void createDataSetter(final Klass klass) { private void createChildrenListSetter(final Klass klass) { final Method method = new Method(Strings.TYPE_BOOLEAN, "setChildrenList"); method.makePublic(); - method.addArgument("List", "list"); + method.addArgument(Strings.TYPE_NODE_LIST, "list"); method.setBody(this.getChildrenListSetterBody()); klass.addMethod(method); } @@ -375,6 +387,11 @@ private void createNodeCreator(final Klass klass) { method.makePublic(); final String name = this.getRule().getName(); final List lines = new ArrayList<>(16); + if (this.validator) { + lines.add("if (!this.isValid()) {"); + lines.add("throw new IllegalStateException();"); + lines.add("}"); + } lines.add(String.format("final %s node = new %s();", name, name)); lines.add("node.fragment = this.fragment;"); this.fillNodeCreator(lines); diff --git a/src/main/java/org/cqfn/astranaut/codegen/java/RegularNodeGenerator.java b/src/main/java/org/cqfn/astranaut/codegen/java/RegularNodeGenerator.java index ed7a966e..66e621a0 100644 --- a/src/main/java/org/cqfn/astranaut/codegen/java/RegularNodeGenerator.java +++ b/src/main/java/org/cqfn/astranaut/codegen/java/RegularNodeGenerator.java @@ -24,6 +24,8 @@ package org.cqfn.astranaut.codegen.java; import java.util.List; +import java.util.Locale; +import org.cqfn.astranaut.dsl.ChildDescriptorExt; import org.cqfn.astranaut.dsl.NodeDescriptor; import org.cqfn.astranaut.dsl.RegularNodeDescriptor; @@ -38,12 +40,18 @@ public final class RegularNodeGenerator extends NonAbstractNodeGenerator { */ private final RegularNodeDescriptor rule; + /** + * Names of variables that store child nodes. + */ + private final String[] names; + /** * Constructor. * @param rule The rule that describes regular node */ public RegularNodeGenerator(final RegularNodeDescriptor rule) { this.rule = rule; + this.names = RegularNodeGenerator.generateVariableNames(rule); } @Override @@ -53,13 +61,23 @@ public NodeDescriptor getRule() { @Override public void createSpecificEntitiesInNodeClass(final Klass klass) { + final Method list = new Method(Strings.TYPE_NODE_LIST, "getChildrenList"); + list.makePublic(); if (this.rule.getExtChildTypes().isEmpty()) { this.needCollectionsClass(); - final Method list = new Method("List", "getChildrenList"); - list.makePublic(); list.setBody("return Collections.emptyList();"); - klass.addMethod(list); + } else { + list.setBody("return this.children;"); + this.createFieldsWithGettersForTaggedChildren(klass); + final Field children = new Field( + Strings.TYPE_NODE_LIST, + "children", + "List of child nodes" + ); + children.makePrivate(); + klass.addField(children); } + klass.addMethod(list); } @Override @@ -69,17 +87,31 @@ public String getDataGetterBody() { @Override public String getChildCountGetterBody() { - return "return 0;"; + final String body; + if (this.names.length == 0) { + body = "return 0;"; + } else { + body = "return this.children.size();"; + } + return body; } @Override public String getChildGetterBody() { - return "throw new IndexOutOfBoundsException();"; + final String body; + if (this.names.length == 0) { + body = "throw new IndexOutOfBoundsException();"; + } else { + body = "return this.children.get(index);"; + } + return body; } @Override public void createSpecificEntitiesInBuilderClass(final Klass klass) { - this.getClass(); + if (this.names.length > 0) { + this.createFieldsWithSettersForTaggedChildren(klass); + } } @Override @@ -94,11 +126,150 @@ public String getChildrenListSetterBody() { @Override public String getValidatorBody() { - return "return true;"; + boolean flag = false; + final StringBuilder builder = new StringBuilder(); + final List children = this.rule.getExtChildTypes(); + for (int index = 0; index < children.size(); index = index + 1) { + final ChildDescriptorExt descriptor = children.get(index); + if (descriptor.isOptional()) { + continue; + } + if (flag) { + builder.append(" && "); + } + flag = true; + builder.append(this.names[index]).append(" != null"); + } + final String expression; + if (flag) { + expression = builder.toString(); + this.hasNonTrivialValidator(); + } else { + expression = "true"; + } + return String.format("return %s;", expression); } @Override public void fillNodeCreator(final List lines) { this.getClass(); } + + /** + * Creates fields ans getters for all tagged children. + * @param klass Class describing regular node + */ + private void createFieldsWithGettersForTaggedChildren(final Klass klass) { + for (final ChildDescriptorExt descriptor : this.rule.getExtChildTypes()) { + final String tag = descriptor.getTag(); + if (tag.isEmpty()) { + continue; + } + final Field field = new Field( + descriptor.getType(), + tag.toLowerCase(Locale.ENGLISH), + String.format("Child node with '%s' tag", tag) + ); + field.makePrivate(); + klass.addField(field); + final Method getter = new Method( + descriptor.getType(), + String.format( + "get%s%s", + tag.substring(0, 1).toUpperCase(Locale.ENGLISH), + tag.substring(1) + ), + String.format("Returns child node with '%s' tag", tag) + ); + getter.makePublic(); + if (descriptor.isOptional()) { + getter.setReturnsDescription( + String.format( + "Child node or {@code null} if the node with '%s' tag is not specified", + tag + ) + ); + } else { + getter.setReturnsDescription("Child node (can't be {@code null})"); + } + getter.setBody(String.format("return this.%s;", tag.toLowerCase(Locale.ENGLISH))); + klass.addMethod(getter); + } + } + + /** + * Creates fields and setters for all tagged children. + * @param klass Class describing builder of regular node + */ + private void createFieldsWithSettersForTaggedChildren(final Klass klass) { + final List children = this.rule.getExtChildTypes(); + for (int index = 0; index < children.size(); index = index + 1) { + final ChildDescriptorExt descriptor = children.get(index); + final String tag = descriptor.getTag(); + final String name = this.names[index]; + final String brief; + if (tag.isEmpty()) { + brief = String.format( + "%s%s node", + name.substring(0, 1).toUpperCase(Locale.ENGLISH), + name.substring(1) + ); + } else { + brief = String.format("Child node with '%s' tag", tag); + } + final Field field = new Field( + descriptor.getType(), + name, + brief + ); + field.makePrivate(); + klass.addField(field); + if (tag.isEmpty()) { + continue; + } + final Method setter = new Method( + Strings.TYPE_VOID, + String.format( + "set%s%s", + tag.substring(0, 1).toUpperCase(Locale.ENGLISH), + tag.substring(1) + ), + String.format("Sets child node with '%s' tag", tag) + ); + setter.makePublic(); + setter.addArgument(descriptor.getType(), "object", "Child node"); + if (descriptor.isOptional()) { + setter.setBody(String.format("this.%s = object;", tag.toLowerCase(Locale.ENGLISH))); + } else { + setter.setBody( + String.format( + "if (object != null) { this.%s = object; }", + tag.toLowerCase(Locale.ENGLISH) + ) + ); + } + klass.addMethod(setter); + } + } + + /** + * Generates variable names that store child nodes. + * @param rule Descriptor on the basis of which the source code will be built + * @return Array of variable names. + */ + private static String[] generateVariableNames(final RegularNodeDescriptor rule) { + final List children = rule.getExtChildTypes(); + final String[] names = new String[children.size()]; + final NameGenerator generator = new NameGenerator(); + for (int index = 0; index < children.size(); index = index + 1) { + final ChildDescriptorExt child = children.get(index); + final String tag = child.getTag(); + String name = generator.nextName(); + if (!tag.isEmpty()) { + name = tag.toLowerCase(Locale.ENGLISH); + } + names[index] = name; + } + return names; + } } diff --git a/src/main/java/org/cqfn/astranaut/codegen/java/Strings.java b/src/main/java/org/cqfn/astranaut/codegen/java/Strings.java index 58cc65f5..babdd331 100644 --- a/src/main/java/org/cqfn/astranaut/codegen/java/Strings.java +++ b/src/main/java/org/cqfn/astranaut/codegen/java/Strings.java @@ -53,6 +53,11 @@ final class Strings { */ public static final String TYPE_NODE = "Node"; + /** + * The 'List<Node>' type. + */ + public static final String TYPE_NODE_LIST = "List"; + /** * The 'Type' type. */