diff --git a/java/com/facebook/yoga/YogaNode.java b/java/com/facebook/yoga/YogaNode.java index 093d209d..1a280dd8 100644 --- a/java/com/facebook/yoga/YogaNode.java +++ b/java/com/facebook/yoga/YogaNode.java @@ -16,7 +16,7 @@ import java.util.List; import javax.annotation.Nullable; @DoNotStrip -public class YogaNode { +public class YogaNode implements Cloneable { static { SoLoader.loadLibrary("yoga"); @@ -31,7 +31,7 @@ public class YogaNode { private List mChildren; private YogaMeasureFunction mMeasureFunction; private YogaBaselineFunction mBaselineFunction; - private final long mNativePointer; + private long mNativePointer; private Object mData; /* Those flags needs be in sync with YGJNI.cpp */ @@ -160,6 +160,18 @@ public class YogaNode { jni_YGNodeInsertChild(mNativePointer, child.mNativePointer, i); } + private native long jni_YGNodeClone(long nativePointer, Object newNode); + + @Override + public YogaNode clone() throws CloneNotSupportedException { + YogaNode clonedYogaNode = (YogaNode) super.clone(); + long clonedNativePointer = jni_YGNodeClone(mNativePointer, clonedYogaNode); + clonedYogaNode.mNativePointer = clonedNativePointer; + clonedYogaNode.mChildren = + mChildren != null ? (List) ((ArrayList) mChildren).clone() : null; + return clonedYogaNode; + } + private native void jni_YGNodeRemoveChild(long nativePointer, long childPointer); public YogaNode removeChildAt(int i) { diff --git a/java/jni/YGJNI.cpp b/java/jni/YGJNI.cpp index 67042729..b8cfe0a6 100644 --- a/java/jni/YGJNI.cpp +++ b/java/jni/YGJNI.cpp @@ -250,6 +250,16 @@ jlong jni_YGNodeNewWithConfig(alias_ref thiz, jlong configPointer) { return reinterpret_cast(node); } +jlong jni_YGNodeClone( + alias_ref thiz, + jlong nativePointer, + alias_ref clonedJavaObject) { + const YGNodeRef clonedYogaNode = YGNodeClone(_jlong2YGNodeRef(nativePointer)); + clonedYogaNode->setContext( + new weak_ref(make_weak(clonedJavaObject))); + return reinterpret_cast(clonedYogaNode); +} + void jni_YGNodeFree(alias_ref thiz, jlong nativePointer) { const YGNodeRef node = _jlong2YGNodeRef(nativePointer); delete YGNodeJobject(node); @@ -612,6 +622,7 @@ jint JNI_OnLoad(JavaVM *vm, void *) { YGMakeNativeMethod(jni_YGNodeStyleSetAspectRatio), YGMakeNativeMethod(jni_YGNodeGetInstanceCount), YGMakeNativeMethod(jni_YGNodePrint), + YGMakeNativeMethod(jni_YGNodeClone), }); registerNatives("com/facebook/yoga/YogaConfig", { diff --git a/java/tests/com/facebook/yoga/YogaNodeTest.java b/java/tests/com/facebook/yoga/YogaNodeTest.java index c655f081..4f7388d5 100644 --- a/java/tests/com/facebook/yoga/YogaNodeTest.java +++ b/java/tests/com/facebook/yoga/YogaNodeTest.java @@ -10,9 +10,12 @@ package com.facebook.yoga; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; public class YogaNodeTest { @@ -221,7 +224,61 @@ public class YogaNodeTest { } @Test - public void testNodeClonedLeak() throws Exception { + public void testCloneNode() throws Exception { + YogaConfig config = new YogaConfig(); + YogaNode root = new YogaNode(config); + YogaNode child = new YogaNode(config); + YogaNode grandChild = new YogaNode(config); + root.addChildAt(child, 0); + child.addChildAt(grandChild, 0); + child.setFlexDirection(YogaFlexDirection.ROW); + + YogaNode clonedChild = child.clone(); + + assertNotSame(clonedChild, child); + + assertEquals(YogaFlexDirection.ROW, child.getFlexDirection()); + assertEquals(child.getFlexDirection(), clonedChild.getFlexDirection()); + + // Verify the cloning is shallow on the List of children + assertEquals(1, child.getChildCount()); + assertEquals(child.getChildCount(), clonedChild.getChildCount()); + assertEquals(child.getChildAt(0), clonedChild.getChildAt(0)); + + child.removeChildAt(0); + assertEquals(0, child.getChildCount()); + assertEquals(1, clonedChild.getChildCount()); + } + + @Test + public void testCloneNodeListener() throws Exception { + final AtomicBoolean onNodeClonedExecuted = new AtomicBoolean(false); + YogaConfig config = new YogaConfig(); + config.setOnNodeCloned( + new YogaNodeClonedFunction() { + @Override + public void onNodeCloned( + YogaNode oldNode, YogaNode newNode, YogaNode parent, int childIndex) { + onNodeClonedExecuted.set(true); + } + }); + YogaNode root = new YogaNode(config); + root.setWidth(100f); + root.setHeight(100f); + YogaNode child0 = new YogaNode(config); + root.addChildAt(child0, 0); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + // Force a clone to happen. + final YogaNode root2 = root.clone(); + root2.setWidth(200f); + root2.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertTrue(onNodeClonedExecuted.get()); + } + + @Test + public void testOnNodeClonedLeak() throws Exception { YogaConfig config = new YogaConfig(); config.setOnNodeCloned( new YogaNodeClonedFunction() { @@ -232,7 +289,7 @@ public class YogaNodeTest { } }); config.setOnNodeCloned(null); - java.lang.ref.WeakReference ref = new java.lang.ref.WeakReference(config); + WeakReference ref = new WeakReference(config); // noinspection UnusedAssignment config = null; // try and free for the next 5 seconds, usually it works after the