Repository: javaparser/javaparser (2025-07-06T19:07:08Z)

Issue:
Strings and Boxed types should be compared using "equals()"
These methods seemed to assume that in order to detect an unchanged argument (and to skip property change notification), identity comparison is appropriate. But this [would only work in rare circumstances](https://stackoverflow.com/a/32926523/7641). See also [SonarQube rule 4973](https://rules.sonarsource.com/java/type/Bug/RSPEC-4973).

Created from the PR https://github.com/javaparser/javaparser/pull/4790


Discussion:
I created the issue https://github.com/javaparser/javaparser/issues/4791 
I started working on this.
Created PR #4792.

Note: it turned out that other than AST classes¹ and `java.lang.String`, the only type appearing in setters that use the `value == this.value` pattern is `boolean` (primitive, not boxed). So the fix in the PR is in fact complete even though it does not do anything about boxed types. ("Boxed types" only appears in the title of this issue because it mirrors [SonarQube rule 4973](https://rules.sonarsource.com/java/type/Bug/RSPEC-4973).)

¹ I guess that for AST classes, usage of identity comparison is in fact correct.



Patch:
diff --git a/javaparser-core-generators/src/main/java/com/github/javaparser/generator/core/node/PropertyGenerator.java b/javaparser-core-generators/src/main/java/com/github/javaparser/generator/core/node/PropertyGenerator.java
index a2d1d9a4cd..6bfc62ea5d 100644
--- a/javaparser-core-generators/src/main/java/com/github/javaparser/generator/core/node/PropertyGenerator.java
+++ b/javaparser-core-generators/src/main/java/com/github/javaparser/generator/core/node/PropertyGenerator.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -109,7 +109,11 @@ private void generateSetter(
 
         // Check if the new value is the same as the old value
         String returnValue = CodeUtils.castValue("this", setter.getType(), nodeMetaModel.getTypeName());
-        body.addStatement(f("if (%s == this.%s) { return %s; }", name, name, returnValue));
+        if (property.getType().equals(String.class)) {
+            body.addStatement(f("if (%s.equals(this.%s)) { return %s; }", name, name, returnValue));
+        } else {
+            body.addStatement(f("if (%s == this.%s) { return %s; }", name, name, returnValue));
+        }
 
         body.addStatement(f("notifyPropertyChange(ObservableProperty.%s, this.%s, %s);", observableName, name, name));
         if (property.isNode()) {
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/comments/Comment.java b/javaparser-core/src/main/java/com/github/javaparser/ast/comments/Comment.java
index d9b7fc0d86..e5bf72be5b 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/comments/Comment.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/comments/Comment.java
@@ -83,7 +83,7 @@ public String getContent() {
     @Generated("com.github.javaparser.generator.core.node.PropertyGenerator")
     public Comment setContent(final String content) {
         assertNotNull(content);
-        if (content == this.content) {
+        if (content.equals(this.content)) {
             return this;
         }
         notifyPropertyChange(ObservableProperty.CONTENT, this.content, content);
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/LiteralStringValueExpr.java b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/LiteralStringValueExpr.java
index 1192a18e45..dcf0071a13 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/LiteralStringValueExpr.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/LiteralStringValueExpr.java
@@ -62,7 +62,7 @@ public String getValue() {
     @Generated("com.github.javaparser.generator.core.node.PropertyGenerator")
     public LiteralStringValueExpr setValue(final String value) {
         assertNotNull(value);
-        if (value == this.value) {
+        if (value.equals(this.value)) {
             return this;
         }
         notifyPropertyChange(ObservableProperty.VALUE, this.value, value);
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/MethodReferenceExpr.java b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/MethodReferenceExpr.java
index 09fce9570f..6ba7188472 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/MethodReferenceExpr.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/MethodReferenceExpr.java
@@ -151,7 +151,7 @@ public String getIdentifier() {
     @Generated("com.github.javaparser.generator.core.node.PropertyGenerator")
     public MethodReferenceExpr setIdentifier(final String identifier) {
         assertNonEmpty(identifier);
-        if (identifier == this.identifier) {
+        if (identifier.equals(this.identifier)) {
             return this;
         }
         notifyPropertyChange(ObservableProperty.IDENTIFIER, this.identifier, identifier);
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/Name.java b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/Name.java
index e0f6927bc8..14a8239a3c 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/Name.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/Name.java
@@ -101,7 +101,7 @@ public String getIdentifier() {
     @Generated("com.github.javaparser.generator.core.node.PropertyGenerator")
     public Name setIdentifier(final String identifier) {
         assertNonEmpty(identifier);
-        if (identifier == this.identifier) {
+        if (identifier.equals(this.identifier)) {
             return this;
         }
         notifyPropertyChange(ObservableProperty.IDENTIFIER, this.identifier, identifier);
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/SimpleName.java b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/SimpleName.java
index e70d3ffaf1..34da734713 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/expr/SimpleName.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/expr/SimpleName.java
@@ -85,7 +85,7 @@ public String getIdentifier() {
     @Generated("com.github.javaparser.generator.core.node.PropertyGenerator")
     public SimpleName setIdentifier(final String identifier) {
         assertNonEmpty(identifier);
-        if (identifier == this.identifier) {
+        if (identifier.equals(this.identifier)) {
             return this;
         }
         notifyPropertyChange(ObservableProperty.IDENTIFIER, this.identifier, identifier);


Test patch:
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/comments/CommentTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/comments/CommentTest.java
index f82084cdaa..65442b8760 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/comments/CommentTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/comments/CommentTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -24,11 +24,14 @@
 import static com.github.javaparser.StaticJavaParser.parse;
 import static com.github.javaparser.utils.TestUtils.assertEqualsStringIgnoringEol;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
 import com.github.javaparser.ast.CompilationUnit;
 import com.github.javaparser.ast.NodeList;
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
 import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.observer.AstObserver;
 import com.github.javaparser.ast.stmt.BlockStmt;
 import com.github.javaparser.javadoc.Javadoc;
 import com.github.javaparser.javadoc.description.JavadocDescription;
@@ -206,4 +209,18 @@ void testVerifyOrphanCommentInsertedInEmptyBlock() {
         block.addOrphanComment(new LineComment("TODO"));
         assertTrue(block.toString().contains("TODO"));
     }
+
+    @Test
+    void issue4791Test() {
+        String a = new String("Hello World");
+        String b = new String("Hello World");
+        LineComment comment = new LineComment(a);
+
+        AstObserver observer = mock(AstObserver.class);
+        comment.register(observer);
+
+        comment.setContent(b);
+
+        verifyNoInteractions(observer);
+    }
 }
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/LiteralStringValueExprTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/LiteralStringValueExprTest.java
index 41c92d71e5..3cff98cb30 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/LiteralStringValueExprTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/LiteralStringValueExprTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -23,7 +23,10 @@
 import static com.github.javaparser.StaticJavaParser.parseExpression;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
+import com.github.javaparser.ast.observer.AstObserver;
 import java.math.BigInteger;
 import org.assertj.core.data.Percentage;
 import org.junit.jupiter.api.Test;
@@ -185,4 +188,18 @@ void specialCharactersInStringsAreEscaped() {
         assertThat(new StringLiteralExpr("Hello\nWorld\rHello\"World\'").asString())
                 .isEqualTo("Hello\nWorld\rHello\"World\'");
     }
+
+    @Test
+    void issue4791Test() {
+        String a = new String("Hello World");
+        String b = new String("Hello World");
+        StringLiteralExpr expression = new StringLiteralExpr(a);
+
+        AstObserver observer = mock(AstObserver.class);
+        expression.register(observer);
+
+        expression.setValue(b);
+
+        verifyNoInteractions(observer);
+    }
 }
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/MethodReferenceExprTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/MethodReferenceExprTest.java
index 13597ea5f4..bc4fc86747 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/MethodReferenceExprTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/MethodReferenceExprTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -23,7 +23,10 @@
 
 import static com.github.javaparser.utils.TestUtils.assertExpressionValid;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
+import com.github.javaparser.ast.observer.AstObserver;
 import org.junit.jupiter.api.Test;
 
 class MethodReferenceExprTest {
@@ -132,4 +135,19 @@ void reference18() {
     void reference19() {
         assertExpressionValid("int[]::new // array creation");
     }
+
+    @Test
+    void issue4791Test() {
+        String a = new String("Greeter::sayHelloWorld");
+        String b = new String("Greeter::sayHelloWorld");
+        MethodReferenceExpr expression = new MethodReferenceExpr();
+        expression.setIdentifier(a);
+
+        AstObserver observer = mock(AstObserver.class);
+        expression.register(observer);
+
+        expression.setIdentifier(b);
+
+        verifyNoInteractions(observer);
+    }
 }
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/NameTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/NameTest.java
index 3194a7b484..deb27c4884 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/NameTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/NameTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -23,10 +23,13 @@
 
 import static com.github.javaparser.StaticJavaParser.*;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
 import com.github.javaparser.ParseProblemException;
 import com.github.javaparser.ast.CompilationUnit;
 import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.observer.AstObserver;
 import com.github.javaparser.printer.ConcreteSyntaxModel;
 import com.github.javaparser.utils.LineSeparator;
 import org.junit.jupiter.api.Test;
@@ -93,4 +96,18 @@ void isTopLevelPositive() {
         Name name = parseName("a.b.c");
         assertTrue(name.isTopLevel());
     }
+
+    @Test
+    void issue4791Test() {
+        String a = new String("c");
+        String b = new String("c");
+        Name expression = new Name(a);
+
+        AstObserver observer = mock(AstObserver.class);
+        expression.register(observer);
+
+        expression.setIdentifier(b);
+
+        verifyNoInteractions(observer);
+    }
 }
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/SimpleNameTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/SimpleNameTest.java
index 047ce5670e..fa7040320b 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/SimpleNameTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/expr/SimpleNameTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
- * Copyright (C) 2011, 2013-2024 The JavaParser Team.
+ * Copyright (C) 2011, 2013-2025 The JavaParser Team.
  *
  * This file is part of JavaParser.
  *
@@ -24,7 +24,10 @@
 import static com.github.javaparser.StaticJavaParser.parseSimpleName;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
+import com.github.javaparser.ast.observer.AstObserver;
 import org.junit.jupiter.api.Test;
 
 class SimpleNameTest {
@@ -49,4 +52,18 @@ void unicodeEscapesArePreservedInIdentifiers() {
         SimpleName name = parseSimpleName("xxx\\u2122xxx");
         assertEquals("xxx\\u2122xxx", name.asString());
     }
+
+    @Test
+    void issue4791Test() {
+        String a = new String("someName");
+        String b = new String("someName");
+        SimpleName expression = new SimpleName(a);
+
+        AstObserver observer = mock(AstObserver.class);
+        expression.register(observer);
+
+        expression.setIdentifier(b);
+
+        verifyNoInteractions(observer);
+    }
 }