This is an automated email from the ASF dual-hosted git repository.
tv pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jcs.git
The following commit(s) were added to refs/heads/master by this push:
new 1a7c35b7 Remove WIP document
1a7c35b7 is described below
commit 1a7c35b79ad258209eb7721871045bed4c933cc9
Author: Thomas Vandahl <[email protected]>
AuthorDate: Sun Feb 8 23:18:34 2026 +0100
Remove WIP document
---
RECORDS_REFACTORING_EXAMPLE.md | 790 -----------------------------------------
1 file changed, 790 deletions(-)
diff --git a/RECORDS_REFACTORING_EXAMPLE.md b/RECORDS_REFACTORING_EXAMPLE.md
deleted file mode 100644
index e2b49ae2..00000000
--- a/RECORDS_REFACTORING_EXAMPLE.md
+++ /dev/null
@@ -1,790 +0,0 @@
-<!---
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-# Refactoring PropertySetter Pattern to Java Records
-
-## Current Approach: PropertySetter with Reflection
-
-The current codebase uses `PropertySetter` (similar to log4j's implementation)
to dynamically populate mutable objects from `Properties` using reflection and
JavaBeans introspection.
-
-**Example at line 180 in CompositeCacheConfigurator:**
-```java
-AuxiliaryCacheAttributes auxAttr = ccm.registryAttrGet( auxName );
-auxAttr = auxAttr.clone();
-PropertySetter.setProperties( auxAttr, props, attrName + "." );
-auxAttr.setCacheName( regName );
-```
-
-The pattern:
-1. Create/clone a mutable object
-2. Use reflection to introspect setter methods
-3. Parse and convert property strings to target types
-4. Invoke setters via reflection
-5. Manual post-setup (e.g., `setCacheName()`)
-
----
-
-## Problems with Current Approach
-
-- **Boilerplate**: Each attribute class needs getters/setters for every
property
-- **No compile-time safety**: Property names and types are strings, discovered
at runtime
-- **Mutability**: Objects are mutable, making debugging harder
-- **Performance**: Reflection overhead, introspection on every configuration
load
-- **Inheritance complexity**: Child classes override parent setters, must
maintain parallel hierarchies
-- **Cloning requirement**: Objects must be cloneable for reuse, adds complexity
-
----
-
-## Solution: Java Records + Builder Pattern
-
-Java Records (Java 14+, finalized in Java 16) provide immutable, compact data
carriers. Combined with a builder pattern, they eliminate reflection while
maintaining flexibility.
-
-### Approach 1: Simple Record + Static Builder
-
-**Before:**
-```java
-// CompositeCacheAttributes.java (current)
-public class CompositeCacheAttributes implements ICompositeCacheAttributes {
- private boolean useLateral = DEFAULT_USE_LATERAL;
- private boolean useRemote = DEFAULT_USE_REMOTE;
- private boolean useDisk = DEFAULT_USE_DISK;
- private int maxObjs = DEFAULT_MAX_OBJECTS;
- private long maxMemoryIdleTimeSeconds =
DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS;
- private String cacheName;
- private String memoryCacheName;
- // ... 15+ more properties
-
- public void setUseLateral(boolean val) { this.useLateral = val; }
- public void setUseRemote(boolean val) { this.useRemote = val; }
- public void setUseDisk(boolean val) { this.useDisk = val; }
- // ... more setters
-}
-```
-
-**After with Records:**
-```java
-public record CompositeCacheAttributes(
- boolean useLateral,
- boolean useRemote,
- boolean useDisk,
- boolean useMemoryShrinker,
- int maxObjs,
- long maxMemoryIdleTimeSeconds,
- long shrinkerIntervalSeconds,
- int maxSpoolPerRun,
- String cacheName,
- String memoryCacheName,
- DiskUsagePattern diskUsagePattern
-) implements ICompositeCacheAttributes {
-
- // Compact constructor for validation
- public CompositeCacheAttributes {
- // Add validation here if needed
- }
-
- // Static factory with builder
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder {
- private boolean useLateral = DEFAULT_USE_LATERAL;
- private boolean useRemote = DEFAULT_USE_REMOTE;
- private boolean useDisk = DEFAULT_USE_DISK;
- private boolean useMemoryShrinker = DEFAULT_USE_SHRINKER;
- private int maxObjs = DEFAULT_MAX_OBJECTS;
- private long maxMemoryIdleTimeSeconds =
DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS;
- private long shrinkerIntervalSeconds =
DEFAULT_SHRINKER_INTERVAL_SECONDS;
- private int maxSpoolPerRun = DEFAULT_MAX_SPOOL_PER_RUN;
- private String cacheName;
- private String memoryCacheName;
- private DiskUsagePattern diskUsagePattern = DiskUsagePattern.SWAP;
-
- public Builder useLateral(boolean val) { this.useLateral = val; return
this; }
- public Builder useRemote(boolean val) { this.useRemote = val; return
this; }
- public Builder useDisk(boolean val) { this.useDisk = val; return this;
}
- public Builder useMemoryShrinker(boolean val) { this.useMemoryShrinker
= val; return this; }
- public Builder maxObjs(int val) { this.maxObjs = val; return this; }
- public Builder maxMemoryIdleTimeSeconds(long val) {
this.maxMemoryIdleTimeSeconds = val; return this; }
- public Builder shrinkerIntervalSeconds(long val) {
this.shrinkerIntervalSeconds = val; return this; }
- public Builder maxSpoolPerRun(int val) { this.maxSpoolPerRun = val;
return this; }
- public Builder cacheName(String val) { this.cacheName = val; return
this; }
- public Builder memoryCacheName(String val) { this.memoryCacheName =
val; return this; }
- public Builder diskUsagePattern(DiskUsagePattern val) {
this.diskUsagePattern = val; return this; }
-
- public CompositeCacheAttributes build() {
- return new CompositeCacheAttributes(
- useLateral, useRemote, useDisk, useMemoryShrinker,
- maxObjs, maxMemoryIdleTimeSeconds, shrinkerIntervalSeconds,
- maxSpoolPerRun, cacheName, memoryCacheName, diskUsagePattern
- );
- }
- }
-}
-```
-
-### Usage Comparison
-
-**Current (PropertySetter with reflection):**
-```java
-ICompositeCacheAttributes ccAttr = new CompositeCacheAttributes();
-PropertySetter.setProperties(ccAttr, props, attrName + ".");
-ccAttr.setCacheName(regName);
-```
-
-**New (Type-safe builder):**
-```java
-var builder = ICompositeCacheAttributes.builder();
-for (String key : props.stringPropertyNames()) {
- if (key.startsWith(attrName + ".")) {
- String propName = key.substring(attrName.length() + 1);
- String value = props.getProperty(key);
- builder.setPropertyByName(propName, value); // see below
- }
-}
-ICompositeCacheAttributes ccAttr = builder.cacheName(regName).build();
-```
-
----
-
-### Approach 2: Type-Safe Configuration Parser (Recommended)
-
-Instead of generic `PropertySetter`, create a **configuration parser**
specific to each record type:
-
-```java
-public record CompositeCacheAttributes(...) implements
ICompositeCacheAttributes {
-
- public static class Parser {
- private static final Map<String, BiConsumer<Builder, String>>
PROPERTY_SETTERS = Map.ofEntries(
- Map.entry("useLateral", (b, v) ->
b.useLateral(Boolean.parseBoolean(v))),
- Map.entry("useRemote", (b, v) ->
b.useRemote(Boolean.parseBoolean(v))),
- Map.entry("useDisk", (b, v) -> b.useDisk(Boolean.parseBoolean(v))),
- Map.entry("useMemoryShrinker", (b, v) ->
b.useMemoryShrinker(Boolean.parseBoolean(v))),
- Map.entry("maxObjs", (b, v) -> b.maxObjs(Integer.parseInt(v))),
- Map.entry("maxMemoryIdleTimeSeconds", (b, v) ->
b.maxMemoryIdleTimeSeconds(Long.parseLong(v))),
- Map.entry("shrinkerIntervalSeconds", (b, v) ->
b.shrinkerIntervalSeconds(Long.parseLong(v))),
- Map.entry("maxSpoolPerRun", (b, v) ->
b.maxSpoolPerRun(Integer.parseInt(v))),
- Map.entry("memoryCacheName", (b, v) -> b.memoryCacheName(v)),
- Map.entry("diskUsagePattern", (b, v) ->
b.diskUsagePattern(DiskUsagePattern.valueOf(v.toUpperCase())))
- );
-
- public static CompositeCacheAttributes fromProperties(
- Properties props,
- String prefix,
- String cacheName) {
- var builder = CompositeCacheAttributes.builder();
- builder.cacheName(cacheName);
-
- final int prefixLen = prefix.length();
- for (String key : props.stringPropertyNames()) {
- if (key.startsWith(prefix)) {
- // Ignore nested properties (containing dots after prefix)
- if (key.indexOf('.', prefixLen + 1) > 0) {
- continue;
- }
-
- String propName = key.substring(prefixLen);
- String value = props.getProperty(key);
-
- var setter = PROPERTY_SETTERS.get(propName);
- if (setter != null) {
- try {
- setter.accept(builder, value);
- } catch (NumberFormatException |
IllegalArgumentException e) {
- log.warn("Failed to parse property {}: {}", key,
e.getMessage());
- }
- } else {
- log.warn("Unknown property: {}", propName);
- }
- }
- }
-
- return builder.build();
- }
- }
-}
-```
-
-**Usage:**
-```java
-ICompositeCacheAttributes ccAttr =
CompositeCacheAttributes.Parser.fromProperties(
- props, attrName + ".", regName
-);
-```
-
-**Benefits:**
-- ✅ Type-safe: Each property has explicit type conversion
-- ✅ Compile-time safety: Property names are constants in the map
-- ✅ No reflection: Direct method calls via `BiConsumer`
-- ✅ Better error handling: Specific exception handling per property type
-- ✅ Immutable: Records are immutable by default
-- ✅ Performance: No introspection overhead
-- ✅ IDE support: Auto-completion and refactoring work perfectly
-
----
-
-### Approach 3: Annotation-Based Configuration (Most Elegant)
-
-For a more scalable solution that reduces boilerplate, use annotations:
-
-#### Annotation Definition
-
-```java
-package org.apache.commons.jcs4.utils.config;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks a record component as a configurable property that can be loaded from
Properties.
- * Applied to individual record components.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.RECORD_COMPONENT)
-public @interface ConfigurableProperty {
-
- /**
- * The property name in the configuration file.
- * If not specified, defaults to the record component name.
- */
- String name() default "";
-
- /**
- * The type of conversion needed: "boolean", "int", "long", "double",
"string", "enum"
- */
- String type();
-
- /**
- * Default value if property is not provided (as a string).
- */
- String defaultValue() default "";
-
- /**
- * Whether this property is required (no default).
- */
- boolean required() default false;
-
- /**
- * For enum types, the enum class name (if type="enum").
- */
- String enumClass() default "";
-}
-
-/**
- * Applied to record classes to enable configuration parsing.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Configurable {
- /**
- * Optional description of what this configuration represents.
- */
- String description() default "";
-}
-```
-
-#### Record Definition
-
-```java
-@Configurable(description = "Composite cache region attributes")
-public record CompositeCacheAttributes(
- @ConfigurableProperty(name = "useLateral", type = "boolean", defaultValue
= "true")
- boolean useLateral,
-
- @ConfigurableProperty(name = "useRemote", type = "boolean", defaultValue =
"true")
- boolean useRemote,
-
- @ConfigurableProperty(name = "useDisk", type = "boolean", defaultValue =
"true")
- boolean useDisk,
-
- @ConfigurableProperty(name = "useMemoryShrinker", type = "boolean",
defaultValue = "false")
- boolean useMemoryShrinker,
-
- @ConfigurableProperty(name = "maxObjs", type = "int", defaultValue = "100")
- int maxObjs,
-
- @ConfigurableProperty(name = "maxMemoryIdleTimeSeconds", type = "long",
defaultValue = "7200")
- long maxMemoryIdleTimeSeconds,
-
- @ConfigurableProperty(name = "shrinkerIntervalSeconds", type = "long",
defaultValue = "30")
- long shrinkerIntervalSeconds,
-
- @ConfigurableProperty(name = "maxSpoolPerRun", type = "int", defaultValue
= "-1")
- int maxSpoolPerRun,
-
- @ConfigurableProperty(name = "memoryCacheName", type = "string",
- defaultValue =
"org.apache.commons.jcs4.engine.memory.lru.LRUMemoryCache")
- String memoryCacheName,
-
- @ConfigurableProperty(name = "diskUsagePattern", type = "enum",
- enumClass =
"org.apache.commons.jcs4.engine.DiskUsagePattern",
- defaultValue = "SWAP")
- DiskUsagePattern diskUsagePattern,
-
- // Not annotated - injected programmatically
- String cacheName
-) implements ICompositeCacheAttributes {
-
- public static CompositeCacheAttributes fromProperties(
- Properties props,
- String prefix,
- String cacheName) {
- return ConfigurationBuilder.create(CompositeCacheAttributes.class)
- .fromProperties(props, prefix)
- .set("cacheName", cacheName)
- .build();
- }
-}
-```
-
-#### Generic Configuration Builder
-
-```java
-package org.apache.commons.jcs4.utils.config;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.RecordComponent;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Generic builder for record types annotated with @Configurable.
- * Automatically parses Properties and creates record instances.
- */
-public class ConfigurationBuilder<T> {
-
- private static final Log log = Log.getLog(ConfigurationBuilder.class);
-
- private final Class<T> recordClass;
- private final Map<String, Object> values = new HashMap<>();
- private final Map<String, String> errors = new HashMap<>();
-
- private ConfigurationBuilder(Class<T> recordClass) {
- if (!recordClass.isRecord()) {
- throw new IllegalArgumentException(recordClass.getName() + " is
not a record");
- }
- this.recordClass = recordClass;
- initializeDefaults();
- }
-
- /**
- * Create a new builder for the given record class.
- */
- public static <T> ConfigurationBuilder<T> create(Class<T> recordClass) {
- return new ConfigurationBuilder<>(recordClass);
- }
-
- /**
- * Initialize default values from annotations.
- */
- private void initializeDefaults() {
- RecordComponent[] components = recordClass.getRecordComponents();
- for (RecordComponent component : components) {
- ConfigurableProperty prop =
component.getAnnotation(ConfigurableProperty.class);
- if (prop != null && !prop.defaultValue().isEmpty()) {
- Object value = parseValue(prop.defaultValue(), prop.type(),
prop.enumClass());
- if (value != null) {
- values.put(component.getName(), value);
- }
- }
- }
- }
-
- /**
- * Load properties from a Properties object with a given prefix.
- */
- public ConfigurationBuilder<T> fromProperties(Properties props, String
prefix) {
- if (props == null || props.isEmpty()) {
- return this;
- }
-
- RecordComponent[] components = recordClass.getRecordComponents();
- final int prefixLen = prefix.length();
-
- for (RecordComponent component : components) {
- ConfigurableProperty prop =
component.getAnnotation(ConfigurableProperty.class);
- if (prop == null) {
- continue;
- }
-
- String propName = prop.name().isEmpty() ? component.getName() :
prop.name();
- String fullKey = prefix + propName;
- String value = props.getProperty(fullKey);
-
- if (value != null) {
- try {
- Object parsed = parseValue(value, prop.type(),
prop.enumClass());
- if (parsed != null) {
- values.put(component.getName(), parsed);
- log.debug("Loaded property {}: {}", fullKey, parsed);
- }
- } catch (Exception e) {
- String errMsg = String.format(
- "Failed to parse property '%s' with value '%s' as %s",
- fullKey, value, prop.type()
- );
- errors.put(component.getName(), errMsg);
- log.warn("{}: {}", errMsg, e.getMessage());
- }
- } else if (prop.required()) {
- String errMsg = String.format("Required property '%s' not
found", fullKey);
- errors.put(component.getName(), errMsg);
- log.error(errMsg);
- }
- }
-
- return this;
- }
-
- /**
- * Explicitly set a property value.
- */
- public ConfigurationBuilder<T> set(String componentName, Object value) {
- if (value != null) {
- values.put(componentName, value);
- }
- return this;
- }
-
- /**
- * Explicitly set a property value from a string.
- */
- public ConfigurationBuilder<T> setProperty(String componentName, String
value) {
- RecordComponent component = findComponent(componentName);
- if (component == null) {
- log.warn("No record component named: {}", componentName);
- return this;
- }
-
- ConfigurableProperty prop =
component.getAnnotation(ConfigurableProperty.class);
- if (prop != null) {
- try {
- Object parsed = parseValue(value, prop.type(),
prop.enumClass());
- if (parsed != null) {
- values.put(componentName, parsed);
- }
- } catch (Exception e) {
- errors.put(componentName, e.getMessage());
- log.warn("Failed to set property {}: {}", componentName,
e.getMessage());
- }
- } else {
- values.put(componentName, value);
- }
-
- return this;
- }
-
- /**
- * Check if there were any errors during configuration.
- */
- public boolean hasErrors() {
- return !errors.isEmpty();
- }
-
- /**
- * Get all configuration errors.
- */
- public Map<String, String> getErrors() {
- return new HashMap<>(errors);
- }
-
- /**
- * Build the record instance, throwing if any required properties are
missing.
- */
- public T build() {
- // Validate all required properties are present
- for (RecordComponent component : recordClass.getRecordComponents()) {
- ConfigurableProperty prop =
component.getAnnotation(ConfigurableProperty.class);
- if (prop != null && prop.required() &&
!values.containsKey(component.getName())) {
- throw new IllegalStateException(
- "Required property missing: " + component.getName()
- );
- }
- }
-
- return createInstance();
- }
-
- /**
- * Build the record instance, returning null if any errors occurred.
- */
- public T buildSafely() {
- if (hasErrors()) {
- return null;
- }
- return createInstance();
- }
-
- /**
- * Create the record instance using the canonical constructor.
- */
- private T createInstance() {
- try {
- RecordComponent[] components = recordClass.getRecordComponents();
- Class<?>[] paramTypes = new Class<?>[components.length];
- Object[] params = new Object[components.length];
-
- for (int i = 0; i < components.length; i++) {
- paramTypes[i] = components[i].getType();
- Object value = values.get(components[i].getName());
- if (value == null) {
- // Use null-safe defaults for primitives
- value = getDefaultForType(components[i].getType());
- }
- params[i] = value;
- }
-
- Constructor<T> constructor =
recordClass.getDeclaredConstructor(paramTypes);
- T instance = constructor.newInstance(params);
- log.debug("Created instance of {}", recordClass.getSimpleName());
- return instance;
-
- } catch (Exception e) {
- throw new RuntimeException(
- "Failed to create instance of " + recordClass.getName(), e
- );
- }
- }
-
- /**
- * Parse a string value to the appropriate type.
- */
- private Object parseValue(String value, String type, String enumClassName)
{
- if (value == null || value.trim().isEmpty()) {
- return null;
- }
-
- String trimmed = value.trim();
-
- try {
- return switch (type.toLowerCase()) {
- case "boolean" -> Boolean.parseBoolean(trimmed);
- case "int" -> Integer.parseInt(trimmed);
- case "long" -> Long.parseLong(trimmed);
- case "double" -> Double.parseDouble(trimmed);
- case "string" -> value;
- case "enum" -> parseEnum(trimmed, enumClassName);
- default -> {
- log.warn("Unknown type for conversion: {}", type);
- yield value;
- }
- };
- } catch (Exception e) {
- throw new RuntimeException(
- String.format("Cannot convert '%s' to type %s", value, type), e
- );
- }
- }
-
- /**
- * Parse an enum value.
- */
- @SuppressWarnings("unchecked")
- private Object parseEnum(String value, String enumClassName) throws
Exception {
- Class<? extends Enum> enumClass = (Class<? extends Enum>)
Class.forName(enumClassName);
- return Enum.valueOf(enumClass, value.toUpperCase());
- }
-
- /**
- * Get default value for a primitive type.
- */
- private Object getDefaultForType(Class<?> type) {
- if (type == boolean.class) return false;
- if (type == int.class) return 0;
- if (type == long.class) return 0L;
- if (type == double.class) return 0.0d;
- if (type == float.class) return 0.0f;
- return null;
- }
-
- /**
- * Find a record component by name.
- */
- private RecordComponent findComponent(String name) {
- for (RecordComponent component : recordClass.getRecordComponents()) {
- if (component.getName().equals(name)) {
- return component;
- }
- }
- return null;
- }
-}
-```
-
-#### Usage Examples
-
-```java
-// Simple case: load from properties
-Properties props = new Properties();
-props.load(new FileInputStream("cache.properties"));
-CompositeCacheAttributes attrs = ConfigurationBuilder
- .create(CompositeCacheAttributes.class)
- .fromProperties(props, "jcs.region.myregion.cacheattributes.")
- .set("cacheName", "myregion")
- .build();
-
-// With error handling
-var builder = ConfigurationBuilder.create(CompositeCacheAttributes.class)
- .fromProperties(props, "jcs.region.myregion.cacheattributes.")
- .set("cacheName", "myregion");
-
-if (builder.hasErrors()) {
- builder.getErrors().forEach((prop, error) ->
- log.error("Configuration error in {}: {}", prop, error)
- );
- return null;
-}
-
-CompositeCacheAttributes attrs = builder.build();
-
-// In CompositeCacheConfigurator.parseCompositeCacheAttributes()
-protected ICompositeCacheAttributes parseCompositeCacheAttributes(
- final Properties props,
- final String regName,
- final ICompositeCacheAttributes defaultCCAttr,
- final String regionPrefix) {
-
- final String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
-
- try {
- var builder =
ConfigurationBuilder.create(CompositeCacheAttributes.class)
- .fromProperties(props, attrName + ".");
-
- if (builder.hasErrors()) {
- log.error("Configuration errors for region {}: {}",
- regName, builder.getErrors());
- return defaultCCAttr;
- }
-
- return builder.set("cacheName", regName).build();
- } catch (Exception e) {
- log.error("Failed to parse cache attributes for region {}", regName,
e);
- return defaultCCAttr;
- }
-}
-
-// Works with other record types too (ElementAttributes, etc.)
-ElementAttributes elemAttrs = ConfigurationBuilder
- .create(ElementAttributes.class)
- .fromProperties(props, "jcs.region.myregion.elementattributes.")
- .build();
-```
-
-#### Advantages of Annotation-Based Approach
-
-- **Single Source of Truth**: Metadata is embedded in the record definition
-- **Type-Safe**: Compiler ensures properties exist and have correct types
-- **Minimal Boilerplate**: No need to write parser classes for each record type
-- **Reusable Infrastructure**: `ConfigurationBuilder` works for any
`@Configurable` record
-- **Runtime Flexibility**: Properties can be added without recompilation
-- **Better IDE Support**: Annotations provide navigation and quick-fixes
-- **Self-Documenting**: Annotations serve as documentation
-- **Extensible**: Easy to add new annotation features (validation,
transformation, etc.)
-```
-
----
-
-## Handling Inheritance (Child Types)
-
-If you have child attribute classes inheriting from parent classes, records
handle this differently:
-
-**Current (with inheritance):**
-```java
-public abstract class AbstractAuxiliaryCacheAttributes { ... }
-public class JDBCAuxiliaryCacheAttributes extends
AbstractAuxiliaryCacheAttributes { ... }
-```
-
-**With Records (sealed types):**
-```java
-// Base record
-public record AuxiliaryCacheAttributes(
- String name,
- int maxFailureWaitTimeSeconds,
- long failureCountWaitTimeSeconds,
- // common properties
-) { }
-
-// Sealed type hierarchy (Java 17+)
-public sealed record JDBCAuxiliaryCacheAttributes(
- String name,
- int maxFailureWaitTimeSeconds,
- long failureCountWaitTimeSeconds,
- String username,
- String password,
- String driver,
- // JDBC-specific properties
-) extends AuxiliaryCacheAttributes { }
-
-public sealed record RemoteAuxiliaryCacheAttributes(
- String name,
- int maxFailureWaitTimeSeconds,
- long failureCountWaitTimeSeconds,
- String remoteHost,
- int remotePort,
- // Remote-specific properties
-) extends AuxiliaryCacheAttributes { }
-```
-
----
-
-## Migration Strategy
-
-1. **Phase 1**: Create new record classes alongside existing ones
-2. **Phase 2**: Add parsers for records (Approach 2 above)
-3. **Phase 3**: Update configurators to use new parsers
-4. **Phase 4**: Gradually deprecate old attribute classes
-5. **Phase 5**: Remove PropertySetter dependency
-
-**Example migration in CompositeCacheConfigurator:**
-
-```java
-// OLD (lines 258-266)
-protected ICompositeCacheAttributes parseCompositeCacheAttributes(...) {
- ICompositeCacheAttributes ccAttr = new CompositeCacheAttributes();
- final String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
- PropertySetter.setProperties(ccAttr, props, attrName + ".");
- ccAttr.setCacheName(regName);
- return ccAttr;
-}
-
-// NEW
-protected ICompositeCacheAttributes parseCompositeCacheAttributes(...) {
- final String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
- return CompositeCacheAttributes.Parser.fromProperties(
- props, attrName + ".", regName
- );
-}
-```
-
----
-
-## Summary
-
-| Aspect | PropertySetter | Record + Builder | Record + Parser | Record +
Annotation |
-|--------|---|---|---|---|
-| Boilerplate | High | Medium | Low | Very Low |
-| Type Safety | Low | High | High | High |
-| Performance | Low (Reflection) | High | High | Medium |
-| Error Handling | Generic | Better | Best | Good |
-| IDE Support | Limited | Excellent | Excellent | Excellent |
-| Maintenance | Difficult | Easier | Easy | Easiest |
-| Learning Curve | Moderate | Low | Low | Medium |
-| Immutability | No | Yes | Yes | Yes |
-
-**Recommendation**: Start with **Approach 2 (Type-Safe Parser)** for immediate
benefits with minimal refactoring. Evolve to **Approach 3 (Annotation-Based)**
if configuration complexity grows.