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()