This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new a95a12691 fix(java): ignore non-Scala/Lombok-style default helper 
methods (#3733)
a95a12691 is described below

commit a95a12691a58fead31dcf9017317e3026fe53718
Author: Sebastian Mandrean <[email protected]>
AuthorDate: Thu Jun 4 00:22:03 2026 +0200

    fix(java): ignore non-Scala/Lombok-style default helper methods (#3733)
    
    ## Why?
    
    When Scala is present on the runtime classpath, Java compatible
    serializers enable Scala default-value support globally. Plain Java
    classes generated with Lombok `@Builder.Default` can contain private
    helper methods named like `$default$imageRelations()`, which previously
    matched the broad Scala default-method scan and caused warning noise
    during deserialization.
    
    ## What does this PR do?
    
    - Restricts Scala default-value method detection to known Scala
    constructor/apply helper prefixes with numeric parameter suffixes.
    - Ignores non-Scala/Lombok-style `$default$` helper names and
    out-of-range parameter indexes without invoking them.
    - Keeps Scala case-class and regular-class constructor defaults working,
    including nested/default-value regression coverage.
    - Adds Java fixtures and Scala serializer tests covering Lombok-like
    helper methods.
    
    ## Related issues
    
    Closes #3720
    
    ## AI Contribution Checklist
    
    - [ ] Substantial AI assistance was used in this PR: `yes`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    ## Does this PR introduce any user-facing change?
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    N/A. This is a targeted default-method filtering fix and does not change
    serialization format or hot-path serialization logic.
    
    ## Validation
    
    - `mvn -T16 -pl fory-core -am install -DskipTests
    -Dmaven.javadoc.skip=true -Dmaven.source.skip=true`
    - `mvn -pl fory-core
    
-Dtest=org.apache.fory.platform.AndroidSupportStaticCheckTest#testScalaDefaultValuesDoNotUseTrustedLookupOnAndroid
    test`
    - `sbt "+testOnly org.apache.fory.util.ScalaDefaultValueUtilsTest
    org.apache.fory.serializer.scala.ScalaDefaultValueTest"`
    - `sbt +test`
    - `mvn -T16 spotless:check checkstyle:check`
    
    Co-authored-by: Shawn Yang <[email protected]>
---
 .../org/apache/fory/util/DefaultValueUtils.java    | 182 +++++++++++++--------
 .../platform/AndroidSupportStaticCheckTest.java    |  29 ++--
 .../scala/LombokDefaultLikeJavaPojo.java           |  79 +++++++++
 .../fory/util/ConstructorDefaultLikeJavaClass.java |  40 +++++
 .../fory/util/LombokDefaultLikeJavaClass.java      |  61 +++++++
 .../serializer/scala/ScalaDefaultValueTest.scala   |  12 +-
 .../fory/util/ScalaDefaultValueUtilsTest.scala     |  50 ++++++
 7 files changed, 375 insertions(+), 78 deletions(-)

diff --git 
a/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java 
b/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
index e5d43cc9a..77ff2d540 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
@@ -50,6 +50,9 @@ import org.apache.fory.type.Types;
 @Internal
 public class DefaultValueUtils {
   private static final Logger LOG = 
LoggerFactory.getLogger(DefaultValueUtils.class);
+  private static final String SCALA_APPLY_DEFAULT_METHOD_PREFIX = 
"apply$default$";
+  private static final String SCALA_CONSTRUCTOR_DEFAULT_METHOD_PREFIX =
+      "$lessinit$greater$default$";
 
   private static final ClassValueCache<Map<Integer, Object>> 
cachedCtrDefaultValues =
       ClassValueCache.newClassKeySoftCache(32);
@@ -226,8 +229,8 @@ public class DefaultValueUtils {
       }
       Preconditions.checkNotNull(
           primaryConstructor, "Primary constructor not found for class " + 
cls.getName());
-      Map<Integer, Object> defaultValues = getDefaultValuesForClass(cls);
       int paramCount = primaryConstructor.getParameterCount();
+      Map<Integer, Object> defaultValues = getDefaultValuesForClass(cls, 
paramCount);
       for (int i = 0; i < paramCount; i++) {
         String paramName = primaryConstructor.getParameters()[i].getName();
         Object defaultValue = defaultValues.get(i + 1); // +1 because default 
values are 1-indexed
@@ -245,75 +248,42 @@ public class DefaultValueUtils {
      * @param cls the Scala class
      * @return a map from parameter index to method handle
      */
-    private Map<Integer, Object> getDefaultValuesForClass(Class<?> cls) {
+    private Map<Integer, Object> getDefaultValuesForClass(Class<?> cls, int 
paramCount) {
       if (cachedCtrDefaultValues.getIfPresent(cls) != null) {
         return cachedCtrDefaultValues.getIfPresent(cls);
       }
       Map<Integer, Object> defaultValueMethods;
       if (ScalaTypes.isScalaProductType(cls)) {
-        defaultValueMethods = getDefaultValuesForCaseClass(cls);
+        defaultValueMethods = getDefaultValuesForCaseClass(cls, paramCount);
       } else {
-        defaultValueMethods = getDefaultValuesForRegularScalaClass(cls);
+        defaultValueMethods = getDefaultValuesForRegularScalaClass(cls, 
paramCount);
       }
       cachedCtrDefaultValues.put(cls, defaultValueMethods);
       return defaultValueMethods;
     }
 
-    private static Map<Integer, Object> getDefaultValuesForCaseClass(Class<?> 
cls) {
+    private static Map<Integer, Object> getDefaultValuesForCaseClass(Class<?> 
cls, int paramCount) {
       Map<Integer, Object> values = new HashMap<>();
-      String companionClassName = cls.getName() + "$";
-      Class<?> companionClass = null;
-      Object companionInstance = null;
-      try {
-        companionClass = Class.forName(companionClassName, false, 
cls.getClassLoader());
-        companionInstance = companionClass.getField("MODULE$").get(null);
-      } catch (Exception e) {
-        // For nested case classes, try to find the companion object in the 
enclosing class
-        Class<?> enclosingClass = cls.getEnclosingClass();
-        if (enclosingClass != null) {
-          // Look for a companion object field in the enclosing class
-          for (java.lang.reflect.Field field : 
enclosingClass.getDeclaredFields()) {
-            if (field.getType().getName().equals(companionClassName)) {
-              field.setAccessible(true);
-              try {
-                companionInstance = field.get(null);
-              } catch (Exception e1) {
-                LOG.warn(
-                    "Error {} accessing companion object for {}, default 
values support is disabled when deserializing object of type {}",
-                    e1.getMessage(),
-                    cls.getName(),
-                    cls.getName());
-                return values;
-              }
-              if (companionInstance != null) {
-                companionClass = companionInstance.getClass();
-                break;
-              }
-            }
-          }
-        }
-      }
-      if (companionClass == null) {
-        LOG.warn(
-            "Companion class not found for {}, default values support is 
disabled when deserializing object of type {}",
-            cls.getName(),
-            cls.getName());
+      ScalaCompanionObject companionObject = getScalaCompanionObject(cls, 
true);
+      if (companionObject == null) {
         return values;
       }
       MethodHandles.Lookup lookup =
-          AndroidSupport.IS_ANDROID ? null : 
_JDKAccess._trustedLookup(companionClass);
+          AndroidSupport.IS_ANDROID ? null : 
_JDKAccess._trustedLookup(companionObject.cls);
 
-      // Look for methods named `apply$default$1`, `apply$default$2`, etc.
-      Method[] companionMethods = companionClass.getDeclaredMethods();
+      Method[] companionMethods = companionObject.cls.getDeclaredMethods();
       for (Method method : companionMethods) {
-        String methodName = method.getName();
-        if (methodName.contains("$default$")) {
+        int paramIndex =
+            getScalaDefaultParameterIndex(method, 
SCALA_APPLY_DEFAULT_METHOD_PREFIX, paramCount);
+        if (paramIndex < 0) {
+          paramIndex =
+              getScalaDefaultParameterIndex(
+                  method, SCALA_CONSTRUCTOR_DEFAULT_METHOD_PREFIX, paramCount);
+        }
+        if (paramIndex > 0) {
           try {
-            // Extract the parameter index from the method name
-            String indexStr =
-                methodName.substring(methodName.lastIndexOf("$default$") + 
"$default$".length());
-            int paramIndex = Integer.parseInt(indexStr);
-            Object defaultValue = invokeDefaultValueMethod(lookup, method, 
companionInstance);
+            Object defaultValue =
+                invokeDefaultValueMethod(lookup, method, 
companionObject.instance);
             values.put(paramIndex, defaultValue);
           } catch (Throwable e) {
             LOG.warn(
@@ -328,23 +298,25 @@ public class DefaultValueUtils {
       return values;
     }
 
-    private static Map<Integer, Object> 
getDefaultValuesForRegularScalaClass(Class<?> cls) {
+    private static Map<Integer, Object> getDefaultValuesForRegularScalaClass(
+        Class<?> cls, int paramCount) {
       Map<Integer, Object> values = new HashMap<>();
+      ScalaCompanionObject companionObject = getScalaCompanionObject(cls, 
false);
+      if (companionObject == null) {
+        return values;
+      }
       try {
         MethodHandles.Lookup lookup =
-            AndroidSupport.IS_ANDROID ? null : _JDKAccess._trustedLookup(cls);
-        Method[] classMethods = cls.getDeclaredMethods();
-        for (Method method : classMethods) {
-          String methodName = method.getName();
-          if (methodName.contains("$default$")) {
+            AndroidSupport.IS_ANDROID ? null : 
_JDKAccess._trustedLookup(companionObject.cls);
+        Method[] companionMethods = companionObject.cls.getDeclaredMethods();
+        for (Method method : companionMethods) {
+          int paramIndex =
+              getScalaDefaultParameterIndex(
+                  method, SCALA_CONSTRUCTOR_DEFAULT_METHOD_PREFIX, paramCount);
+          if (paramIndex > 0) {
             try {
-              // Extract the parameter index from the method name
-              String indexStr =
-                  methodName.substring(methodName.lastIndexOf("$default$") + 
"$default$".length());
-              int paramIndex = Integer.parseInt(indexStr);
-              // For regular Scala classes, we need to create an instance to 
call instance methods
-              // Since these are default value methods, we can try to call 
them as static methods
-              Object defaultValue = invokeDefaultValueMethod(lookup, method, 
null);
+              Object defaultValue =
+                  invokeDefaultValueMethod(lookup, method, 
companionObject.instance);
               values.put(paramIndex, defaultValue);
             } catch (Throwable e) {
               LOG.warn(
@@ -367,6 +339,48 @@ public class DefaultValueUtils {
       return values;
     }
 
+    private static ScalaCompanionObject getScalaCompanionObject(
+        Class<?> cls, boolean warnIfMissing) {
+      String companionClassName = cls.getName() + "$";
+      try {
+        Class<?> companionClass = Class.forName(companionClassName, false, 
cls.getClassLoader());
+        Object companionInstance = 
companionClass.getField("MODULE$").get(null);
+        if (companionInstance != null) {
+          return new ScalaCompanionObject(companionClass, companionInstance);
+        }
+      } catch (Exception e) {
+        // For nested case classes, try to find the companion object in the 
enclosing class.
+        Class<?> enclosingClass = cls.getEnclosingClass();
+        if (enclosingClass != null) {
+          for (java.lang.reflect.Field field : 
enclosingClass.getDeclaredFields()) {
+            if (field.getType().getName().equals(companionClassName)) {
+              field.setAccessible(true);
+              try {
+                Object companionInstance = field.get(null);
+                if (companionInstance != null) {
+                  return new 
ScalaCompanionObject(companionInstance.getClass(), companionInstance);
+                }
+              } catch (Exception e1) {
+                LOG.warn(
+                    "Error {} accessing companion object for {}, default 
values support is disabled when deserializing object of type {}",
+                    e1.getMessage(),
+                    cls.getName(),
+                    cls.getName());
+                return null;
+              }
+            }
+          }
+        }
+      }
+      if (warnIfMissing) {
+        LOG.warn(
+            "Companion class not found for {}, default values support is 
disabled when deserializing object of type {}",
+            cls.getName(),
+            cls.getName());
+      }
+      return null;
+    }
+
     private static Object invokeDefaultValueMethod(
         MethodHandles.Lookup lookup, Method method, Object target) throws 
Throwable {
       if (AndroidSupport.IS_ANDROID) {
@@ -383,6 +397,44 @@ public class DefaultValueUtils {
       }
       return methodHandle.invoke(target);
     }
+
+    private static int getScalaDefaultParameterIndex(
+        Method method, String methodPrefix, int maxParamIndex) {
+      if (method.getParameterCount() != 0) {
+        return -1;
+      }
+      String methodName = method.getName();
+      if (!methodName.startsWith(methodPrefix)) {
+        return -1;
+      }
+      int indexStart = methodPrefix.length();
+      if (indexStart == methodName.length()) {
+        return -1;
+      }
+      int paramIndex = 0;
+      for (int i = indexStart; i < methodName.length(); i++) {
+        char c = methodName.charAt(i);
+        if (c < '0' || c > '9') {
+          return -1;
+        }
+        int digit = c - '0';
+        if (paramIndex > (Integer.MAX_VALUE - digit) / 10) {
+          return -1;
+        }
+        paramIndex = paramIndex * 10 + digit;
+      }
+      return paramIndex > 0 && paramIndex <= maxParamIndex ? paramIndex : -1;
+    }
+
+    private static final class ScalaCompanionObject {
+      private final Class<?> cls;
+      private final Object instance;
+
+      private ScalaCompanionObject(Class<?> cls, Object instance) {
+        this.cls = cls;
+        this.instance = instance;
+      }
+    }
   }
 
   /** Convert value to correct type based on dispatchId. */
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/platform/AndroidSupportStaticCheckTest.java
 
b/java/fory-core/src/test/java/org/apache/fory/platform/AndroidSupportStaticCheckTest.java
index 47066b0da..8d1797850 100644
--- 
a/java/fory-core/src/test/java/org/apache/fory/platform/AndroidSupportStaticCheckTest.java
+++ 
b/java/fory-core/src/test/java/org/apache/fory/platform/AndroidSupportStaticCheckTest.java
@@ -28,6 +28,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import org.testng.annotations.Test;
@@ -42,15 +43,12 @@ public class AndroidSupportStaticCheckTest {
   private static final Pattern ANDROID_GATED_FIELD_GET_ANNOTATED_TYPE =
       Pattern.compile(
           
"AndroidSupport\\.IS_ANDROID[\\s\\S]{0,160}\\.getAnnotatedType\\s*\\(", 
Pattern.DOTALL);
-  private static final Pattern ANDROID_GATED_SCALA_COMPANION_LOOKUP =
+  private static final Pattern SCALA_TRUSTED_LOOKUP =
+      Pattern.compile("_JDKAccess\\._trustedLookup\\(");
+  private static final Pattern ANDROID_GATED_SCALA_TRUSTED_LOOKUP =
       Pattern.compile(
           "AndroidSupport\\.IS_ANDROID\\s*\\?\\s*null\\s*:\\s*"
-              + "_JDKAccess\\._trustedLookup\\(companionClass\\)",
-          Pattern.DOTALL);
-  private static final Pattern ANDROID_GATED_SCALA_CLASS_LOOKUP =
-      Pattern.compile(
-          "AndroidSupport\\.IS_ANDROID\\s*\\?\\s*null\\s*:\\s*"
-              + "_JDKAccess\\._trustedLookup\\(cls\\)",
+              + "_JDKAccess\\._trustedLookup\\([^)]*\\)",
           Pattern.DOTALL);
   private static final Pattern ANDROID_REFLECTIVE_SCALA_DEFAULT_INVOKE =
       Pattern.compile(
@@ -155,12 +153,19 @@ public class AndroidSupportStaticCheckTest {
   public void testScalaDefaultValuesDoNotUseTrustedLookupOnAndroid() throws 
IOException {
     Path sourcePath = 
Paths.get("src/main/java/org/apache/fory/util/DefaultValueUtils.java");
     String source = new String(Files.readAllBytes(sourcePath), 
StandardCharsets.UTF_8);
+    List<String> violations = new ArrayList<>();
+    Matcher matcher = SCALA_TRUSTED_LOOKUP.matcher(source);
+    while (matcher.find()) {
+      int start = Math.max(0, matcher.start() - 120);
+      int end = Math.min(source.length(), matcher.end() + 160);
+      String context = source.substring(start, end);
+      if (!ANDROID_GATED_SCALA_TRUSTED_LOOKUP.matcher(context).find()) {
+        violations.add(context);
+      }
+    }
     assertTrue(
-        ANDROID_GATED_SCALA_COMPANION_LOOKUP.matcher(source).find(),
-        "Scala companion default values must not use _JDKAccess trusted lookup 
on Android");
-    assertTrue(
-        ANDROID_GATED_SCALA_CLASS_LOOKUP.matcher(source).find(),
-        "Regular Scala default values must not use _JDKAccess trusted lookup 
on Android");
+        violations.isEmpty(),
+        "Scala default values must not use _JDKAccess trusted lookup on 
Android: " + violations);
     assertTrue(
         ANDROID_REFLECTIVE_SCALA_DEFAULT_INVOKE.matcher(source).find(),
         "Android Scala default values must invoke default methods through 
direct reflection");
diff --git 
a/scala/src/test/java/org/apache/fory/serializer/scala/LombokDefaultLikeJavaPojo.java
 
b/scala/src/test/java/org/apache/fory/serializer/scala/LombokDefaultLikeJavaPojo.java
new file mode 100644
index 000000000..53038b125
--- /dev/null
+++ 
b/scala/src/test/java/org/apache/fory/serializer/scala/LombokDefaultLikeJavaPojo.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ *   http://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.
+ */
+
+package org.apache.fory.serializer.scala;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class LombokDefaultLikeJavaPojo {
+  private String id;
+  private List<String> imageRelations;
+
+  public LombokDefaultLikeJavaPojo() {
+    this(null, Collections.emptyList());
+  }
+
+  public LombokDefaultLikeJavaPojo(String id, List<String> imageRelations) {
+    this.id = id;
+    this.imageRelations = imageRelations;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public List<String> getImageRelations() {
+    return imageRelations;
+  }
+
+  private static List<String> $default$imageRelations() {
+    return Collections.emptyList();
+  }
+
+  private static List<String> $default$1() {
+    return Collections.emptyList();
+  }
+
+  private List<String> $default$2() {
+    return Collections.emptyList();
+  }
+
+  private static List<String> $default$99() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof LombokDefaultLikeJavaPojo)) {
+      return false;
+    }
+    LombokDefaultLikeJavaPojo that = (LombokDefaultLikeJavaPojo) o;
+    return Objects.equals(id, that.id) && Objects.equals(imageRelations, 
that.imageRelations);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(id, imageRelations);
+  }
+}
diff --git 
a/scala/src/test/java/org/apache/fory/util/ConstructorDefaultLikeJavaClass.java 
b/scala/src/test/java/org/apache/fory/util/ConstructorDefaultLikeJavaClass.java
new file mode 100644
index 000000000..142362da2
--- /dev/null
+++ 
b/scala/src/test/java/org/apache/fory/util/ConstructorDefaultLikeJavaClass.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   http://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.
+ */
+
+package org.apache.fory.util;
+
+public class ConstructorDefaultLikeJavaClass {
+  private final String id;
+
+  public ConstructorDefaultLikeJavaClass(String id) {
+    this.id = id;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  private static String $lessinit$greater$default$1() {
+    return "default-id";
+  }
+
+  private static String $lessinit$greater$default$2() {
+    return "out-of-range";
+  }
+}
diff --git 
a/scala/src/test/java/org/apache/fory/util/LombokDefaultLikeJavaClass.java 
b/scala/src/test/java/org/apache/fory/util/LombokDefaultLikeJavaClass.java
new file mode 100644
index 000000000..177f269a3
--- /dev/null
+++ b/scala/src/test/java/org/apache/fory/util/LombokDefaultLikeJavaClass.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ *   http://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.
+ */
+
+package org.apache.fory.util;
+
+import java.util.Collections;
+import java.util.List;
+
+public class LombokDefaultLikeJavaClass {
+  private String id;
+  private List<String> imageRelations;
+
+  public LombokDefaultLikeJavaClass() {
+    this(null, Collections.emptyList());
+  }
+
+  public LombokDefaultLikeJavaClass(String id, List<String> imageRelations) {
+    this.id = id;
+    this.imageRelations = imageRelations;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public List<String> getImageRelations() {
+    return imageRelations;
+  }
+
+  private static List<String> $default$imageRelations() {
+    return Collections.emptyList();
+  }
+
+  private static List<String> $default$1() {
+    return Collections.emptyList();
+  }
+
+  private List<String> $default$2() {
+    return Collections.emptyList();
+  }
+
+  private static List<String> $default$99() {
+    return Collections.emptyList();
+  }
+}
diff --git 
a/scala/src/test/scala/org/apache/fory/serializer/scala/ScalaDefaultValueTest.scala
 
b/scala/src/test/scala/org/apache/fory/serializer/scala/ScalaDefaultValueTest.scala
index 95a3c82e0..5236309f0 100644
--- 
a/scala/src/test/scala/org/apache/fory/serializer/scala/ScalaDefaultValueTest.scala
+++ 
b/scala/src/test/scala/org/apache/fory/serializer/scala/ScalaDefaultValueTest.scala
@@ -23,6 +23,7 @@ import org.apache.fory.Fory
 import org.apache.fory.scala.ForyScala
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
+import java.util.Arrays
 
 object NestedCases {
   // Classes WITHOUT default values for serialization
@@ -182,6 +183,15 @@ class ScalaDefaultValueTest extends AnyWordSpec with 
Matchers {
         deserialized.age shouldEqual 25 // Should use default value
         deserialized.city shouldEqual "Unknown" // Should use default value
       }
+
+      s"ignore Java Lombok-style default helper methods in $modeName" in {
+        val fory = createFory(codegen)
+        val original = new LombokDefaultLikeJavaPojo("1", 
Arrays.asList("thumbnail", "hero"))
+        val deserialized =
+          fory.deserialize(fory.serialize(original), 
classOf[LombokDefaultLikeJavaPojo])
+
+        deserialized shouldEqual original
+      }
     }
   }
 }
@@ -194,4 +204,4 @@ case class CaseClassComplexDefaultsNoDefaults(v: String) // 
No list field at all
 // Test case classes WITH default values (for deserialization)
 case class CaseClassWithDefaults(v: String, x: Int = 1)
 case class CaseClassMultipleDefaultsWithDefaults(v: String, x: Int = 1, y: 
Double = 2.0)
-case class CaseClassComplexDefaultsWithDefaults(v: String, list: List[Int] = 
List(1, 2, 3)) 
\ No newline at end of file
+case class CaseClassComplexDefaultsWithDefaults(v: String, list: List[Int] = 
List(1, 2, 3))
diff --git 
a/scala/src/test/scala/org/apache/fory/util/ScalaDefaultValueUtilsTest.scala 
b/scala/src/test/scala/org/apache/fory/util/ScalaDefaultValueUtilsTest.scala
index ea4cc541f..aae7dab8f 100644
--- a/scala/src/test/scala/org/apache/fory/util/ScalaDefaultValueUtilsTest.scala
+++ b/scala/src/test/scala/org/apache/fory/util/ScalaDefaultValueUtilsTest.scala
@@ -26,7 +26,10 @@ import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
 import org.scalatest.Succeeded
 import org.scalatest.matchers.should.Matchers._
+import java.io.{ByteArrayOutputStream, PrintStream}
+import java.nio.charset.StandardCharsets
 import java.util.{List => JavaList, ArrayList}
+import org.apache.fory.logging.{LogLevel, LoggerFactory}
 import scala.jdk.CollectionConverters._
 
 // Test case classes WITH default values for testing DefaultValueUtils
@@ -122,6 +125,18 @@ class TestJavaClass(val name: String, val age: Int) {
   override def toString: String = s"TestJavaClass($name, $age)"
 }
 
+class TestRegularScalaClassMethodDefaultsOnly(val name: String) {
+  def helper(limit: Int = 10): Int = limit
+}
+
+class TestRegularScalaClassOutOfRangeCompanionDefault(val name: String)
+
+object TestRegularScalaClassOutOfRangeCompanionDefault {
+  def `$lessinit$greater$default$2`(): String = {
+    throw new AssertionError("out-of-range constructor default helper should 
not be invoked")
+  }
+}
+
 // Object to contain truly nested case classes
 object NestedClasses {
   case class NestedCaseClass(
@@ -170,8 +185,43 @@ class ScalaDefaultValueUtilsTest extends AnyWordSpec with 
Matchers {
     scalaDefaultValueSupport().buildDefaultValueFields(fory, cls, descriptors)
   }
 
+  def assertNoScalaDefaultValuesOrWarnings(classes: Class[_]*): Unit = {
+    val output = new ByteArrayOutputStream()
+    val previousOut = System.out
+    val previousLogLevel = LoggerFactory.getLogLevel
+    val capture = new PrintStream(output, true, StandardCharsets.UTF_8.name())
+    try {
+      System.setOut(capture)
+      LoggerFactory.setLogLevel(LogLevel.WARN_LEVEL)
+
+      classes.foreach { cls =>
+        withClue(s"${cls.getName}: ") {
+          scalaDefaultValueSupport().hasDefaultValues(cls) shouldEqual false
+          scalaDefaultValueSupport().getAllDefaultValues(cls).asScala shouldBe 
empty
+          buildDefaultValueFields(cls) shouldBe empty
+        }
+      }
+    } finally {
+      LoggerFactory.setLogLevel(previousLogLevel)
+      System.setOut(previousOut)
+      capture.close()
+    }
+
+    val logs = new String(output.toByteArray, StandardCharsets.UTF_8)
+    logs should not include "WARN  DefaultValueUtils"
+    logs should not include "finding default value"
+  }
+
   "DefaultValueUtils" should {
 
+    "ignore non-constructor default-like methods without warnings" in {
+      assertNoScalaDefaultValuesOrWarnings(
+        classOf[LombokDefaultLikeJavaClass],
+        classOf[ConstructorDefaultLikeJavaClass],
+        classOf[TestRegularScalaClassMethodDefaultsOnly],
+        classOf[TestRegularScalaClassOutOfRangeCompanionDefault])
+    }
+
     "detect Scala classes with default values correctly" in {
       // Test case classes with default values
       
scalaDefaultValueSupport().hasDefaultValues(classOf[TestCaseClassWithDefaults]) 
shouldEqual true


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to