This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 03afda73 fix(migrate_annotations): enhance property parsing for 
additional types and constant references
03afda73 is described below

commit 03afda73c0e57e15682fb520ab929adda709007e
Author: Konrad Windszus <[email protected]>
AuthorDate: Wed May 13 14:27:05 2026 +0200

    fix(migrate_annotations): enhance property parsing for additional types and 
constant references
---
 .../scripts/migrate_annotations.py                 | 141 +++++++--
 .../scripts/tests/test_migrate_annotations.py      | 317 ++++++++++++++++++++-
 2 files changed, 422 insertions(+), 36 deletions(-)

diff --git a/skills/osgi-scr-migrator/scripts/migrate_annotations.py 
b/skills/osgi-scr-migrator/scripts/migrate_annotations.py
index 38666311..08a5ec30 100755
--- a/skills/osgi-scr-migrator/scripts/migrate_annotations.py
+++ b/skills/osgi-scr-migrator/scripts/migrate_annotations.py
@@ -28,12 +28,16 @@ class PropertyType(Enum):
     LONG = "Long"
     FLOAT = "Float"
     DOUBLE = "Double"
+    CHAR = "Character"
+    SHORT = "Short"
+    BYTE = "Byte"
 
 
 @dataclass
 class Property:
     """Represents a component property."""
     name: str
+    # all string values are stored as raw Java expressions (quoted literals or 
constant references)
     value: Optional[str] = None
     values: List[str] = field(default_factory=list)
     type: PropertyType = PropertyType.STRING
@@ -57,7 +61,7 @@ class Property:
         4. Property is single-valued (not array)
         """
         # Only single-value, non-configurable properties can use property type 
annotations
-        if self.is_configurable or self.values:
+        if self.is_configurable or bool(self.values):
             return False
 
         # Must have a value to convert
@@ -122,6 +126,12 @@ class Property:
             if self.type in (PropertyType.FLOAT, PropertyType.DOUBLE):
                 return not 
bool(re.fullmatch(r'-?(?:\d+\.\d*|\d*\.\d+|\d+)(?:[fFdD])?', val))
 
+            if self.type == PropertyType.CHAR:
+                return not bool(re.fullmatch(r"'([^'\\]|\\.)'"  , val))
+
+            if self.type in (PropertyType.SHORT, PropertyType.BYTE):
+                return not bool(re.fullmatch(r'-?\d+', val))
+
             return True
 
         if self.values:
@@ -152,9 +162,9 @@ class Property:
         """
         parts = []
         if self.label:
-            parts.append(f'name = "{self.label}"')
+            parts.append(f'name = {self.label}')
         if self.description:
-            parts.append(f'description = "{self.description}"')
+            parts.append(f'description = {self.description}')
 
         method_name = self._property_id_to_method_name(self.name)
         java_type = self._get_java_type()
@@ -195,25 +205,25 @@ class Property:
             PropertyType.LONG: "long",
             PropertyType.FLOAT: "float",
             PropertyType.DOUBLE: "double",
+            PropertyType.CHAR: "char",
+            PropertyType.SHORT: "short",
+            PropertyType.BYTE: "byte",
         }
-        if self.values and len(self.values) > 1:
+        if self.values:
             return f"{type_map[self.type]}[]"
         return type_map[self.type]
 
     def _get_default_value(self) -> str:
-        """Get default value expression."""
+        """Get default value expression.
+        
+        Values are stored as raw Java expressions (quoted literals or constant 
references),
+        so no additional quoting is needed.
+        """
         if self.values:
-            if self.type == PropertyType.STRING:
-                vals = ', '.join(f'"{v}"' for v in self.values)
-                return f' default {{{vals}}}'
-            else:
-                vals = ', '.join(self.values)
-                return f' default {{{vals}}}'
+            vals = ', '.join(self.values)
+            return f' default {{{vals}}}'
         elif self.value is not None:
-            if self.type == PropertyType.STRING:
-                return f' default "{self.value}"'
-            else:
-                return f' default {self.value}'
+            return f' default {self.value}'
         return ''
 
 
@@ -345,12 +355,14 @@ class AnnotationMigrator:
                     self.has_metatype = True
                     self.stats.metatype_configs_generated += 1
 
-                # Extract label and description from @Component
-                label_match = re.search(r'label\s*=\s*"([^"]*)"', annotation)
+                # Extract label and description from @Component.
+                # Match either a quoted string literal (captured with its 
quotes)
+                # or a bare identifier / qualified constant reference.
+                label_match = 
re.search(r'label\s*=\s*("[^"]*"|[A-Za-z_$][A-Za-z0-9_$.]*)', annotation)
                 if label_match:
                     self.component_label = label_match.group(1)
 
-                desc_match = re.search(r'description\s*=\s*"([^"]*)"', 
annotation)
+                desc_match = 
re.search(r'description\s*=\s*("[^"]*"|[A-Za-z_$][A-Za-z0-9_$.]*)', annotation)
                 if desc_match:
                     self.component_description = desc_match.group(1)
 
@@ -402,25 +414,65 @@ class AnnotationMigrator:
             idx += 1
 
         # Parse attributes
-        name_match = re.search(r'name\s*=\s*"([^"]*)"', annotation)
+        # Match name as either a quoted literal or a constant reference
+        name_match = 
re.search(r'name\s*=\s*("[^"]*"|[A-Za-z_$][A-Za-z0-9_$.]*)', annotation)
         value_match = re.search(
             
r'(?<!bool)(?<!int)(?<!long)(?<!float)(?<!double)value\s*=\s*("[^"]*"|[A-Za-z_][A-Za-z0-9_.]*)',
             annotation
         )
         values_match = re.search(r'value\s*=\s*\{([^}]+)\}', annotation)
         bool_value_match = re.search(r'boolValue\s*=\s*(true|false)', 
annotation)
+        bool_values_match = re.search(r'boolValue\s*=\s*\{([^}]+)\}', 
annotation)
         int_value_match = re.search(r'intValue\s*=\s*(-?\d+)', annotation)
+        int_values_match = re.search(r'intValue\s*=\s*\{([^}]+)\}', annotation)
         long_value_match = re.search(r'longValue\s*=\s*(-?\d+)L?', annotation)
+        long_values_match = re.search(r'longValue\s*=\s*\{([^}]+)\}', 
annotation)
         float_value_match = re.search(r'floatValue\s*=\s*(-?\d+\.?\d*)f?', 
annotation)
+        float_values_match = re.search(r'floatValue\s*=\s*\{([^}]+)\}', 
annotation)
         double_value_match = re.search(r'doubleValue\s*=\s*(-?\d+\.?\d*)', 
annotation)
-        label_match = re.search(r'label\s*=\s*"([^"]*)"', annotation)
-        desc_match = re.search(r'description\s*=\s*"([^"]*)"', annotation)
+        double_values_match = re.search(r'doubleValue\s*=\s*\{([^}]+)\}', 
annotation)
+        char_value_match = re.search(r"charValue\s*=\s*('[^'\\]'|'\\.')", 
annotation)
+        char_values_match = re.search(r'charValue\s*=\s*\{([^}]+)\}', 
annotation)
+        short_value_match = re.search(r'shortValue\s*=\s*(-?\d+)', annotation)
+        short_values_match = re.search(r'shortValue\s*=\s*\{([^}]+)\}', 
annotation)
+        byte_value_match = re.search(r'byteValue\s*=\s*(-?\d+)', annotation)
+        byte_values_match = re.search(r'byteValue\s*=\s*\{([^}]+)\}', 
annotation)
+        label_match = 
re.search(r'label\s*=\s*("[^"]*"|[A-Za-z_$][A-Za-z0-9_$.]*)', annotation)
+        desc_match = 
re.search(r'description\s*=\s*("[^"]*"|[A-Za-z_$][A-Za-z0-9_$.]*)', annotation)
 
         # If no name attribute, look for the property name in the field 
declaration
         # Pattern: @Property(...) \n private static final String FIELD_NAME = 
"property.name";
         property_name = None
+        constant_to_resolve = None
+        
         if name_match:
-            property_name = name_match.group(1)
+            raw_name = name_match.group(1)
+            # Extract literal value if quoted, otherwise it's a constant 
reference
+            if raw_name.startswith('"') and raw_name.endswith('"'):
+                property_name = raw_name[1:-1]  # Remove quotes from literal
+            else:
+                # Constant reference - extract the simple name to look up
+                # For "PROPERTY_NAME" or "Constants.PROPERTY_NAME", extract 
the last component
+                constant_to_resolve = raw_name.split('.')[-1]
+
+        # If still no property name, look for the field declaration
+        if not property_name:
+            for i in range(idx, min(idx + 3, len(self.lines))):
+                field_line = self.lines[i]
+                
+                # If we have a specific constant name to resolve, look for 
that field
+                if constant_to_resolve:
+                    field_match = re.search(
+                        
rf'(?:(?:private|protected|public)\s+)?static\s+final\s+String\s+{re.escape(constant_to_resolve)}\s*=\s*"([^"]+)"',
+                        field_line
+                    )
+                else:
+                    # Match any static final String field
+                    field_match = 
re.search(r'(?:(?:private|protected|public)\s+)?static\s+final\s+String\s+\w+\s*=\s*"([^"]+)"',
 field_line)
+                
+                if field_match:
+                    property_name = field_match.group(1)
+                    break
         else:
             # Look for the field declaration on the next line(s) after the 
annotation
             for i in range(idx, min(idx + 3, len(self.lines))):
@@ -438,23 +490,56 @@ class AnnotationMigrator:
         prop = Property(name=property_name)
 
         # Determine type and value
-        if bool_value_match:
+        if bool_values_match:
+            prop.type = PropertyType.BOOLEAN
+            prop.values = [v.strip() for v in 
bool_values_match.group(1).split(',')]
+        elif bool_value_match:
             prop.type = PropertyType.BOOLEAN
             prop.value = bool_value_match.group(1)
+        elif int_values_match:
+            prop.type = PropertyType.INTEGER
+            prop.values = [v.strip() for v in 
int_values_match.group(1).split(',')]
         elif int_value_match:
             prop.type = PropertyType.INTEGER
             prop.value = int_value_match.group(1)
+        elif long_values_match:
+            prop.type = PropertyType.LONG
+            prop.values = [v.strip() for v in 
long_values_match.group(1).split(',')]
         elif long_value_match:
             prop.type = PropertyType.LONG
             prop.value = long_value_match.group(1)
+        elif float_values_match:
+            prop.type = PropertyType.FLOAT
+            prop.values = [v.strip() for v in 
float_values_match.group(1).split(',')]
         elif float_value_match:
             prop.type = PropertyType.FLOAT
             prop.value = float_value_match.group(1)
+        elif double_values_match:
+            prop.type = PropertyType.DOUBLE
+            prop.values = [v.strip() for v in 
double_values_match.group(1).split(',')]
         elif double_value_match:
             prop.type = PropertyType.DOUBLE
             prop.value = double_value_match.group(1)
+        elif char_values_match:
+            prop.type = PropertyType.CHAR
+            prop.values = re.findall(r"'[^'\\]'|'\\.'|'\\u[0-9a-fA-F]{4}'", 
char_values_match.group(0))
+        elif char_value_match:
+            prop.type = PropertyType.CHAR
+            prop.value = char_value_match.group(1)
+        elif short_values_match:
+            prop.type = PropertyType.SHORT
+            prop.values = [v.strip() for v in 
short_values_match.group(1).split(',')]
+        elif short_value_match:
+            prop.type = PropertyType.SHORT
+            prop.value = short_value_match.group(1)
+        elif byte_values_match:
+            prop.type = PropertyType.BYTE
+            prop.values = [v.strip() for v in 
byte_values_match.group(1).split(',')]
+        elif byte_value_match:
+            prop.type = PropertyType.BYTE
+            prop.value = byte_value_match.group(1)
         elif values_match:
-            # Array of values - preserve quotes on string literals
+            # Array of string values - preserve quotes on string literals
             values_str = values_match.group(1)
             prop.values = [v.strip() for v in values_str.split(',')]
         elif value_match:
@@ -463,9 +548,9 @@ class AnnotationMigrator:
             prop.value = raw_value
 
         if label_match:
-            prop.label = label_match.group(1)
+            prop.label = label_match.group(1)  # raw Java expression: 
"literal" or CONSTANT
         if desc_match:
-            prop.description = desc_match.group(1)
+            prop.description = desc_match.group(1)  # raw Java expression: 
"literal" or CONSTANT
 
         return prop
 
@@ -973,12 +1058,12 @@ class AnnotationMigrator:
         # Generate Config interface with label and description from @Component
         ocd_parts = []
         if self.component_label:
-            ocd_parts.append(f'name = "{self.component_label}"')
+            ocd_parts.append(f'name = {self.component_label}')
         else:
             ocd_parts.append(f'name = "{self.component_name} Configuration"')
 
         if self.component_description:
-            ocd_parts.append(f'description = "{self.component_description}"')
+            ocd_parts.append(f'description = {self.component_description}')
 
         ocd_attrs = ', '.join(ocd_parts)
 
diff --git a/skills/osgi-scr-migrator/scripts/tests/test_migrate_annotations.py 
b/skills/osgi-scr-migrator/scripts/tests/test_migrate_annotations.py
index 42c5f2b0..9f427771 100644
--- a/skills/osgi-scr-migrator/scripts/tests/test_migrate_annotations.py
+++ b/skills/osgi-scr-migrator/scripts/tests/test_migrate_annotations.py
@@ -110,8 +110,8 @@ class TestPropertyParsing(unittest.TestCase):
         self.assertEqual(len(migrator.component_properties), 1)
         prop = migrator.component_properties[0]
         self.assertEqual(prop.name, "timeout")
-        self.assertEqual(prop.label, "Timeout")
-        self.assertEqual(prop.description, "Request timeout in seconds")
+        self.assertEqual(prop.label, '"Timeout"')
+        self.assertEqual(prop.description, '"Request timeout in seconds"')
         self.assertTrue(prop.is_configurable)
 
     def test_property_name_derived_from_default_static_final_field(self):
@@ -127,7 +127,7 @@ static final String SERVICE_VENDOR = "service.vendor";
         self.assertEqual(len(migrator.component_properties), 1)
         prop = migrator.component_properties[0]
         self.assertEqual(prop.name, "service.vendor")
-        self.assertEqual(prop.value, "Apache Software Foundation")
+        self.assertEqual(prop.value, "\"Apache Software Foundation\"")
 
     def test_property_name_derived_from_private_static_final_field(self):
         """Test deriving property name from private static final field."""
@@ -142,7 +142,7 @@ private static final String SERVICE_VENDOR = 
"service.vendor";
         self.assertEqual(len(migrator.component_properties), 1)
         prop = migrator.component_properties[0]
         self.assertEqual(prop.name, "service.vendor")
-        self.assertEqual(prop.value, "Apache Software Foundation")
+        self.assertEqual(prop.value, "\"Apache Software Foundation\"")
 
     def test_property_name_derived_from_protected_static_final_field(self):
         """Test deriving property name from protected static final field."""
@@ -157,7 +157,7 @@ protected static final String SERVICE_VENDOR = 
"service.vendor";
         self.assertEqual(len(migrator.component_properties), 1)
         prop = migrator.component_properties[0]
         self.assertEqual(prop.name, "service.vendor")
-        self.assertEqual(prop.value, "Apache Software Foundation")
+        self.assertEqual(prop.value, "\"Apache Software Foundation\"")
 
     def test_property_name_derived_from_public_static_final_field(self):
         """Test deriving property name from public static final field."""
@@ -172,7 +172,308 @@ public static final String SERVICE_VENDOR = 
"service.vendor";
         self.assertEqual(len(migrator.component_properties), 1)
         prop = migrator.component_properties[0]
         self.assertEqual(prop.name, "service.vendor")
-        self.assertEqual(prop.value, "Apache Software Foundation")
+        self.assertEqual(prop.value, "\"Apache Software Foundation\"")
+
+    def test_property_name_as_simple_constant_reference(self):
+        """Test parsing property name as a simple constant reference."""
+        content = '''
+@Property(name = PROPERTY_NAME, value = "myvalue")
+private static final String PROPERTY_NAME = "my.property";
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "my.property")
+        self.assertEqual(prop.value, "\"myvalue\"")
+
+    def test_property_name_as_qualified_constant_reference(self):
+        """Test parsing property name as a qualified constant reference."""
+        content = '''
+@Property(name = Constants.SERVICE_VENDOR, value = "Apache")
+private static final String SERVICE_VENDOR = "service.vendor";
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "service.vendor")
+        self.assertEqual(prop.value, "\"Apache\"")
+
+    def test_property_name_as_literal_string(self):
+        """Test parsing property name as a literal quoted string."""
+        content = '''
+@Property(name = "service.vendor", value = "Apache")
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "service.vendor")
+        self.assertEqual(prop.value, "\"Apache\"")
+
+    def test_long_property(self):
+        """Test parsing long property."""
+        content = '''
+@Property(name = "cache.ttl", longValue = 86400L)
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "cache.ttl")
+        self.assertEqual(prop.value, "86400")
+        self.assertEqual(prop.type, PropertyType.LONG)
+        self.assertFalse(prop.values)
+
+    def test_float_property(self):
+        """Test parsing float property."""
+        content = '''
+@Property(name = "threshold", floatValue = 0.5f)
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "threshold")
+        self.assertEqual(prop.value, "0.5")
+        self.assertEqual(prop.type, PropertyType.FLOAT)
+        self.assertFalse(prop.values)
+
+    def test_double_property(self):
+        """Test parsing double property."""
+        content = '''
+@Property(name = "precision", doubleValue = 3.14)
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "precision")
+        self.assertEqual(prop.value, "3.14")
+        self.assertEqual(prop.type, PropertyType.DOUBLE)
+        self.assertFalse(prop.values)
+
+    def test_char_property(self):
+        """Test parsing char property."""
+        content = '''
+@Property(name = "separator", charValue = ',')
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "separator")
+        self.assertEqual(prop.value, "','")
+        self.assertEqual(prop.type, PropertyType.CHAR)
+        self.assertFalse(prop.values)
+
+    def test_char_property_escape_sequence(self):
+        """Test parsing char property with escape sequence."""
+        content = '''
+@Property(name = "newline", charValue = '\\n')
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "newline")
+        self.assertEqual(prop.type, PropertyType.CHAR)
+        self.assertFalse(prop.values)
+
+    def test_short_property(self):
+        """Test parsing short property."""
+        content = '''
+@Property(name = "port", shortValue = 8080)
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "port")
+        self.assertEqual(prop.value, "8080")
+        self.assertEqual(prop.type, PropertyType.SHORT)
+        self.assertFalse(prop.values)
+
+    def test_byte_property(self):
+        """Test parsing byte property."""
+        content = '''
+@Property(name = "flags", byteValue = 42)
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "flags")
+        self.assertEqual(prop.value, "42")
+        self.assertEqual(prop.type, PropertyType.BYTE)
+        self.assertFalse(prop.values)
+
+    def test_boolean_array_property(self):
+        """Test parsing boolean array property."""
+        content = '''
+@Property(name = "flags", boolValue = {true, false, true})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "flags")
+        self.assertEqual(prop.values, ["true", "false", "true"])
+        self.assertEqual(prop.type, PropertyType.BOOLEAN)
+        self.assertTrue(prop.values)
+
+    def test_integer_array_property(self):
+        """Test parsing integer array property."""
+        content = '''
+@Property(name = "ports", intValue = {80, 443, 8080})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "ports")
+        self.assertEqual(prop.values, ["80", "443", "8080"])
+        self.assertEqual(prop.type, PropertyType.INTEGER)
+        self.assertTrue(prop.values)
+
+    def test_long_array_property(self):
+        """Test parsing long array property."""
+        content = '''
+@Property(name = "timestamps", longValue = {1000L, 2000L, 3000L})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "timestamps")
+        self.assertEqual(prop.values, ["1000L", "2000L", "3000L"])
+        self.assertEqual(prop.type, PropertyType.LONG)
+        self.assertTrue(prop.values)
+
+    def test_float_array_property(self):
+        """Test parsing float array property."""
+        content = '''
+@Property(name = "ratios", floatValue = {0.25f, 0.5f, 0.75f})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "ratios")
+        self.assertEqual(prop.values, ["0.25f", "0.5f", "0.75f"])
+        self.assertEqual(prop.type, PropertyType.FLOAT)
+        self.assertTrue(prop.values)
+
+    def test_double_array_property(self):
+        """Test parsing double array property."""
+        content = '''
+@Property(name = "coefficients", doubleValue = {1.1, 2.2, 3.3})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "coefficients")
+        self.assertEqual(prop.values, ["1.1", "2.2", "3.3"])
+        self.assertEqual(prop.type, PropertyType.DOUBLE)
+        self.assertTrue(prop.values)
+
+    def test_char_array_property(self):
+        """Test parsing char array property."""
+        content = """
+@Property(name = "delimiters", charValue = {',', ';', '|'})
+"""
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "delimiters")
+        self.assertEqual(prop.values, ["','", "';'", "'|'"])
+        self.assertEqual(prop.type, PropertyType.CHAR)
+        self.assertTrue(prop.values)
+
+    def test_short_array_property(self):
+        """Test parsing short array property."""
+        content = '''
+@Property(name = "codes", shortValue = {100, 200, 404})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "codes")
+        self.assertEqual(prop.values, ["100", "200", "404"])
+        self.assertEqual(prop.type, PropertyType.SHORT)
+        self.assertTrue(prop.values)
+
+    def test_byte_array_property(self):
+        """Test parsing byte array property."""
+        content = '''
+@Property(name = "mask", byteValue = {0, 1, 127})
+'''
+        stats = MigrationStats()
+        migrator = AnnotationMigrator(content, Path("test.java"), stats)
+        migrator._collect_properties()
+
+        self.assertEqual(len(migrator.component_properties), 1)
+        prop = migrator.component_properties[0]
+        self.assertEqual(prop.name, "mask")
+        self.assertEqual(prop.values, ["0", "1", "127"])
+        self.assertEqual(prop.type, PropertyType.BYTE)
+        self.assertTrue(prop.values)
+
+    def test_single_value_not_array(self):
+        """Test that single-value properties have an empty values list."""
+        for content, expected_type in [
+            ('@Property(name = "a", boolValue = true)', PropertyType.BOOLEAN),
+            ('@Property(name = "a", intValue = 1)', PropertyType.INTEGER),
+            ('@Property(name = "a", longValue = 1L)', PropertyType.LONG),
+            ('@Property(name = "a", floatValue = 1.0f)', PropertyType.FLOAT),
+            ('@Property(name = "a", doubleValue = 1.0)', PropertyType.DOUBLE),
+            ('@Property(name = "a", shortValue = 1)', PropertyType.SHORT),
+            ('@Property(name = "a", byteValue = 1)', PropertyType.BYTE),
+        ]:
+            stats = MigrationStats()
+            migrator = AnnotationMigrator(content, Path("test.java"), stats)
+            migrator._collect_properties()
+            prop = migrator.component_properties[0]
+            self.assertFalse(prop.values, f"Expected empty values for 
{expected_type}")
+            self.assertEqual(prop.type, expected_type)
 
 
 class TestPropertyConversion(unittest.TestCase):
@@ -217,8 +518,8 @@ class TestPropertyConversion(unittest.TestCase):
             name="my.timeout",
             value="30",
             type=PropertyType.INTEGER,
-            label="Timeout",
-            description="Request timeout"
+            label='"Timeout"',
+            description='"Request timeout"'
         )
         result = prop.to_metatype_attribute()
 

Reply via email to