Skip to content

Commit 5a62f87

Browse files
authored
Merge pull request #5 from MrMaydo/dev
Enum Generator
2 parents 1b10caf + 4ef7129 commit 5a62f87

10 files changed

Lines changed: 469 additions & 196 deletions

src/enum_generator.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import re
2+
from typing import List
3+
4+
from src.java_model import EnumClass, indent_lvl1, indent_lvl2, indent_lvl3
5+
from src.header_generator import set_package
6+
7+
8+
def to_java_constant(value: str) -> str:
9+
value = re.sub(r"[^A-Za-z0-9]", "_", value) # delimiters a-a -> A_A
10+
value = re.sub(r"([a-z])[_]?([A-Z])([A-Z])([a-z])", r"\1_\2_\3\4", value) # aBCd / a_BCd-> a_B_CD
11+
value = re.sub(r"([a-z])([A-Z])", r"\1_\2", value) # aA -> A_A
12+
value = re.sub(r"([A-Za-z])([0-9])", r"\1_\2", value) # a9 / A9 -> a_9
13+
value = re.sub(r"([0-9])([A-Za-z])", r"\1_\2", value) # 9a / 9A -> 9_A
14+
15+
return value.upper()
16+
17+
18+
def generate_enum_class(enum_class: EnumClass, package: str) -> str:
19+
enum_body = [
20+
set_package(package),
21+
"",
22+
f"import java.util.HashMap;",
23+
f"import java.util.Map;",
24+
f""
25+
]
26+
27+
enum_body.extend(_get_javadoc(enum_class.description))
28+
29+
enum_body.append(f"public enum {enum_class.name} {{")
30+
enum_body.extend(_get_constants(enum_class.values))
31+
32+
enum_body.append(f"")
33+
enum_body.append(
34+
f"{indent_lvl1}private final static Map<String, {enum_class.name}> CONSTANTS = new HashMap<String, {enum_class.name}>();")
35+
enum_body.append(f"")
36+
enum_body.extend(_get_static_method(enum_class.name))
37+
38+
enum_body.append(f"")
39+
enum_body.append(f"{indent_lvl1}private final String value;")
40+
enum_body.append(f"")
41+
enum_body.extend(_get_constructor(enum_class.name))
42+
43+
enum_body.append(f"")
44+
enum_body.extend(_get_from_value_method(enum_class.name))
45+
46+
enum_body.append(f"")
47+
enum_body.extend(_get_to_string_method())
48+
49+
enum_body.append(f"")
50+
enum_body.extend(_get_value_method())
51+
enum_body.append("}")
52+
enum_body.append(f"")
53+
54+
return "\n".join(enum_body)
55+
56+
57+
def _get_javadoc(description: str) -> List[str]:
58+
javadoc = [""]
59+
if description is not None:
60+
javadoc = [
61+
"",
62+
"/**",
63+
f" * {description}",
64+
" */"
65+
]
66+
return javadoc
67+
68+
69+
def _get_constants(constants: List[str]) -> List[str]:
70+
values = []
71+
for i, value in enumerate(constants):
72+
line_end = ";" if i == (len(constants) - 1) else ","
73+
values.append(
74+
f'{indent_lvl1}{to_java_constant(value)}("{value}"){line_end}'
75+
)
76+
return values
77+
78+
79+
def _get_static_method(class_name: str) -> List[str]:
80+
body = [
81+
f"{indent_lvl1}static {{",
82+
f"{indent_lvl2}for ({class_name} c : values()) {{",
83+
f"{indent_lvl3}CONSTANTS.put(c.value, c);",
84+
f"{indent_lvl2}}}",
85+
f"{indent_lvl1}}}"
86+
]
87+
return body
88+
89+
90+
def _get_constructor(class_name: str) -> List[str]:
91+
body = [
92+
f"{indent_lvl1}{class_name}(String value) {{",
93+
f"{indent_lvl2}this.value = value;",
94+
f"{indent_lvl1}}}"
95+
]
96+
return body
97+
98+
99+
def _get_from_value_method(class_name: str) -> List[str]:
100+
body = [
101+
f"{indent_lvl1}public static {class_name} fromValue(String value) {{",
102+
f"{indent_lvl2}{class_name} constant = CONSTANTS.get(value);",
103+
f"{indent_lvl2}if (constant == null) {{",
104+
f"{indent_lvl3}throw new IllegalArgumentException(value);",
105+
f"{indent_lvl2}}} else {{",
106+
f"{indent_lvl3}return constant;",
107+
f"{indent_lvl2}}}",
108+
f"{indent_lvl1}}}"
109+
]
110+
return body
111+
112+
113+
def _get_to_string_method() -> List[str]:
114+
body = [
115+
f"{indent_lvl1}@Override",
116+
f"{indent_lvl1}public String toString() {{",
117+
f"{indent_lvl2}return this.value;",
118+
f"{indent_lvl1}}}"
119+
]
120+
return body
121+
122+
123+
def _get_value_method() -> List[str]:
124+
body = [
125+
f"{indent_lvl1}public String value() {{",
126+
f"{indent_lvl2}return this.value;",
127+
f"{indent_lvl1}}}"
128+
]
129+
return body

src/header_generator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
def set_package(package: str) -> str:
5-
PACKAGE_REGEX = r"^(?:[a-z_][a-z0-9_]*)(?:\.(?:[a-z_][a-z0-9_]*))*$"
5+
PACKAGE_REGEX = r"^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.(?:[A-Za-z_][A-Za-z0-9_]*))*$"
66
if not re.match(PACKAGE_REGEX, package):
77
raise ValueError(f"Invalid package: '{package}'")
8-
return f"package {package}"
8+
return f"package {package};"

src/java_method_generator.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import re
2+
from typing import List
3+
4+
from src.java_model import JAVA_KEYWORDS, JAVA_BUILTIN_TYPES, JAVA_LITERALS, indent_lvl1, indent_lvl2, indent_lvl3, \
5+
return_indent, Field
6+
7+
8+
def generate_fields_block(fields: List[Field]) -> str:
9+
declaration = []
10+
for field in fields:
11+
declaration.append(generate_field_declaration(field))
12+
13+
return "\n".join(declaration)
14+
15+
16+
def generate_field_declaration(field: Field) -> str:
17+
_validate_java_field_name(field.name)
18+
19+
declaration = [
20+
_render_javadoc(field),
21+
f"{indent_lvl1}private {field.type} {field.name};"
22+
]
23+
return "\n".join(declaration)
24+
25+
26+
def _render_javadoc(field: Field) -> str:
27+
if field.description is None:
28+
return ""
29+
30+
return "\n".join([
31+
"",
32+
f"{indent_lvl1}/**",
33+
f"{indent_lvl1} * {field.description}",
34+
f"{indent_lvl1} */"
35+
])
36+
37+
38+
def generate_getters_and_setters(fields: List[Field]) -> str:
39+
methods = []
40+
for field in fields:
41+
methods.append(generate_getter(field))
42+
methods.append(generate_setter(field))
43+
44+
return "\n\n".join(methods)
45+
46+
47+
def generate_getter(field: Field) -> str:
48+
getter_name = _build_getter_name(field.name)
49+
50+
getter = [
51+
"",
52+
f"{indent_lvl1}public {field.type} {getter_name}() {{",
53+
f"{indent_lvl2}return {field.name};",
54+
f"{indent_lvl1}}}"
55+
]
56+
return "\n".join(getter)
57+
58+
59+
def generate_setter(field: Field) -> str:
60+
setter_name = _build_setter_name(field.name)
61+
62+
setter = [
63+
"",
64+
f"{indent_lvl1}public void {setter_name}({field.type} {field.name}) {{",
65+
f"{indent_lvl2}this.{field.name} = {field.name};",
66+
f"{indent_lvl1}}}"
67+
]
68+
return "\n".join(setter)
69+
70+
71+
def generate_equals(class_name: str, fields: List[Field]) -> str:
72+
equals = [
73+
"",
74+
f"{indent_lvl1}@Override",
75+
f"{indent_lvl1}public boolean equals(Object obj) {{",
76+
77+
f"{indent_lvl2}if (this == obj)",
78+
f"{indent_lvl3}return true;",
79+
80+
f"{indent_lvl2}if (!(obj instanceof {class_name}))",
81+
f"{indent_lvl3}return false;",
82+
83+
f"{indent_lvl2}{class_name} that = ({class_name}) obj;",
84+
85+
_render_equals_return_statement(fields),
86+
f"{indent_lvl1}}}"
87+
]
88+
89+
return "\n".join(equals)
90+
91+
92+
def _render_equals_return_statement(fields: List[Field]) -> str:
93+
return_statement = []
94+
for i, field in enumerate(fields):
95+
getter_name = _build_getter_name(field.name)
96+
end_line = ";" if i == (len(fields) - 1) else ""
97+
if i == 0:
98+
return_statement.append(f"{indent_lvl2}return Objects.equals({getter_name}(), that.{getter_name}()){end_line}")
99+
else:
100+
return_statement.append(
101+
f"{indent_lvl2}{return_indent}&& Objects.equals({getter_name}(), that.{getter_name}()){end_line}")
102+
return "\n".join(return_statement)
103+
104+
105+
def generate_hash_code(fields: List[Field]) -> str:
106+
hash_code = [
107+
"",
108+
f"{indent_lvl1}@Override",
109+
f"{indent_lvl1}public int hashCode() {{",
110+
_render_hashcode_return_statement(fields),
111+
f"{indent_lvl1}}}"
112+
]
113+
114+
return "\n".join(hash_code)
115+
116+
117+
def _render_hashcode_return_statement(fields: List[Field]) -> str:
118+
if len(fields) == 1:
119+
return _render_hashcode_return_statement_single_field(fields)
120+
121+
return _render_hashcode_return_statement_multiple_field(fields)
122+
123+
124+
def _render_hashcode_return_statement_single_field(fields: List[Field]) -> str:
125+
field_name = fields[0].name
126+
getter_name = _build_getter_name(field_name)
127+
return f"{indent_lvl2}return Objects.hash({getter_name}());"
128+
129+
130+
def _render_hashcode_return_statement_multiple_field(fields: List[Field]) -> str:
131+
return_statement = [f"{indent_lvl2}return Objects.hash("]
132+
for index, field in enumerate(fields):
133+
getter_name = _build_getter_name(field.name)
134+
comma = "," if index < (len(fields) - 1) else ""
135+
return_statement.append(f"{indent_lvl2}{return_indent}{getter_name}(){comma}")
136+
137+
return_statement.append(f"{indent_lvl2});")
138+
139+
return "\n".join(return_statement)
140+
141+
142+
def _build_getter_name(field_name: str) -> str:
143+
_validate_java_field_name(field_name)
144+
return "get" + field_name[0].upper() + field_name[1:]
145+
146+
147+
def _build_setter_name(field_name: str) -> str:
148+
_validate_java_field_name(field_name)
149+
return "set" + field_name[0].upper() + field_name[1:]
150+
151+
152+
def _validate_java_field_name(name: str) -> None:
153+
if not name:
154+
raise ValueError("Field name cannot be empty")
155+
156+
if name in JAVA_KEYWORDS | JAVA_BUILTIN_TYPES | JAVA_LITERALS:
157+
raise ValueError(f"'{name}' is a Java reserved keyword")
158+
159+
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name):
160+
raise ValueError(f"Invalid Java identifier: '{name}'")

src/java_model.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from dataclasses import dataclass
2+
from typing import List, Optional
3+
4+
JAVA_KEYWORDS = {
5+
"abstract", "assert", "boolean", "break", "byte", "case", "catch",
6+
"char", "class", "const", "continue", "default", "do", "double",
7+
"else", "enum", "extends", "final", "finally", "float", "for",
8+
"goto", "if", "implements", "import", "instanceof", "int",
9+
"interface", "long", "native", "new", "package", "private",
10+
"protected", "public", "return", "short", "static", "strictfp",
11+
"super", "switch", "synchronized", "this", "throw", "throws",
12+
"transient", "try", "void", "volatile", "while"
13+
}
14+
JAVA_BUILTIN_TYPES = {
15+
"Boolean", "Byte", "Character", "Double", "Float", "Integer", "List", "Long", "Short",
16+
"Class", "Object", "String", "Void"
17+
}
18+
JAVA_LITERALS = {
19+
"null", "true", "false"
20+
}
21+
indent_lvl1 = " " * 4
22+
indent_lvl2 = indent_lvl1 * 2
23+
indent_lvl3 = indent_lvl1 * 3
24+
return_indent = " "
25+
26+
27+
@dataclass
28+
class EnumClass:
29+
name: str
30+
values: List[str]
31+
description: Optional[str] = None
32+
33+
34+
@dataclass
35+
class Field:
36+
name: str
37+
type: str
38+
description: Optional[str] = None

0 commit comments

Comments
 (0)