13 KiB
Haxe Serializer: Java Getter to Haxe Field/Method Mapping Analysis
Overview
This document contains a comprehensive analysis of ALL patterns for transforming Java getter calls into appropriate Haxe field/method access, extracted from the java-haxe-diff.md file.
General Patterns
1. Simple Field Access Pattern (Most Common)
Java getter getX() maps to Haxe field x:Type
Examples:
getName()→name:StringgetDuration()→duration:FloatgetTimeScale()→timeScale:FloatgetLoop()→loop:BoolgetX()→x:FloatgetY()→y:FloatgetRotation()→rotation:FloatgetScaleX()→scaleX:FloatgetScaleY()→scaleY:FloatgetShearX()→shearX:FloatgetShearY()→shearY:FloatgetWidth()→width:FloatgetHeight()→height:FloatgetColor()→color:ColorgetAlpha()→alpha:FloatgetMix()→mix:FloatgetVisible()→visible:BoolgetIndex()→index:Int
2. Array Field Access Pattern
Java getter getX() returns array → Haxe field x:Array<Type>
Examples:
getTimelines()→timelines:Array<Timeline>getBones()→bones:Array<Int>orbones:Array<BoneData>orbones:Array<BonePose>(context-dependent)getChildren()→children:Array<Bone>getFrames()→frames:Array<Float>getAttachmentNames()→attachmentNames:Array<String>getVertices()→vertices:Array<Float>orvertices:Array<Array<Float>>(context-dependent)getEvents()→events:Array<Event>getDrawOrders()→drawOrders:Array<Array<Int>>getSlots()→slots:Array<Slot>orslots:Array<SlotData>(context-dependent)getTracks()→tracks:Array<TrackEntry>getTriangles()→triangles:Array<Int>getUVs()→uvs:Array<Float>getRegionUVs()→regionUVs:Array<Float>getEdges()→edges:Array<Int>getLengths()→lengths:Array<Float>getRegions()→regions:Array<TextureRegion>getAnimations()→animations:Array<Animation>getSkins()→skins:Array<Skin>getConstraints()→constraints:Array<Constraint<Dynamic, Dynamic, Dynamic>>orconstraints:Array<ConstraintData<Dynamic, Dynamic>>getPhysicsConstraints()→physics:Array<PhysicsConstraint>getProperties()→properties:Array<FromProperty>getDeform()→deform:Array<Float>
3. Method Remains Method Pattern
Some getters remain as methods in Haxe, typically those that perform calculations or have side effects.
Examples:
getFrameCount()→getFrameCount():IntgetFrameEntries()→getFrameEntries():IntgetDuration()→getDuration():Float(in Timeline classes)getSlotIndex()→getSlotIndex():IntgetBoneIndex()→getBoneIndex():IntgetConstraintIndex()→getConstraintIndex():IntgetData()→getData():T(where T is the data type)getPose()→getPose():T(where T is the pose type)getAppliedPose()→getAppliedPose():TgetSetupPose()→getSetupPose():TgetAnimationTime()→getAnimationTime():FloatgetTrackComplete()→getTrackComplete():FloatgetWorldRotationX()→getWorldRotationX():FloatgetWorldRotationY()→getWorldRotationY():FloatgetWorldScaleX()→getWorldScaleX():FloatgetWorldScaleY()→getWorldScaleY():FloatgetAttachments()→getAttachments():Array<SkinEntry>getOffsetRotation()→getOffsetRotation():FloatgetOffsetX()→getOffsetX():FloatgetOffsetY()→getOffsetY():FloatgetOffsetScaleX()→getOffsetScaleX():FloatgetOffsetScaleY()→getOffsetScaleY():FloatgetOffsetShearY()→getOffsetShearY():Float
4. Special Property Access Pattern
Some properties use Haxe's property syntax with get/set accessors.
Examples:
getName()→name(get, never):String(in Attachment subclasses)getRootBone()→rootBone(get, never):BonegetScaleY()→scaleY(get, default):Float(in Skeleton)
5. Field Name Variations
Some getters have slight variations in their Haxe field names.
Examples:
getInt()→intValue:IntgetFloat()→floatValue:FloatgetString()→stringValue:StringgetUpdateCache()→_updateCache:Array<Dynamic>(with underscore prefix)getPropertyIds()→propertyIds:Array<String>getDefaultSkin()→defaultSkin:Skin
6. Type Reference Patterns
Getters that reference other objects.
Examples:
getParent()→parent:Boneorparent:BoneData(context-dependent)getTarget()→target:Boneortarget:BoneData(context-dependent)getSource()→source:Boneorsource:BoneData(context-dependent)getAttachment()→attachment:Attachmentorattachment:VertexAttachment(context-dependent)getSlot()→slot:Slotorslot:SlotData(context-dependent)getBone()→bone:Boneorbone:BoneDataorbone:BonePose(context-dependent)getSkin()→skin:SkingetAnimation()→animation:AnimationgetRegion()→region:TextureRegiongetSequence()→sequence:SequencegetParentMesh()→parentMesh:MeshAttachmentgetEndSlot()→endSlot:SlotData
Context-Dependent Mappings
1. getBones() mapping depends on containing class:
- In
Animation: →bones:Array<Int> - In
BoneData,IkConstraintData,PathConstraintData,TransformConstraintData,Skin: →bones:Array<BoneData> - In
IkConstraint,PathConstraint,TransformConstraint: →bones:Array<BonePose> - In
BoundingBoxAttachment,ClippingAttachment,MeshAttachment,PathAttachment,VertexAttachment: →bones:Array<Int>
2. getVertices() mapping depends on containing class:
- In
DeformTimeline: →vertices:Array<Array<Float>>(2D array) - In
ClippingAttachment,MeshAttachment,PathAttachment,VertexAttachment: →vertices:Array<Float>(1D array)
3. getDuration() mapping depends on containing class:
- In
Animation: →duration:Float(field) - In Timeline classes: →
getDuration():Float(method)
4. Special Cases in Timeline Classes:
All Timeline subclasses have these getters as methods:
getFrameCount()→getFrameCount():IntgetFrameEntries()→getFrameEntries():IntgetDuration()→getDuration():FloatgetPropertyIds()→propertyIds:Array<String>(field)getFrames()→frames:Array<Float>(field)
5. Special Cases in Constraint Classes:
getData()→getData():T(method returning specific data type)getPose()→getPose():T(method returning specific pose type)getAppliedPose()→getAppliedPose():T(method returning specific pose type)
Type Transformations
Java to Haxe Type Mappings:
int→Intfloat→Floatdouble→Floatboolean→BoolString→StringArray/List→Array<T>IntArray→Array<Int>(custom type)- Object types remain the same (e.g.,
Color→Color)
Special Edge Cases
1. Incomplete Mappings (marked as TODO in the file):
BonePose.getInherit()→ TODOBoundingBoxAttachment.getVertices()→ TODOBoundingBoxAttachment.getWorldVerticesLength()→ TODOBoundingBoxAttachment.getTimelineAttachment()→ TODOBoundingBoxAttachment.getId()→ TODOBoundingBoxAttachment.getName()→ TODO
2. Inherited Methods:
Some getters are inherited from parent classes and noted as such:
getName()in attachment classes inherits fromAttachment- Properties in
FromRotate,FromScaleX, etc. inherit fromFromProperty - Properties in constraint data classes inherit from
PosedData
3. Special Skeleton Fields:
getUpdateCache()→_updateCache:Array<Dynamic>(private with underscore)getRootBone()→rootBone(get, never):Bone(computed property)getScaleY()→scaleY(get, default):Float(property with default)
4. DrawOrderTimeline Exception:
getFrameCount()→frameCount:Int(field instead of method, unlike other timelines)
5. Enum and Constant Mappings:
getBlendMode()→blendMode:BlendModegetPositionMode()→positionMode:PositionModegetSpacingMode()→spacingMode:SpacingModegetRotateMode()→rotateMode:RotateModegetMixBlend()→mixBlend:MixBlendgetInherit()→inherit:Inherit
Summary
The transformation rules can be categorized as:
- Default Rule:
getX()→x:Type(lowercase first letter, remove get prefix) - Method Preservation: Keep as method for calculated values or methods with side effects
- Special Properties: Use Haxe property syntax for computed/readonly properties
- Context Awareness: Same getter can map differently based on containing class
- Type Transformation: Java primitive types map to Haxe equivalents
- Special Cases: Some fields have custom names (e.g.,
getInt()→intValue)
When implementing the Haxe serializer generator, these patterns should be applied in order of specificity:
- Check for exact class + getter combinations first
- Check for class-specific patterns (e.g., all Timeline getters)
- Apply general transformation rules
- Handle special cases and exceptions
Revised Implementation Plan: Reflection-Based Haxe Serializer
Key Insight
Instead of maintaining complex mapping tables, we can leverage Haxe's dynamic reflection capabilities to automatically resolve field vs method access at runtime.
Simplified Architecture
Core Approach: Runtime Property Resolution
private function getPropertyValue(obj:Dynamic, javaGetter:String):Dynamic {
// Extract property name from Java getter
var propName = extractPropertyName(javaGetter); // getName() → "name"
// 1. Try direct field access first (most common case)
if (Reflect.hasField(obj, propName)) {
return Reflect.field(obj, propName);
}
// 2. Try special field variations
var specialNames = getSpecialFieldNames(javaGetter, propName);
for (name in specialNames) {
if (Reflect.hasField(obj, name)) {
return Reflect.field(obj, name);
}
}
// 3. Try method access (for computed properties)
if (Reflect.hasField(obj, javaGetter.replace("()", ""))) {
return Reflect.callMethod(obj, Reflect.field(obj, javaGetter.replace("()", "")), []);
}
// 4. Handle property syntax (get, never)
// This would need special handling or we just access the underlying getter
throw 'Property ${javaGetter} not found on object';
}
Special Name Mappings
Based on the pattern analysis, we only need to handle these special cases:
private function getSpecialFieldNames(javaGetter:String, defaultName:String):Array<String> {
return switch(javaGetter) {
case "getInt()": ["intValue"];
case "getFloat()": ["floatValue"];
case "getString()": ["stringValue"];
case "getPhysicsConstraints()": ["physics"];
case "getUpdateCache()": ["_updateCache"];
case "getSetupPose()": ["setup"];
case "getAppliedPose()": ["applied"];
default: [];
}
}
Implementation Steps
Phase 1: Core Reflection System
- Implement
getPropertyValuewith fallback chain - Handle special field name mappings
- Test with known edge cases
Phase 2: Type Handling
- Keep existing Java → Haxe type transformations
- Use
Dynamicfor runtime resolution - Cast results when needed for type safety
Phase 3: Code Generation
- Generate simpler code using reflection helpers
- No need for complex getter-to-field mappings
- Handle enums with runtime type checking
Advantages
- Simplicity: No need to parse mapping files or maintain lookup tables
- Robustness: Automatically handles API changes
- Correctness: Runtime resolution ensures we get the right value
- Maintainability: Minimal special cases to maintain
Trade-offs
- Performance: Reflection is slower than direct access (acceptable for serialization)
- Type Safety: Less compile-time checking (mitigated by runtime tests)
- Debugging: Harder to trace field access (can add logging)
Example Generated Code
private function writeAnimation(obj:Animation):Void {
// ... cycle detection ...
json.writeObjectStart();
json.writeName("type");
json.writeValue("Animation");
// Use reflection for all properties
json.writeName("timelines");
writeArray(getPropertyValue(obj, "getTimelines()"), writeTimeline);
json.writeName("duration");
json.writeValue(getPropertyValue(obj, "getDuration()"));
json.writeName("bones");
writeIntArray(getPropertyValue(obj, "getBones()"));
json.writeName("name");
json.writeValue(getPropertyValue(obj, "getName()"));
json.writeObjectEnd();
}
Summary
This reflection-based approach eliminates the complexity of maintaining mapping tables while preserving correctness. The patterns we analyzed show that most getters follow predictable conventions, with only a handful of special cases that can be handled with a simple switch statement.