diff --git a/CHANGELOG.md b/CHANGELOG.md index f623762..d513d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.1.4] - 2025-08-07 ### Added - N/A ### Changed -- N/A +- Modified `ArrayTypeName.of()` to `ArrayTypeName.get()` for consistency with other TypeName methods ### Fixed -- N/A +- Fixed interface methods with no modifiers incorrectly generating function bodies - they now correctly generate as abstract method declarations (e.g., `void word();` instead of `void word() { }`) ## [0.1.3] - 2025-08-04 - Moved all common ClassNames to be under ClassName instead of TypeName diff --git a/README.md b/README.md index 1fa9c1a..93b791c 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,34 @@ public interface Drawable { } ``` +**Interface Method Modifiers:** + +PyJavaPoet correctly handles interface methods with and without explicit modifiers: + +**Python Code:** +```python +# Interface method with explicit public modifier +interface_with_modifiers = TypeSpec.interface_builder("TestInterface") \ + .add_modifiers(Modifier.PUBLIC) \ + .add_method(MethodSpec.method_builder("word") + .add_modifiers(Modifier.PUBLIC) + .returns("void") + .build()) \ + .build() + +java_file = JavaFile.builder("com.example", interface_with_modifiers).build() +print(java_file) +``` + +**Generated Java Code:** +```java +package com.example; + +public interface TestInterface { + public void word(); +} +``` + ### 3. Creating Enums with Custom Methods **Python Code:** diff --git a/pyjavapoet/method_spec.py b/pyjavapoet/method_spec.py index c1c6efe..a8afdde 100644 --- a/pyjavapoet/method_spec.py +++ b/pyjavapoet/method_spec.py @@ -59,6 +59,7 @@ def __init__( code: Optional["CodeBlock"], default_value: Optional["CodeBlock"], kind: "MethodSpec.Kind", + in_interface: bool, ): self.name = name self.modifiers = modifiers @@ -71,7 +72,7 @@ def __init__( self.code = code self.default_value = default_value self.kind = kind - + self.in_interface = in_interface # Validate that constructors don't have a return type if self.kind == MethodSpec.Kind.CONSTRUCTOR and self.return_type is not None: raise ValueError("Constructors cannot have a return type") @@ -131,12 +132,16 @@ def emit(self, code_writer: "CodeWriter") -> None: exception.emit(code_writer) # Emit body or semicolon - if Modifier.ABSTRACT in self.modifiers or (Modifier.NATIVE in self.modifiers and not self.default_value): - code_writer.emit(";\n") - elif self.default_value is not None: + if self.default_value: code_writer.emit(" default ") self.default_value.emit(code_writer) code_writer.emit(";\n") + elif ( + Modifier.ABSTRACT in self.modifiers + or Modifier.NATIVE in self.modifiers + or (self.in_interface and Modifier.DEFAULT not in self.modifiers) + ): + code_writer.emit(";\n") else: code_writer.emit(" {\n") code_writer.indent() @@ -160,6 +165,7 @@ def to_builder(self) -> "MethodSpec.Builder": deep_copy(self.annotations), self.code.to_builder() if self.code else CodeBlock.builder(), deep_copy(self.default_value), + self.in_interface, ) @staticmethod @@ -191,6 +197,7 @@ class Builder(Code.Builder["MethodSpec"]): __annotations: list["AnnotationSpec"] __code_builder: "CodeBlock.Builder" __default_value: Optional["CodeBlock"] + __in_interface: bool def __init__( self, @@ -205,6 +212,7 @@ def __init__( annotations: list["AnnotationSpec"] | None = None, code_builder: Optional["CodeBlock.Builder"] = None, default_value: Optional["CodeBlock"] = None, + in_interface: bool = False, ): self.__name = name self.__kind = kind @@ -217,6 +225,7 @@ def __init__( self.__annotations = annotations or [] self.__code_builder = code_builder or CodeBlock.builder() self.__default_value = default_value + self.__in_interface = in_interface def add_modifiers(self, *modifiers: Modifier) -> "MethodSpec.Builder": self.__modifiers.update(modifiers) @@ -356,6 +365,10 @@ def default_value(self, format_string: str, *args) -> "MethodSpec.Builder": self.__default_value = CodeBlock.of(format_string, *args) return self + def in_interface(self) -> "MethodSpec.Builder": + self.__in_interface = True + return self + def set_name(self, name: str) -> "MethodSpec.Builder": self.__name = name return self @@ -384,4 +397,5 @@ def build(self) -> "MethodSpec": self.__code_builder.build() if self.__code_builder else None, deep_copy(self.__default_value), deep_copy(self.__kind), + self.__in_interface, ) diff --git a/pyjavapoet/type_name.py b/pyjavapoet/type_name.py index bc27da8..c25676a 100644 --- a/pyjavapoet/type_name.py +++ b/pyjavapoet/type_name.py @@ -371,7 +371,7 @@ def copy(self) -> "ArrayTypeName": return ArrayTypeName(deep_copy(self.component_type), deep_copy(self.annotations)) @staticmethod - def of(component_type: Union["TypeName", str, type]) -> "ArrayTypeName": + def get(component_type: Union["TypeName", str, type]) -> "ArrayTypeName": return ArrayTypeName(TypeName.get(component_type)) diff --git a/pyjavapoet/type_spec.py b/pyjavapoet/type_spec.py index 21331c8..0488d74 100644 --- a/pyjavapoet/type_spec.py +++ b/pyjavapoet/type_spec.py @@ -458,6 +458,9 @@ def add_method(self, method_spec: MethodSpec) -> "TypeSpec.Builder": if method_spec.kind in (MethodSpec.Kind.CONSTRUCTOR, MethodSpec.Kind.COMPACT_CONSTRUCTOR): method_spec = method_spec.to_builder().set_name(self.__name).build() + if self.__kind == TypeSpec.Kind.INTERFACE: + method_spec = method_spec.to_builder().in_interface().build() + self.__methods.append(method_spec) return self diff --git a/tests/test_method_spec.py b/tests/test_method_spec.py index d9fed10..bd5c466 100644 --- a/tests/test_method_spec.py +++ b/tests/test_method_spec.py @@ -114,6 +114,21 @@ def test_abstract_method(self): # Abstract methods should not have a body self.assertNotIn("{", result) + def test_method_in_interface(self): + """Test method in interface.""" + method = ( + MethodSpec.method_builder("process") + .returns("void") + .add_parameter(ClassName.get("java.lang", "Object"), "data") + .in_interface() + .build() + ) + + result = str(method) + self.assertIn("void process", result) + # Interface methods should not have a body + self.assertNotIn("{", result) + def test_method_with_exceptions(self): """Test method with exceptions.""" method = ( diff --git a/tests/test_type_name.py b/tests/test_type_name.py index 876c98f..c07fe53 100644 --- a/tests/test_type_name.py +++ b/tests/test_type_name.py @@ -65,9 +65,9 @@ def test_equals_and_hash_code_class_name(self): def test_equals_and_hash_code_array_type_name(self): """Test equals and hash code for array types.""" - a = ArrayTypeName.of(ClassName.get("java.lang", "String")) - b = ArrayTypeName.of(ClassName.get("java.lang", "String")) - c = ArrayTypeName.of(ClassName.get("java.lang", "Object")) + a = ArrayTypeName.get(ClassName.get("java.lang", "String")) + b = ArrayTypeName.get(ClassName.get("java.lang", "String")) + c = ArrayTypeName.get(ClassName.get("java.lang", "Object")) self.assertEqual(a, b) self.assertEqual(hash(a), hash(b)) @@ -167,11 +167,11 @@ def test_can_box_annotated_primitive(self): def test_array_type_creation(self): """Test array type creation.""" - string_array = ArrayTypeName.of(ClassName.get("java.lang", "String")) + string_array = ArrayTypeName.get(ClassName.get("java.lang", "String")) self.assertEqual(str(string_array), "String[]") # Multi-dimensional array - int_2d_array = ArrayTypeName.of(ArrayTypeName.of(TypeName.get("int"))) + int_2d_array = ArrayTypeName.get(ArrayTypeName.get(TypeName.get("int"))) self.assertEqual(str(int_2d_array), "int[][]") def test_type_variable_with_bounds(self): diff --git a/tests/test_type_spec.py b/tests/test_type_spec.py index 86a4f48..54323d9 100644 --- a/tests/test_type_spec.py +++ b/tests/test_type_spec.py @@ -211,12 +211,19 @@ def test_interface_creation(self): .returns("void") .build() ) + .add_method(MethodSpec.method_builder("random").returns("void").build()) .build() ) result = str(drawable) - self.assertIn("public interface Drawable", result) - self.assertIn("public abstract void draw()", result) + expected = """\ +public interface Drawable { + public abstract void draw(); + + void random(); +}\ +""" + self.assertEqual(result, expected) def test_interface_with_default_method(self): """Test interface with default method."""