Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Complex control flow structures
- Records (Java 14+)
- File I/O operations
- Enhanced TypeName support for common Java library types (List, Map, Set, etc.)
- Better Python type mapping (bool -> boolean, int -> int, etc.)
- Added `add_javadoc` method alongside existing `add_javadoc_line`
- Added `add_raw_line` method for raw code with newlines
- Support for handling None values in TypeName.get()

### Changed
- Updated README structure with clear Python-to-Java code mappings
- Enhanced documentation with working examples
- Improved TypeName.get() to handle Python types directly (e.g., `TypeName.get(bool)` returns `TypeName.BOOLEAN`)
- Updated method API: `add_code()` renamed to `add_raw_code()` for clarity
- Enhanced JavaDoc generation with better newline handling
- Fixed annotation newline formatting issues
- Improved static class name handling

### Fixed
- TypeName.get() now properly handles Python type objects instead of just type names
- JavaDoc emission now properly handles newlines and prefixes
- Annotation formatting with correct newline placement
- Primitive type boxing to proper wrapper classes (e.g., int -> java.lang.Integer)

## [0.1.0] - 2025-07-29

Expand Down Expand Up @@ -128,3 +144,8 @@ and the following files were added:
- Added begin_statement_chain, add_chained_item, and end_statement_chain to MethodSpec builder
- Includes corresponding changes in CodeBlock
- Switched add_javadoc to add_javadoc_line
- Added more default types to TypeName

### Fixes
- Fixed annotation newline issue
- Fixed passing in python types to give you a TypeName
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -977,8 +977,8 @@ print(str(java_file))

# Java file with imports and file comment
java_file_with_imports = JavaFile.builder("com.example", simple_class) \
.add_file_comment("This is a generated file.") \
.add_file_comment("Do not edit manually.") \
.add_file_comment_line("This is a generated file.") \
.add_file_comment_line("Do not edit manually.") \
.add_static_import(ClassName.get("java.lang", "System"), "out") \
.build()
print(str(java_file_with_imports))
Expand Down Expand Up @@ -1026,6 +1026,7 @@ method = MethodSpec.method_builder("example") \
```

## TODOs
I think of these as nice-ities, but they are more about convenience rather than correctness. There are work-arounds/other tools that can be used to create these desired affects.

1. TreeSitter API to synactically validate java file
2. Add kwargs to method spec builder. Currently code block will have an issue of overwriting previous
Expand All @@ -1035,8 +1036,7 @@ method = MethodSpec.method_builder("example") \
5. Name Allocator if we so desire (?)
6. Annotation member has to be valid java identifier
7. Handle primitive types better in ClassName i.e. validation
8. Improve tests with exact output strings and also slim down unneeded tests
9. Pass in TypeSpec for Types as well (for nested classes) ? It might work and we can include a self key too
8. Pass in TypeSpec for Types as well (for nested classes) ? It might work and we can include a self key too

## License

Expand Down
2 changes: 1 addition & 1 deletion examples/complex_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def main():
# Create the Java file
java_file = (
JavaFile.builder("com.example.processor", processor)
.add_file_comment("This is a generated file. Do not edit!")
.add_file_comment_line("This is a generated file. Do not edit!")
.build()
)

Expand Down
7 changes: 0 additions & 7 deletions pyjavapoet/annotation_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@

Modified by Matthew Au-Yeung on 2025-07-29; see changelog.md for more details.
- Similar APIs ported from Java to Python.

Changes and Current API:
- The API is modeled after JavaPoet's AnnotationSpec, but adapted for Python.
- AnnotationSpec is immutable; use the builder to create new instances.
- Supports representing Java annotations for classes, methods, fields, parameters, etc.
- The main API:
- AnnotationSpec(type_name, members)
"""

from typing import Any, Union
Expand Down
46 changes: 37 additions & 9 deletions pyjavapoet/code_block.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Copyright (C) 2015 Square, Inc.

Expand All @@ -15,11 +15,6 @@

Modified by Matthew Au-Yeung on 2025-07-29; see changelog.md for more details.
- Similar APIs ported from Java to Python.

Changes and Current API:
- The API is modeled after JavaPoet's CodeBlock, but adapted for Python.
- CodeBlock is immutable; use the builder to create new instances.
- Supports formatting Java code with placeholders.
"""

import re
Expand Down Expand Up @@ -51,7 +46,7 @@
placeholder_match = re.compile(
r"""
\$(
(?P<type1>[LSTN<>]) # $L, $S, $T, $N, $<, $>
(?P<type1>[LSTN<>]) # $L, $S, $T, $N, $<, $>
| # or
(?P<name>[a-zA-Z_][a-zA-Z0-9_]*) # $name
: # :
Expand All @@ -64,6 +59,26 @@
re.VERBOSE,
)

placeholder_match_with_newlines = re.compile(
r"""
(
\$(
(?P<type1>[LSTN<>]) # $L, $S, $T, $N, $<, $>
| # or
(?P<name>[a-zA-Z_][a-zA-Z0-9_]*) # $name
: # :
(?P<type2>[LSTN]) # T, L, S, N
| # or
(?P<index>\d+) # $1, $2, etc.
(?P<type3>[LSTN<>]) # L, S, T, N, $<, $>
)
|
(\n) # or a literal newline
)
""",
re.VERBOSE,
)

def __init__(self, format_parts: list[str], args: list[Any], named_args: dict[str, Any]):
self.format_parts = format_parts
self.args = args
Expand Down Expand Up @@ -140,9 +155,10 @@
code_writer.emit(part, new_line_prefix)

def emit_javadoc(self, code_writer: "CodeWriter") -> None:
code_writer.emit("/**\n * ")
code_writer.emit("/**\n")
self.emit(code_writer, " * ")
code_writer.emit("\n */")
code_writer.emit("\n", " * ")
code_writer.emit(" */")

def javadoc(self) -> str:
writer = CodeWriter()
Expand Down Expand Up @@ -178,6 +194,13 @@

@staticmethod
def add_javadoc(javadoc: Optional["CodeBlock"], format_string: str, *args) -> "CodeBlock":
if javadoc:
return CodeBlock.join_to_code([javadoc, CodeBlock.of(format_string, *args)])
else:
return CodeBlock.of(format_string, *args)

@staticmethod
def add_javadoc_line(javadoc: Optional["CodeBlock"], format_string: str, *args) -> "CodeBlock":
if javadoc:
return CodeBlock.join_to_code([javadoc, CodeBlock.of(format_string, *args)], "\n")
else:
Expand Down Expand Up @@ -207,7 +230,7 @@

def add(self, format_string: str, *args, **kwargs) -> "CodeBlock.Builder":
# Check for arguments in the format string
matches = list(re.finditer(CodeBlock.placeholder_match, format_string))
matches = list(re.finditer(CodeBlock.placeholder_match_with_newlines, format_string))

# Simple case: no arguments
if not matches:
Expand Down Expand Up @@ -248,6 +271,11 @@
self.add(format_string, *args, **kwargs)
self.add(";\n")
return self

def add_line(self, format_string: str, *args, **kwargs) -> "CodeBlock.Builder":
self.add(format_string, *args, **kwargs)
self.add("\n")
return self

def begin_statement(self, format_string: str, *args, **kwargs) -> "CodeBlock.Builder":
parts = format_string.split("\n")
Expand Down
9 changes: 4 additions & 5 deletions pyjavapoet/code_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@

Modified by Matthew Au-Yeung on 2025-07-29; see changelog.md for more details.
- Similar APIs ported from Java to Python.

Changes and Current API:
- The API is modeled after JavaPoet's CodeWriter, but adapted for Python.
- CodeWriter is immutable; use the builder to create new instances.
- Supports emitting Java code with proper formatting.
"""

from typing import Annotated, Literal
Expand Down Expand Up @@ -80,6 +75,10 @@ def unindent(self, count: int = 1) -> None:

def emit(self, s: str | Constant, new_line_prefix: str = "") -> "CodeWriter":
if s.startswith("\n"):
if self.__line_start and new_line_prefix:
self.__out.append(self.__indent * self.__indent_level)
self.__out.append(new_line_prefix)

# Reset line start
self.__out.append("\n")
self.__line_start = True
Expand Down
76 changes: 7 additions & 69 deletions pyjavapoet/field_spec.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Copyright (C) 2015 Square, Inc.

Expand All @@ -15,23 +15,17 @@

Modified by Matthew Au-Yeung on 2025-07-29; see changelog.md for more details.
- Similar APIs ported from Java to Python.

Changes and Current API:
- The API is modeled after JavaPoet's FieldSpec, but adapted for Python.
- FieldSpec is immutable; use the builder to create new instances.
- Supports Java modifiers (from Modifier), annotations (AnnotationSpec), type information (TypeName),
and initializer (CodeBlock).
"""

from typing import TYPE_CHECKING, Optional, Union

from pyjavapoet.annotation_spec import AnnotationSpec
from pyjavapoet.code_base import Code
from pyjavapoet.code_block import CodeBlock
from pyjavapoet.code_writer import CodeWriter
from pyjavapoet.code_writer import EMPTY_STRING, CodeWriter
from pyjavapoet.modifier import Modifier
from pyjavapoet.type_name import TypeName
from pyjavapoet.util import deep_copy
from pyjavapoet.util import deep_copy, throw_if_invalid_java_identifier

if TYPE_CHECKING:
from pyjavapoet.code_writer import CodeWriter
Expand Down Expand Up @@ -98,69 +92,9 @@
deep_copy(self.initializer),
)

@staticmethod
def is_valid_field_name(name: str) -> bool:
java_keywords = {
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
"true",
"false",
"null",
}
return name.isidentifier() and name not in java_keywords

@staticmethod
def builder(type_name: Union["TypeName", str, type], name: str) -> "Builder":
if not FieldSpec.is_valid_field_name(name):
raise ValueError(f"Invalid field name: {name}")
throw_if_invalid_java_identifier(name)

if not isinstance(type_name, TypeName):
type_name = TypeName.get(type_name)
Expand Down Expand Up @@ -207,6 +141,10 @@
def add_javadoc(self, format_string: str, *args) -> "FieldSpec.Builder":
self.__javadoc = CodeBlock.add_javadoc(self.__javadoc, format_string, *args)
return self

def add_javadoc_line(self, format_string: str = EMPTY_STRING, *args) -> "FieldSpec.Builder":
self.__javadoc = CodeBlock.add_javadoc_line(self.__javadoc, format_string, *args)
return self

def initializer(self, format_string: str | CodeBlock, *args) -> "FieldSpec.Builder":
if isinstance(format_string, str):
Expand Down
18 changes: 4 additions & 14 deletions pyjavapoet/java_file.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Copyright (C) 2015 Square, Inc.

Expand All @@ -15,13 +15,6 @@

Modified by Matthew Au-Yeung on 2025-07-29; see changelog.md for more details.
- Similar APIs ported from Java to Python.

Changes and Current API:
- The API is modeled after JavaPoet's JavaFile, but adapted for Python.
- JavaFile is immutable; use the builder to create new instances.
- Supports package declaration, imports, and type declarations.
- The main API:
- JavaFile(package_name, type_spec, file_comment, indent, static_imports)
"""

import sys
Expand Down Expand Up @@ -188,16 +181,13 @@
self.__indent = indent
self.__static_imports = static_imports or {}

def add_generated_by(self, creator: str, extra_comment: str = "", *args) -> "JavaFile.Builder":
self.add_file_comment("@generated")
self.add_file_comment(f"Generated by {creator}", *args)
if extra_comment:
self.add_file_comment(extra_comment, *args)
return self

def add_file_comment(self, format_string: str = EMPTY_STRING, *args) -> "JavaFile.Builder":
self.__file_comment = CodeBlock.add_javadoc(self.__file_comment, format_string, *args)
return self

def add_file_comment_line(self, format_string: str = EMPTY_STRING, *args) -> "JavaFile.Builder":
self.__file_comment = CodeBlock.add_javadoc_line(self.__file_comment, format_string, *args)
return self

def indent(self, indent: str) -> "JavaFile.Builder":
self.__indent = indent
Expand Down
Loading
Loading