# Developer Experience Improvements for bstore-java ## Current State Analysis ### Current API Patterns - Field access: `Product.name.set(product, "Alice")` / `Product.name.get(product, null)` - Slot-based access: `dbo.set(nameSlot, "Alice")` / `dbo.get(nameSlot, null)` - Positional access: `dbo.set(0, value)` / `dbo.get(0)` (already Java-like!) - Field-based queries: `PersonDBO.AGE.gte(18)` - Action chaining: `terminal.begin().load(PersonDBO).filterBy(...).execute()` ### Issues Identified 1. **Verbose field access**: `Product.name.set(product, "Alice")` is verbose 2. **No convenience methods**: Can't use `dbo.get(Product.NAME)` or `dbo.set(Product.NAME, "Alice")` 3. **No equals/hashCode**: Can't compare DBOs by value 4. **No cloning**: Can't easily copy DBO instances 5. **No map conversion**: Can't convert to/from `Map` 6. **No fluent API**: Missing `withField()` methods for method chaining 7. **No field lookup by name**: Entity doesn't have `getField(String name)` method 8. **toString() could be better**: Currently returns JSON, could have human-readable format ## Proposed Improvements ### 1. Convenience Methods for Field Access (High Impact) ✅ **Goal**: Allow `dbo.get(Product.NAME)` instead of `Product.NAME.get(dbo, null)` **Implementation**: ```java /** * Get field value by Field instance. * * @param field Field to get value for * @return Field value, or null if not set */ public Object get(Field field) { return get(field, null); } /** * Get field value by Field instance with default. * * @param field Field to get value for * @param defaultValue Default value if field is null * @return Field value, or defaultValue if not set */ public Object get(Field field, Object defaultValue) { return get((Slot)field, defaultValue); } /** * Set field value by Field instance. * * @param field Field to set value for * @param value Value to set * @return Self for chaining */ public DBO set(Field field, Object value) { return (DBO)set((Slot)field, value); } ``` **Benefits**: - More intuitive: `person.get(PersonDBO.NAME)` vs `PersonDBO.NAME.get(person, null)` - Consistent with positional access pattern - Backward compatible (Field.get/set still work) **Trade-offs**: - Minimal - just convenience wrappers ### 2. Equals and HashCode (Medium Impact) **Goal**: Value-based comparison **Implementation**: ```java @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; DBO other = (DBO)obj; // Compare entity types if (type != other.type) return false; if (type == null) return true; // Compare by primary key if available Field pk = type.getPk(); if (pk != null) { Object thisPk = get(pk); Object otherPk = other.get(pk); if (thisPk != null && otherPk != null) { return thisPk.equals(otherPk); } } // Compare all values if (values == null) return other.values == null; if (other.values == null) return false; if (values.length != other.values.length) return false; for (int i = 0; i < values.length; i++) { if (!java.util.Objects.equals(values[i], other.values[i])) { return false; } } return true; } @Override public int hashCode() { if (type == null) return System.identityHashCode(this); // Hash by primary key if available Field pk = type.getPk(); if (pk != null) { Object pkValue = get(pk); if (pkValue != null) { return java.util.Objects.hash(type.getName(), pkValue); } } // Hash by all values return java.util.Objects.hash(type.getName(), java.util.Arrays.hashCode(values)); } ``` ### 3. Cloning Support (Low Effort) **Goal**: Standard Java pattern **Implementation**: ```java /** * Create a shallow copy of this DBO. * * @return New DBO instance with copied values */ public DBO clone() { DBO cloned = new DBO(); cloned.setType(type); cloned.setTerminal(terminal); cloned.status = status; if (values != null) { cloned.values = values.clone(); } return cloned; } ``` ### 4. Map Conversion (Medium Impact) **Goal**: Dictionary-like access **Implementation**: ```java import java.util.Map; import java.util.HashMap; /** * Convert DBO to Map with field names as keys. * * @return Map with field names as keys and values as values */ public Map toMap() { Map result = new HashMap<>(); if (type != null) { FieldSlice slice = new FieldSlice(type); while (slice.hasNext()) { Field field = slice.next(); Object value = get(field); if (value != null) { result.put(field.getName(), value); } } } return result; } /** * Create DBO instance from Map. * * @param cls DBO class * @param data Map with field values * @param terminal Optional terminal to attach * @return New DBO instance */ public static T fromMap(Class cls, Map data, Terminal terminal) { try { T instance = cls.getDeclaredConstructor().newInstance(); if (terminal != null) { instance.setTerminal(terminal); } Entity entity = Entity.recall(cls); if (entity != null && data != null) { for (Map.Entry entry : data.entrySet()) { Field field = entity.getField(entry.getKey()); if (field != null) { instance.set(field, entry.getValue()); } } } return instance; } catch (Exception e) { throw new RuntimeException("Failed to create DBO from map", e); } } ``` ### 5. Fluent API (Low Effort) **Goal**: Method chaining **Implementation**: ```java /** * Set field value (fluent API). * * @param field Field to set * @param value Value to set * @return Self for chaining */ public DBO withField(Field field, Object value) { set(field, value); return this; } /** * Set multiple fields from Map (fluent API). * * @param data Map with field values * @return Self for chaining */ public DBO withFields(Map data) { if (type != null && data != null) { for (Map.Entry entry : data.entrySet()) { Field field = type.getField(entry.getKey()); if (field != null) { set(field, entry.getValue()); } } } return this; } ``` ### 6. Entity Field Lookup by Name (Low Effort) **Goal**: Find fields by name **Implementation**: ```java // In Entity class /** * Get field by name (case-insensitive). * * @param name Field name to find * @return Field instance or null if not found */ public Field getField(String name) { if (name == null) return null; // Search own fields for (Slot slot : getOwnSlots()) { if (slot instanceof Field && slot.equals(name)) { return (Field)slot; } } // Search base entity if (base != null) { Field field = base.getField(name); if (field != null) return field; } return null; } ``` ### 7. Better toString() (Low Effort) **Goal**: Human-readable representation **Implementation**: ```java /** * Return human-readable string representation. * * @return String like "PersonDBO(name=Alice, age=30)" */ public String toDisplayString() { if (type == null) { return getClass().getSimpleName() + "()"; } StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append("("); FieldSlice slice = new FieldSlice(type); boolean first = true; while (slice.hasNext()) { Field field = slice.next(); Object value = get(field); if (value != null) { if (!first) sb.append(", "); sb.append(field.getName()).append("=").append(value); first = false; } } sb.append(")"); return sb.toString(); } // Keep existing toString() for JSON representation ``` ## Priority Recommendations ### Phase 1: Quick Wins (Low Effort, High Impact) 1. ✅ **Convenience methods** (`get(Field)`, `set(Field, value)`) 2. ✅ **Entity.getField(String)** - field lookup by name 3. ✅ **Fluent API** (`withField()`, `withFields()`) ### Phase 2: Standard Java Patterns (Medium Effort, High Value) 4. ✅ **Equals and hashCode** - value comparison 5. ✅ **Clone support** - object copying 6. ✅ **Map conversion** - `toMap()`, `fromMap()` ### Phase 3: Nice to Have 7. ✅ **Better toString()** - human-readable format (keep JSON as default) ## Implementation Notes - All changes should be **backward compatible** - Maintain existing `Field.get()`/`Field.set()` methods - Add new methods alongside old ones - Follow Java conventions (PEP 8 equivalent: Java Code Conventions) - Use `@Override` where appropriate - Add comprehensive Javadoc ## Example Usage After Improvements ```java // Before (still works): Product product = new Product(); Product.name.set(product, "Widget"); String name = (String)Product.name.get(product, null); // After (new convenience methods): Product product = new Product(); product.set(Product.NAME, "Widget"); // Convenience method String name = (String)product.get(Product.NAME); // Convenience method // Fluent API: Product product = new Product() .withField(Product.NAME, "Widget") .withField(Product.PRICE, 19.99); // Map conversion: Map data = product.toMap(); Product product2 = DBO.fromMap(Product.class, data, terminal); // Equality: if (product1.equals(product2)) { System.out.println("Same product"); } // Cloning: Product copy = product.clone(); ```