diff --git a/src/class_generator.py b/src/class_generator.py new file mode 100644 index 0000000..56977b3 --- /dev/null +++ b/src/class_generator.py @@ -0,0 +1,41 @@ +from src.header_generator import set_package, set_imports, render_javadoc +from src.java_method_generator import generate_fields_block, generate_getters_and_setters, generate_equals, \ + generate_hash_code +from src.java_model import JavaClass + + +def generate_java_class(java_class: JavaClass, package: str) -> str: + if len(java_class.fields) == 0: + return _empty_java_class(java_class, package) + + body = [ + set_package(package), + "", + "", + set_imports(java_class.fields), + "", + render_javadoc(java_class.description, indent_lvl=0), + f"public class {java_class.name} {{", + generate_fields_block(java_class.fields), + "", + generate_getters_and_setters(java_class.fields), + "", + generate_equals(java_class.name, java_class.fields), + "", + generate_hash_code(java_class.fields), + "}", + "" + ] + return "\n".join(body) + + +def _empty_java_class(java_class: JavaClass, package: str) -> str: + body = [ + set_package(package), + "", + render_javadoc(java_class.description, indent_lvl=0), + f"public class {java_class.name} {{", + "}", + "" + ] + return "\n".join(body) diff --git a/src/enum_generator.py b/src/enum_generator.py index 5dc43b2..0b821f9 100644 --- a/src/enum_generator.py +++ b/src/enum_generator.py @@ -1,8 +1,8 @@ import re from typing import List -from src.java_model import EnumClass, indent_lvl1, indent_lvl2, indent_lvl3 -from src.header_generator import set_package +from src.header_generator import set_package, render_javadoc +from src.java_model import EnumClass, indent def to_java_constant(value: str) -> str: @@ -21,109 +21,89 @@ def generate_enum_class(enum_class: EnumClass, package: str) -> str: "", f"import java.util.HashMap;", f"import java.util.Map;", + f"", + render_javadoc(enum_class.description, indent_lvl=0), + f"public enum {enum_class.name} {{", + _get_constants(enum_class.values), + f"", + f"{indent(1)}private final static Map CONSTANTS = new HashMap();", + _get_static_method(enum_class.name), + f"", + f"{indent(1)}private final String value;", + _get_constructor(enum_class.name), + _get_from_value_method(enum_class.name), + _get_to_string_method(), + _get_value_method(), + "}", f"" ] - enum_body.extend(_get_javadoc(enum_class.description)) - - enum_body.append(f"public enum {enum_class.name} {{") - enum_body.extend(_get_constants(enum_class.values)) - - enum_body.append(f"") - enum_body.append( - f"{indent_lvl1}private final static Map CONSTANTS = new HashMap();") - enum_body.append(f"") - enum_body.extend(_get_static_method(enum_class.name)) - - enum_body.append(f"") - enum_body.append(f"{indent_lvl1}private final String value;") - enum_body.append(f"") - enum_body.extend(_get_constructor(enum_class.name)) - - enum_body.append(f"") - enum_body.extend(_get_from_value_method(enum_class.name)) - - enum_body.append(f"") - enum_body.extend(_get_to_string_method()) - - enum_body.append(f"") - enum_body.extend(_get_value_method()) - enum_body.append("}") - enum_body.append(f"") - return "\n".join(enum_body) -def _get_javadoc(description: str) -> List[str]: - javadoc = [""] - if description is not None: - javadoc = [ - "", - "/**", - f" * {description}", - " */" - ] - return javadoc - - -def _get_constants(constants: List[str]) -> List[str]: +def _get_constants(constants: List[str]) -> str: values = [] for i, value in enumerate(constants): line_end = ";" if i == (len(constants) - 1) else "," values.append( - f'{indent_lvl1}{to_java_constant(value)}("{value}"){line_end}' + f'{indent(1)}{to_java_constant(value)}("{value}"){line_end}' ) - return values + return "\n".join(values) -def _get_static_method(class_name: str) -> List[str]: +def _get_static_method(class_name: str) -> str: body = [ - f"{indent_lvl1}static {{", - f"{indent_lvl2}for ({class_name} c : values()) {{", - f"{indent_lvl3}CONSTANTS.put(c.value, c);", - f"{indent_lvl2}}}", - f"{indent_lvl1}}}" + "", + f"{indent(1)}static {{", + f"{indent(2)}for ({class_name} c : values()) {{", + f"{indent(3)}CONSTANTS.put(c.value, c);", + f"{indent(2)}}}", + f"{indent(1)}}}" ] - return body + return "\n".join(body) -def _get_constructor(class_name: str) -> List[str]: +def _get_constructor(class_name: str) -> str: body = [ - f"{indent_lvl1}{class_name}(String value) {{", - f"{indent_lvl2}this.value = value;", - f"{indent_lvl1}}}" + "", + f"{indent(1)}{class_name}(String value) {{", + f"{indent(2)}this.value = value;", + f"{indent(1)}}}" ] - return body + return "\n".join(body) -def _get_from_value_method(class_name: str) -> List[str]: +def _get_from_value_method(class_name: str) -> str: body = [ - f"{indent_lvl1}public static {class_name} fromValue(String value) {{", - f"{indent_lvl2}{class_name} constant = CONSTANTS.get(value);", - f"{indent_lvl2}if (constant == null) {{", - f"{indent_lvl3}throw new IllegalArgumentException(value);", - f"{indent_lvl2}}} else {{", - f"{indent_lvl3}return constant;", - f"{indent_lvl2}}}", - f"{indent_lvl1}}}" + "", + f"{indent(1)}public static {class_name} fromValue(String value) {{", + f"{indent(2)}{class_name} constant = CONSTANTS.get(value);", + f"{indent(2)}if (constant == null) {{", + f"{indent(3)}throw new IllegalArgumentException(value);", + f"{indent(2)}}} else {{", + f"{indent(3)}return constant;", + f"{indent(2)}}}", + f"{indent(1)}}}" ] - return body + return "\n".join(body) -def _get_to_string_method() -> List[str]: +def _get_to_string_method() -> str: body = [ - f"{indent_lvl1}@Override", - f"{indent_lvl1}public String toString() {{", - f"{indent_lvl2}return this.value;", - f"{indent_lvl1}}}" + "", + f"{indent(1)}@Override", + f"{indent(1)}public String toString() {{", + f"{indent(2)}return this.value;", + f"{indent(1)}}}" ] - return body + return "\n".join(body) -def _get_value_method() -> List[str]: +def _get_value_method() -> str: body = [ - f"{indent_lvl1}public String value() {{", - f"{indent_lvl2}return this.value;", - f"{indent_lvl1}}}" + "", + f"{indent(1)}public String value() {{", + f"{indent(2)}return this.value;", + f"{indent(1)}}}" ] - return body + return "\n".join(body) diff --git a/src/header_generator.py b/src/header_generator.py index ba1487f..f19a6f3 100644 --- a/src/header_generator.py +++ b/src/header_generator.py @@ -1,4 +1,7 @@ import re +from typing import List, Union + +from src.java_model import Field, indent def set_package(package: str) -> str: @@ -6,3 +9,31 @@ def set_package(package: str) -> str: if not re.match(PACKAGE_REGEX, package): raise ValueError(f"Invalid package: '{package}'") return f"package {package};" + + +def set_imports(fields: List[Field]) -> str: + imports = [_imports["Objects"]] + for field in fields: + if field.type in _imports.keys(): + imports.append(_imports[field.type]) + imports.sort() + return "\n".join(imports) + + +def render_javadoc(description: Union[str, None], indent_lvl: int) -> str: + if not description: + return "" + + return "\n".join([ + "", + f"{indent(indent_lvl)}/**", + f"{indent(indent_lvl)} * {description}", + f"{indent(indent_lvl)} */" + ]) + + +_imports = { + "Date": "import java.util.Date;", + "List": "import java.util.List;", + "Objects": "import java.util.Objects;", +} diff --git a/src/java_method_generator.py b/src/java_method_generator.py index 5350fe3..9da2db1 100644 --- a/src/java_method_generator.py +++ b/src/java_method_generator.py @@ -1,8 +1,8 @@ import re from typing import List -from src.java_model import JAVA_KEYWORDS, JAVA_BUILTIN_TYPES, JAVA_LITERALS, indent_lvl1, indent_lvl2, indent_lvl3, \ - return_indent, Field +from src.header_generator import render_javadoc +from src.java_model import JAVA_KEYWORDS, JAVA_BUILTIN_TYPES, JAVA_LITERALS, Field, indent def generate_fields_block(fields: List[Field]) -> str: @@ -17,24 +17,12 @@ def generate_field_declaration(field: Field) -> str: _validate_java_field_name(field.name) declaration = [ - _render_javadoc(field), - f"{indent_lvl1}private {field.type} {field.name};" + render_javadoc(field.description, indent_lvl=1), + f"{indent(1)}private {field.type} {field.name};" ] return "\n".join(declaration) -def _render_javadoc(field: Field) -> str: - if field.description is None: - return "" - - return "\n".join([ - "", - f"{indent_lvl1}/**", - f"{indent_lvl1} * {field.description}", - f"{indent_lvl1} */" - ]) - - def generate_getters_and_setters(fields: List[Field]) -> str: methods = [] for field in fields: @@ -49,9 +37,9 @@ def generate_getter(field: Field) -> str: getter = [ "", - f"{indent_lvl1}public {field.type} {getter_name}() {{", - f"{indent_lvl2}return {field.name};", - f"{indent_lvl1}}}" + f"{indent(1)}public {field.type} {getter_name}() {{", + f"{indent(2)}return {field.name};", + f"{indent(1)}}}" ] return "\n".join(getter) @@ -61,9 +49,9 @@ def generate_setter(field: Field) -> str: setter = [ "", - f"{indent_lvl1}public void {setter_name}({field.type} {field.name}) {{", - f"{indent_lvl2}this.{field.name} = {field.name};", - f"{indent_lvl1}}}" + f"{indent(1)}public void {setter_name}({field.type} {field.name}) {{", + f"{indent(2)}this.{field.name} = {field.name};", + f"{indent(1)}}}" ] return "\n".join(setter) @@ -71,19 +59,19 @@ def generate_setter(field: Field) -> str: def generate_equals(class_name: str, fields: List[Field]) -> str: equals = [ "", - f"{indent_lvl1}@Override", - f"{indent_lvl1}public boolean equals(Object obj) {{", + f"{indent(1)}@Override", + f"{indent(1)}public boolean equals(Object obj) {{", - f"{indent_lvl2}if (this == obj)", - f"{indent_lvl3}return true;", + f"{indent(2)}if (this == obj)", + f"{indent(3)}return true;", - f"{indent_lvl2}if (!(obj instanceof {class_name}))", - f"{indent_lvl3}return false;", + f"{indent(2)}if (!(obj instanceof {class_name}))", + f"{indent(3)}return false;", - f"{indent_lvl2}{class_name} that = ({class_name}) obj;", + f"{indent(2)}{class_name} that = ({class_name}) obj;", _render_equals_return_statement(fields), - f"{indent_lvl1}}}" + f"{indent(1)}}}" ] return "\n".join(equals) @@ -95,20 +83,21 @@ def _render_equals_return_statement(fields: List[Field]) -> str: getter_name = _build_getter_name(field.name) end_line = ";" if i == (len(fields) - 1) else "" if i == 0: - return_statement.append(f"{indent_lvl2}return Objects.equals({getter_name}(), that.{getter_name}()){end_line}") + return_statement.append( + f"{indent(2)}return Objects.equals({getter_name}(), that.{getter_name}()){end_line}") else: return_statement.append( - f"{indent_lvl2}{return_indent}&& Objects.equals({getter_name}(), that.{getter_name}()){end_line}") + f"{indent(4)}&& Objects.equals({getter_name}(), that.{getter_name}()){end_line}") return "\n".join(return_statement) def generate_hash_code(fields: List[Field]) -> str: hash_code = [ "", - f"{indent_lvl1}@Override", - f"{indent_lvl1}public int hashCode() {{", + f"{indent(1)}@Override", + f"{indent(1)}public int hashCode() {{", _render_hashcode_return_statement(fields), - f"{indent_lvl1}}}" + f"{indent(1)}}}" ] return "\n".join(hash_code) @@ -124,17 +113,17 @@ def _render_hashcode_return_statement(fields: List[Field]) -> str: def _render_hashcode_return_statement_single_field(fields: List[Field]) -> str: field_name = fields[0].name getter_name = _build_getter_name(field_name) - return f"{indent_lvl2}return Objects.hash({getter_name}());" + return f"{indent(2)}return Objects.hash({getter_name}());" def _render_hashcode_return_statement_multiple_field(fields: List[Field]) -> str: - return_statement = [f"{indent_lvl2}return Objects.hash("] + return_statement = [f"{indent(2)}return Objects.hash("] for index, field in enumerate(fields): getter_name = _build_getter_name(field.name) comma = "," if index < (len(fields) - 1) else "" - return_statement.append(f"{indent_lvl2}{return_indent}{getter_name}(){comma}") + return_statement.append(f"{indent(4)}{getter_name}(){comma}") - return_statement.append(f"{indent_lvl2});") + return_statement.append(f"{indent(2)});") return "\n".join(return_statement) diff --git a/src/java_model.py b/src/java_model.py index 7991cd4..5c2ab3e 100644 --- a/src/java_model.py +++ b/src/java_model.py @@ -18,10 +18,6 @@ JAVA_LITERALS = { "null", "true", "false" } -indent_lvl1 = " " * 4 -indent_lvl2 = indent_lvl1 * 2 -indent_lvl3 = indent_lvl1 * 3 -return_indent = " " @dataclass @@ -36,3 +32,17 @@ class Field: name: str type: str description: Optional[str] = None + + +@dataclass +class JavaClass: + name: str + fields: List[Field] + description: Optional[str] = None + + +def indent(level: int, size: int = 4) -> str: + if level < 0 or size < 0: + raise ValueError("Indent size cannot be lower than 0") + + return " " * size * level diff --git a/tests/enum_reference_data.py b/tests/reference_data_enum_class.py similarity index 100% rename from tests/enum_reference_data.py rename to tests/reference_data_enum_class.py diff --git a/tests/reference_data_java_class.py b/tests/reference_data_java_class.py new file mode 100644 index 0000000..fcc8eef --- /dev/null +++ b/tests/reference_data_java_class.py @@ -0,0 +1,114 @@ +from src.java_model import Field, JavaClass + +package_example = "expected.DataTypes" +field_name_string = Field(name="name", type="String", description="This contains name.") +field_age_int = Field(name="age", type="int", description="This contains age.") +field_birthdate_date = Field(name="birthDate", type="Date") +field_additionalInfo_CustomObject = Field(name="additionalInfo", type="CustomData") + +person_class_fields = [field_name_string, field_age_int, field_birthdate_date, field_additionalInfo_CustomObject] + +class_person = JavaClass(name="Person", fields=person_class_fields, description="Contains information about a person.") +class_heartbeat = JavaClass(name="Heartbeat", fields=[]) + +expected_java_class_person = """\ +package expected.DataTypes; + + +import java.util.Date; +import java.util.Objects; + + +/** + * Contains information about a person. + */ +public class Person { + + /** + * This contains name. + */ + private String name; + + /** + * This contains age. + */ + private int age; + + private Date birthDate; + + private CustomData additionalInfo; + + + public String getName() { + return name; + } + + + public void setName(String name) { + this.name = name; + } + + + public int getAge() { + return age; + } + + + public void setAge(int age) { + this.age = age; + } + + + public Date getBirthDate() { + return birthDate; + } + + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + + public CustomData getAdditionalInfo() { + return additionalInfo; + } + + + public void setAdditionalInfo(CustomData additionalInfo) { + this.additionalInfo = additionalInfo; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Person)) + return false; + Person that = (Person) obj; + return Objects.equals(getName(), that.getName()) + && Objects.equals(getAge(), that.getAge()) + && Objects.equals(getBirthDate(), that.getBirthDate()) + && Objects.equals(getAdditionalInfo(), that.getAdditionalInfo()); + } + + + @Override + public int hashCode() { + return Objects.hash( + getName(), + getAge(), + getBirthDate(), + getAdditionalInfo() + ); + } +} +""" + +expected_java_class_heartbeat = """\ +package expected.DataTypes; + + +public class Heartbeat { +} +""" diff --git a/tests/reference_data.py b/tests/reference_data_methods.py similarity index 75% rename from tests/reference_data.py rename to tests/reference_data_methods.py index c68668a..3ada138 100644 --- a/tests/reference_data.py +++ b/tests/reference_data_methods.py @@ -3,6 +3,7 @@ field_exampleAttribute_int = Field(name="exampleAttribute", type="int", description="javadoc description") field_someName_String = Field(name="someName", type="String") field_customData_CustomObject = Field(name="customData", type="CustomObject", description="another javadoc description") +field_listOfThings_List_String = Field(name="listOfThings", type="List") expected_getExampleAttribute_int = """ public int getExampleAttribute() { @@ -33,3 +34,16 @@ public void setCustomData(CustomObject customData) { this.customData = customData; }""" + + +expected_getListOfThings_list_string = """ + public List getListOfThings() { + return listOfThings; + }""" + +expected_setListOfThings_list_string = """ + public void setListOfThings(List listOfThings) { + this.listOfThings = listOfThings; + }""" + + diff --git a/tests/test_class_generator.py b/tests/test_class_generator.py new file mode 100644 index 0000000..56a5f20 --- /dev/null +++ b/tests/test_class_generator.py @@ -0,0 +1,10 @@ +from src.class_generator import generate_java_class +from tests.reference_data_java_class import * + + +def test_generate_java_class(): + assert generate_java_class(class_person, package_example) == expected_java_class_person + + +def test_generate_java_class_no_fields(): + assert generate_java_class(class_heartbeat, package_example) == expected_java_class_heartbeat diff --git a/tests/test_enum_generator.py b/tests/test_enum_generator.py index a6e6de4..7240ac9 100644 --- a/tests/test_enum_generator.py +++ b/tests/test_enum_generator.py @@ -1,5 +1,5 @@ from src.enum_generator import to_java_constant, generate_enum_class -from tests.enum_reference_data import * +from tests.reference_data_enum_class import * def test_to_java_constant(): diff --git a/tests/test_header_generator.py b/tests/test_header_generator.py index 4bba937..5e376c0 100644 --- a/tests/test_header_generator.py +++ b/tests/test_header_generator.py @@ -1,6 +1,7 @@ import pytest -from src.header_generator import set_package +from src.java_model import Field +from src.header_generator import set_package, set_imports, render_javadoc def test_set_package(): @@ -25,3 +26,56 @@ def test_set_package_illegal_name(): example_2 = "com.example.123name" with pytest.raises(ValueError): set_package(example_2) + + +def test_set_imports_all(): + field_date = Field(name="birthDate", type="Date") + field_string = Field(name="birthDate", type="String") + field_list = Field(name="setOfSth", type="List") + fields = [field_list, field_string, field_date] + expected = """\ +import java.util.Date; +import java.util.List; +import java.util.Objects;""" + assert set_imports(fields) == expected + + +def test_set_imports_individual_import(): + field_date = Field(name="birthDate", type="Date") + fields = [field_date] + expected = """\ +import java.util.Date; +import java.util.Objects;""" + assert set_imports(fields) == expected + + +def test_set_imports_no_additional_imports(): + field_int = Field(name="age", type="int") + fields = [field_int] + expected = """\ +import java.util.Objects;""" + assert set_imports(fields) == expected + + +def test_render_javadoc_class_indent(): + description = "Example class description" + expected = """ +/** + * Example class description + */""" + assert render_javadoc(description, 0) == expected + + +def test_render_javadoc_field_indent(): + description = "Example field description" + expected = """ + /** + * Example field description + */""" + assert render_javadoc(description, 1) == expected + + +def test_render_javadoc_no_description(): + assert render_javadoc("", 0) == "" + assert render_javadoc(None, 0) == "" + assert render_javadoc(None, 1) == "" diff --git a/tests/test_java_method_generator.py b/tests/test_java_method_generator.py index d511bde..b24bb41 100644 --- a/tests/test_java_method_generator.py +++ b/tests/test_java_method_generator.py @@ -1,6 +1,6 @@ import pytest -from tests.reference_data import * +from tests.reference_data_methods import * from src.java_method_generator import * JAVA_KEYWORDS = { @@ -32,20 +32,22 @@ def test_generate_getter_integer(): attr = field_exampleAttribute_int - - assert (generate_getter(attr) == expected_getExampleAttribute_int) + assert generate_getter(attr) == expected_getExampleAttribute_int def test_generate_getter_string(): attr = field_someName_String - - assert (generate_getter(attr) == expected_getSomeName_String) + assert generate_getter(attr) == expected_getSomeName_String def test_generate_getter_custom_object(): attr = field_customData_CustomObject + assert generate_getter(attr) == expected_getCustomData_CustomObject + - assert (generate_getter(attr) == expected_getCustomData_CustomObject) +def test_generate_getter_list(): + attr = field_listOfThings_List_String + assert generate_getter(attr) == expected_getListOfThings_list_string def test_generate_getter_invalid_name(): @@ -57,20 +59,22 @@ def test_generate_getter_invalid_name(): def test_generate_setter_integer(): attr = field_exampleAttribute_int - - assert (generate_setter(attr) == expected_setExampleAttribute_int) + assert generate_setter(attr) == expected_setExampleAttribute_int def test_generate_setter_string(): attr = field_someName_String - - assert (generate_setter(attr) == expected_setSomeName_String) + assert generate_setter(attr) == expected_setSomeName_String def test_generate_setter_custom_object(): attr = field_customData_CustomObject + assert generate_setter(attr) == expected_setCustomData_CustomObject + - assert (generate_setter(attr) == expected_setCustomData_CustomObject) +def test_generate_setter_list(): + attr = field_listOfThings_List_String + assert generate_setter(attr) == expected_setListOfThings_list_string def test_generate_setter_invalid_name(): diff --git a/tests/test_java_model.py b/tests/test_java_model.py new file mode 100644 index 0000000..a64faeb --- /dev/null +++ b/tests/test_java_model.py @@ -0,0 +1,33 @@ +import pytest + +from src.java_model import indent + + +def test_indent_zero(): + assert indent(0) == "" + + +def test_indent_default_size(): + assert indent(1) == " " * 4 + + +def test_indent_multiple_levels(): + assert indent(3) == " " * 12 + + +def test_indent_custom_size(): + assert indent(2, size=2) == " " * 4 + + +def test_indent_size_zero(): + assert indent(5, size=0) == "" + + +def test_indent_negative_lvl(): + with pytest.raises(ValueError): + indent(level=-1, size=4) + + +def test_indent_negative_size(): + with pytest.raises(ValueError): + indent(level=1, size=-1)