This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push:
new 911396e8f1 Review "Correct equals() implementations in method
expression classes"
911396e8f1 is described below
commit 911396e8f1772a121bce43d3b122534f3824e4e2
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Nov 5 12:52:37 2026 +0000
Review "Correct equals() implementations in method expression classes"
Fix various additional edge cases for method expression equality
---
.../servlet/jsp/el/ImplicitObjectELResolver.java | 12 +-
java/org/apache/el/MethodExpressionImpl.java | 6 +-
java/org/apache/el/ValueExpressionImpl.java | 6 +-
java/org/apache/el/lang/FunctionMapperImpl.java | 10 +-
java/org/apache/el/util/ExpressionUtils.java | 96 +++++++++++++
.../jsp/el/TestImplicitObjectELResolver.java | 152 +++++++++++++++++++++
test/org/apache/el/TestMethodExpressionImpl.java | 117 ++++++++++++++++
test/org/apache/el/TestValueExpressionImpl.java | 43 ++++++
.../org/apache/el/lang/TestFunctionMapperImpl.java | 104 ++++++++++++++
9 files changed, 536 insertions(+), 10 deletions(-)
diff --git a/java/jakarta/servlet/jsp/el/ImplicitObjectELResolver.java
b/java/jakarta/servlet/jsp/el/ImplicitObjectELResolver.java
index 07cdec45b5..caf393fa81 100644
--- a/java/jakarta/servlet/jsp/el/ImplicitObjectELResolver.java
+++ b/java/jakarta/servlet/jsp/el/ImplicitObjectELResolver.java
@@ -508,12 +508,20 @@ public class ImplicitObjectELResolver extends ELResolver {
@Override
public boolean equals(Object obj) {
- return obj != null && this.hashCode() == obj.hashCode();
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Map.Entry<?,?>)) {
+ return false;
+ }
+ return Objects.equals(this.key, ((Map.Entry<?,?>)
obj).getKey()) &&
+ Objects.equals(getValue(), ((Map.Entry<?,?>)
obj).getValue());
}
@Override
public int hashCode() {
- return this.key.hashCode();
+ // As required by Map.Entry
+ return Objects.hashCode(this.key) ^
Objects.hashCode(getValue());
}
}
diff --git a/java/org/apache/el/MethodExpressionImpl.java
b/java/org/apache/el/MethodExpressionImpl.java
index 4de8495706..1ddb955ab3 100644
--- a/java/org/apache/el/MethodExpressionImpl.java
+++ b/java/org/apache/el/MethodExpressionImpl.java
@@ -34,6 +34,7 @@ import jakarta.el.VariableMapper;
import org.apache.el.lang.EvaluationContext;
import org.apache.el.lang.ExpressionBuilder;
import org.apache.el.parser.Node;
+import org.apache.el.util.ExpressionUtils;
import org.apache.el.util.ReflectionUtil;
@@ -137,7 +138,8 @@ public final class MethodExpressionImpl extends
MethodExpression implements Exte
return false;
}
- return this.getNode().equals(((MethodExpressionImpl) obj).getNode());
+ MethodExpressionImpl other = (MethodExpressionImpl) obj;
+ return ExpressionUtils.equals(getNode(), fnMapper, other.getNode(),
other.fnMapper);
}
/**
@@ -216,7 +218,7 @@ public final class MethodExpressionImpl extends
MethodExpression implements Exte
*/
@Override
public int hashCode() {
- return this.getNode().hashCode();
+ return ExpressionUtils.hashCode(getNode(), fnMapper);
}
/**
diff --git a/java/org/apache/el/ValueExpressionImpl.java
b/java/org/apache/el/ValueExpressionImpl.java
index 2bd002a1c2..cc0c4b89dd 100644
--- a/java/org/apache/el/ValueExpressionImpl.java
+++ b/java/org/apache/el/ValueExpressionImpl.java
@@ -34,6 +34,7 @@ import org.apache.el.lang.EvaluationContext;
import org.apache.el.lang.ExpressionBuilder;
import org.apache.el.parser.AstLiteralExpression;
import org.apache.el.parser.Node;
+import org.apache.el.util.ExpressionUtils;
import org.apache.el.util.ReflectionUtil;
@@ -120,7 +121,8 @@ public final class ValueExpressionImpl extends
ValueExpression implements Extern
return false;
}
- return this.getNode().equals(((ValueExpressionImpl) obj).getNode());
+ ValueExpressionImpl other = (ValueExpressionImpl) obj;
+ return ExpressionUtils.equals(getNode(), fnMapper, other.getNode(),
other.fnMapper);
}
/**
@@ -175,7 +177,7 @@ public final class ValueExpressionImpl extends
ValueExpression implements Extern
@Override
public int hashCode() {
- return this.getNode().hashCode();
+ return ExpressionUtils.hashCode(getNode(), fnMapper);
}
@Override
diff --git a/java/org/apache/el/lang/FunctionMapperImpl.java
b/java/org/apache/el/lang/FunctionMapperImpl.java
index 9849c434ce..669a40ba0f 100644
--- a/java/org/apache/el/lang/FunctionMapperImpl.java
+++ b/java/org/apache/el/lang/FunctionMapperImpl.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Method;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -112,9 +113,9 @@ public class FunctionMapperImpl extends FunctionMapper
implements Externalizable
/**
* Creates a new function mapping for the given method.
*
- * @param prefix The namespace prefix
+ * @param prefix The namespace prefix
* @param localName The local function name
- * @param m The method to map
+ * @param m The method to map
*/
public Function(String prefix, String localName, Method m) {
if (localName == null) {
@@ -184,14 +185,15 @@ public class FunctionMapperImpl extends FunctionMapper
implements Externalizable
@Override
public boolean equals(Object obj) {
if (obj instanceof Function) {
- return this.hashCode() == obj.hashCode();
+ return Objects.equals(this.prefix, ((Function) obj).prefix) &&
+ Objects.equals(this.localName, ((Function)
obj).localName);
}
return false;
}
@Override
public int hashCode() {
- return (this.prefix + this.localName).hashCode();
+ return Objects.hash(this.prefix, this.localName);
}
}
diff --git a/java/org/apache/el/util/ExpressionUtils.java
b/java/org/apache/el/util/ExpressionUtils.java
new file mode 100644
index 0000000000..1824b18e14
--- /dev/null
+++ b/java/org/apache/el/util/ExpressionUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.el.util;
+
+import java.lang.reflect.Method;
+
+import jakarta.el.FunctionMapper;
+
+import org.apache.el.parser.AstFunction;
+import org.apache.el.parser.Node;
+
+/**
+ * Compares expression trees using their captured function mappings. Parsed
nodes are shared between expressions, so
+ * function mappings must be considered separately rather than stored on the
nodes.
+ */
+public final class ExpressionUtils {
+
+ private ExpressionUtils() {
+ // Utility class. Hide default constructor.
+ }
+
+
+ /**
+ * Compare parsed expressions and the functions they reference.
+ *
+ * @param first First expression tree
+ * @param firstMapper First expression's captured function mapper
+ * @param second Second expression tree
+ * @param secondMapper Second expression's captured function mapper
+ *
+ * @return {@code true} if the expressions have identical parsed
representations and function mappings
+ */
+ public static boolean equals(Node first, FunctionMapper firstMapper, Node
second, FunctionMapper secondMapper) {
+ return first.equals(second) && equalsFunctions(first, firstMapper,
second, secondMapper);
+ }
+
+
+ private static boolean equalsFunctions(Node first, FunctionMapper
firstMapper, Node second,
+ FunctionMapper secondMapper) {
+ if (first instanceof AstFunction && second instanceof AstFunction &&
+ !getFunction((AstFunction) first, firstMapper)
+ .equals(getFunction((AstFunction) second,
secondMapper))) {
+ return false;
+ }
+ for (int i = 0; i < first.jjtGetNumChildren(); i++) {
+ if (!equalsFunctions(first.jjtGetChild(i), firstMapper,
second.jjtGetChild(i), secondMapper)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Hash a parsed expression and the functions it references.
+ *
+ * @param node Expression tree
+ * @param mapper Expression's captured function mapper
+ *
+ * @return Hash consistent with function-aware expression equality
+ */
+ public static int hashCode(Node node, FunctionMapper mapper) {
+ return node.hashCode() + hashFunctions(node, mapper);
+ }
+
+
+ private static int hashFunctions(Node node, FunctionMapper mapper) {
+ int result = node instanceof AstFunction ? getFunction((AstFunction)
node, mapper).hashCode() : 0;
+ for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+ result = 31 * result + hashFunctions(node.jjtGetChild(i), mapper);
+ }
+ return result;
+ }
+
+
+ private static Object getFunction(AstFunction function, FunctionMapper
mapper) {
+ Method method = mapper == null ? null :
mapper.resolveFunction(function.getPrefix(), function.getLocalName());
+ // Unmapped calls (such as lambdas and imported constructors) are
resolved at evaluation time. Compare their
+ // names so distinct unresolved calls are not treated as aliases.
+ return method == null ? function.getOutputName() : method;
+ }
+}
diff --git a/test/jakarta/servlet/jsp/el/TestImplicitObjectELResolver.java
b/test/jakarta/servlet/jsp/el/TestImplicitObjectELResolver.java
new file mode 100644
index 0000000000..6eeb6e84f2
--- /dev/null
+++ b/test/jakarta/servlet/jsp/el/TestImplicitObjectELResolver.java
@@ -0,0 +1,152 @@
+/*
+ * 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 jakarta.servlet.jsp.el;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.el.ELContext;
+import jakarta.el.ExpressionFactory;
+import jakarta.el.StandardELContext;
+import jakarta.servlet.jsp.JspContext;
+import jakarta.servlet.jsp.PageContext;
+import jakarta.servlet.jsp.TesterPageContext;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/*
+ * The entries of the implicit object scope maps must honour the Map.Entry
equals() contract rather than deciding
+ * equality from the hash code of the key alone.
+ */
+public class TestImplicitObjectELResolver {
+
+ private ELContext context;
+
+ @Before
+ public void setUp() {
+ PageContext page = new AttributePageContext();
+ page.setAttribute("Aa", "value1");
+ page.setAttribute("BB", "value2");
+ page.setAttribute("other", "value3");
+
+ context = new StandardELContext(ExpressionFactory.newInstance());
+ context.putContext(JspContext.class, page);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private Set<Map.Entry<String,Object>> getPageScopeEntries() {
+ Object pageScope = new ImplicitObjectELResolver().getValue(context,
null, "pageScope");
+ Assert.assertTrue(pageScope instanceof Map);
+ return ((Map<String,Object>) pageScope).entrySet();
+ }
+
+
+ private Map.Entry<String,Object> getEntry(String key) {
+ for (Map.Entry<String,Object> entry : getPageScopeEntries()) {
+ if (entry.getKey().equals(key)) {
+ return entry;
+ }
+ }
+ Assert.fail("No entry for key [" + key + "]");
+ return null;
+ }
+
+
+ @Test
+ public void testEntrySetContents() {
+ Assert.assertEquals(3, getPageScopeEntries().size());
+ Assert.assertEquals("value1", getEntry("Aa").getValue());
+ }
+
+
+ @Test
+ public void testEntryEqualsSameKey() {
+ Assert.assertEquals(getEntry("other"), getEntry("other"));
+ Assert.assertEquals(getEntry("other").hashCode(),
getEntry("other").hashCode());
+ }
+
+
+ @Test
+ public void testEntryNotEqualsHashCollidingKeys() {
+ // "Aa" and "BB" are a well known pair of strings with the same hash
code
+ Assert.assertEquals("Aa".hashCode(), "BB".hashCode());
+
+ Assert.assertNotEquals(getEntry("Aa"), getEntry("BB"));
+ }
+
+
+ @Test
+ public void testEntryNotEqualsOtherType() {
+ Map.Entry<String,Object> entry = getEntry("Aa");
+
+ // A String with the same hash code as the key is not an equal
Map.Entry
+ Assert.assertNotEquals(entry, "Aa");
+ Assert.assertNotEquals(entry, null);
+ }
+
+
+ @Test
+ public void testEntryEqualsGenericEntry() {
+ // Map.Entry contract: equal if the keys and values are equal,
whatever the implementation
+ Map.Entry<String,Object> entry = getEntry("Aa");
+ Map.Entry<String,Object> generic = Collections.singletonMap("Aa",
(Object) "value1").entrySet().iterator().next();
+ Map.Entry<String,Object> differentValue =
+ Collections.singletonMap("Aa", (Object)
"other").entrySet().iterator().next();
+
+ Assert.assertEquals(generic, entry);
+ Assert.assertEquals(generic.hashCode(), entry.hashCode());
+ Assert.assertNotEquals(differentValue, entry);
+ }
+
+
+ @Test
+ public void testEntrySetHashCollidingKeysBothPresent() {
+ Set<Map.Entry<String,Object>> set = new
HashSet<>(getPageScopeEntries());
+ Assert.assertEquals(3, set.size());
+ }
+
+
+ private static class AttributePageContext extends TesterPageContext {
+
+ private final Map<String,Object> attributes = new HashMap<>();
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ // The resolver stores its own scope manager as a page attribute.
Only keep the test's attributes.
+ if (value instanceof String) {
+ attributes.put(name, value);
+ }
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNamesInScope(int scope) {
+ return Collections.enumeration(new HashSet<>(attributes.keySet()));
+ }
+ }
+}
diff --git a/test/org/apache/el/TestMethodExpressionImpl.java
b/test/org/apache/el/TestMethodExpressionImpl.java
index a3bdd130d0..dbd35b0b90 100644
--- a/test/org/apache/el/TestMethodExpressionImpl.java
+++ b/test/org/apache/el/TestMethodExpressionImpl.java
@@ -16,6 +16,8 @@
*/
package org.apache.el;
+import java.util.HashSet;
+import java.util.Set;
import java.util.function.Function;
import jakarta.el.ELContext;
@@ -848,6 +850,121 @@ public class TestMethodExpressionImpl {
}
+ @Test
+ public void testNotEqualsDifferentFunctionArguments() throws Exception {
+ ELProcessor elp = new ELProcessor();
+ elp.defineBean("beanB", beanB);
+ elp.defineFunction("fn", "decimal",
Integer.class.getMethod("toString", int.class));
+ elp.defineFunction("fn", "hex", Integer.class.getMethod("toHexString",
int.class));
+ ELContext elpContext = elp.getELManager().getELContext();
+
+ MethodExpression me1 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:decimal(10))}",
+ String.class, null);
+ MethodExpression me2 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:hex(10))}", String.class,
+ null);
+
+ // Function names must distinguish expressions even when their
argument trees are identical.
+ Assert.assertEquals("10", me1.invoke(elpContext, null));
+ Assert.assertEquals("a", me2.invoke(elpContext, null));
+ Assert.assertNotEquals(me1, me2);
+
+ Set<MethodExpression> expressions = new HashSet<>();
+ expressions.add(me1);
+ expressions.add(me2);
+ Assert.assertEquals(2, expressions.size());
+ }
+
+
+ @Test
+ public void testNotEqualsDifferentFunctionPrefixes() throws Exception {
+ ELProcessor elp = new ELProcessor();
+ elp.defineBean("beanB", beanB);
+ elp.defineFunction("decimal", "convert",
Integer.class.getMethod("toString", int.class));
+ elp.defineFunction("hex", "convert",
Integer.class.getMethod("toHexString", int.class));
+ ELContext elpContext = elp.getELManager().getELContext();
+
+ MethodExpression me1 = factory.createMethodExpression(elpContext,
"${beanB.echo(decimal:convert(10))}",
+ String.class, null);
+ MethodExpression me2 = factory.createMethodExpression(elpContext,
"${beanB.echo(hex:convert(10))}",
+ String.class, null);
+
+ Assert.assertEquals("10", me1.invoke(elpContext, null));
+ Assert.assertEquals("a", me2.invoke(elpContext, null));
+ Assert.assertNotEquals(me1, me2);
+ }
+
+
+ @Test
+ public void testEqualsHashCodeSameFunctionArgument() throws Exception {
+ ELProcessor elp = new ELProcessor();
+ elp.defineBean("beanB", beanB);
+ elp.defineFunction("fn", "hex", Integer.class.getMethod("toHexString",
int.class));
+ ELContext elpContext = elp.getELManager().getELContext();
+
+ MethodExpression me1 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:hex(10))}", String.class,
+ null);
+ MethodExpression me2 = factory.createMethodExpression(elpContext, "${
beanB.echo( fn:hex(10) ) }",
+ String.class, null);
+ MethodExpression me3 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:hex(11))}", String.class,
+ null);
+
+ Assert.assertEquals(me1, me2);
+ Assert.assertEquals(me1.hashCode(), me2.hashCode());
+ Assert.assertNotEquals(me1, me3);
+ }
+
+
+ @Test
+ public void testEqualsHashCodeFunctionAliases() throws Exception {
+ ELProcessor elp = new ELProcessor();
+ elp.defineBean("beanB", beanB);
+ elp.defineFunction("one", "convert",
Integer.class.getMethod("toString", int.class));
+ elp.defineFunction("two", "convert",
Integer.class.getMethod("toString", int.class));
+ elp.defineFunction("one", "alias", Integer.class.getMethod("toString",
int.class));
+ ELContext elpContext = elp.getELManager().getELContext();
+
+ MethodExpression me1 = factory.createMethodExpression(elpContext,
"${beanB.echo(one:convert(10))}",
+ String.class, null);
+ MethodExpression me2 = factory.createMethodExpression(elpContext,
"${beanB.echo(two:convert(10))}",
+ String.class, null);
+ MethodExpression me3 = factory.createMethodExpression(elpContext,
"${beanB.echo(one:alias(10))}",
+ String.class, null);
+
+ Assert.assertEquals("10", me1.invoke(elpContext, null));
+ Assert.assertEquals("10", me2.invoke(elpContext, null));
+ Assert.assertEquals("10", me3.invoke(elpContext, null));
+ Assert.assertEquals(me1, me2);
+ Assert.assertEquals(me1, me3);
+ Assert.assertEquals(me1.hashCode(), me2.hashCode());
+ Assert.assertEquals(me1.hashCode(), me3.hashCode());
+
+ Set<MethodExpression> expressions = new HashSet<>();
+ expressions.add(me1);
+ expressions.add(me2);
+ expressions.add(me3);
+ Assert.assertEquals(1, expressions.size());
+ }
+
+
+ @Test
+ public void testNotEqualsRemappedFunction() throws Exception {
+ ELProcessor elp = new ELProcessor();
+ elp.defineBean("beanB", beanB);
+ elp.defineFunction("fn", "convert",
Integer.class.getMethod("toString", int.class));
+ ELContext elpContext = elp.getELManager().getELContext();
+ MethodExpression me1 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:convert(10))}",
+ String.class, null);
+
+ elp.defineFunction("fn", "convert",
Integer.class.getMethod("toHexString", int.class));
+ MethodExpression me2 = factory.createMethodExpression(elpContext,
"${beanB.echo(fn:convert(10))}",
+ String.class, null);
+
+ Assert.assertEquals("10", me1.invoke(elpContext, null));
+ Assert.assertEquals("a", me2.invoke(elpContext, null));
+ Assert.assertNotEquals(me1, me2);
+ }
+
+
@Test
public void testNotEqualsHashCollidingExpressions() {
// "Aa" and "BB" are a well known pair of strings with the same hash
code
diff --git a/test/org/apache/el/TestValueExpressionImpl.java
b/test/org/apache/el/TestValueExpressionImpl.java
index cec9a2edc8..cf43e63eba 100644
--- a/test/org/apache/el/TestValueExpressionImpl.java
+++ b/test/org/apache/el/TestValueExpressionImpl.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import jakarta.el.ELContext;
import jakarta.el.ExpressionFactory;
import jakarta.el.PropertyNotFoundException;
+import jakarta.el.StandardELContext;
import jakarta.el.ValueExpression;
import jakarta.el.ValueReference;
@@ -36,6 +37,48 @@ import org.apache.jasper.el.ELContextImpl;
public class TestValueExpressionImpl {
+ @Test
+ public void testFunctionMappingEquality() throws Exception {
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ ELContext context = new StandardELContext(factory);
+ context.getFunctionMapper().mapFunction("one", "convert",
Integer.class.getMethod("toString", int.class));
+ context.getFunctionMapper().mapFunction("two", "convert",
Integer.class.getMethod("toString", int.class));
+ context.getFunctionMapper().mapFunction("one", "alias",
Integer.class.getMethod("toString", int.class));
+
+ ValueExpression ve1 = factory.createValueExpression(context,
"${one:convert(10)}", String.class);
+ ValueExpression ve2 = factory.createValueExpression(context,
"${two:convert(10)}", String.class);
+ ValueExpression ve3 = factory.createValueExpression(context,
"${one:alias(10)}", String.class);
+
+ Assert.assertEquals(ve1, ve2);
+ Assert.assertEquals(ve1, ve3);
+ Assert.assertEquals(ve1.hashCode(), ve2.hashCode());
+ Assert.assertEquals(ve1.hashCode(), ve3.hashCode());
+
+ context.getFunctionMapper().mapFunction("one", "convert",
Integer.class.getMethod("toHexString", int.class));
+ ValueExpression ve4 = factory.createValueExpression(context,
"${one:convert(10)}", String.class);
+
+ Assert.assertEquals("10", ve1.getValue(context));
+ Assert.assertEquals("a", ve4.getValue(context));
+ Assert.assertNotEquals(ve1, ve4);
+ }
+
+
+ @Test
+ public void testImportedFunctionEquality() {
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ ELContext context = new StandardELContext(factory);
+ ValueExpression ve1 = factory.createValueExpression(context,
"${Integer(10)}", Object.class);
+ ValueExpression ve2 = factory.createValueExpression(context, "${
Integer(10) }", Object.class);
+ ValueExpression ve3 = factory.createValueExpression(context,
"${Long(10)}", Object.class);
+
+ Assert.assertEquals(Integer.valueOf(10), ve1.getValue(context));
+ Assert.assertEquals(Long.valueOf(10), ve3.getValue(context));
+ Assert.assertEquals(ve1, ve2);
+ Assert.assertEquals(ve1.hashCode(), ve2.hashCode());
+ Assert.assertNotEquals(ve1, ve3);
+ }
+
+
@Test
public void testGetValueReference() {
ExpressionFactory factory = ExpressionFactory.newInstance();
diff --git a/test/org/apache/el/lang/TestFunctionMapperImpl.java
b/test/org/apache/el/lang/TestFunctionMapperImpl.java
new file mode 100644
index 0000000000..78ab7c2c25
--- /dev/null
+++ b/test/org/apache/el/lang/TestFunctionMapperImpl.java
@@ -0,0 +1,104 @@
+/*
+ * 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.el.lang;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/*
+ * FunctionMapperImpl.Function must not decide equality from its hash code
alone.
+ */
+public class TestFunctionMapperImpl {
+
+ private static final Method METHOD;
+
+ static {
+ try {
+ METHOD = Integer.class.getMethod("toString", int.class);
+ } catch (NoSuchMethodException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+
+ @Test
+ public void testEqualsSameFunction() {
+ FunctionMapperImpl.Function f1 = new FunctionMapperImpl.Function("fn",
"convert", METHOD);
+ FunctionMapperImpl.Function f2 = new FunctionMapperImpl.Function("fn",
"convert", METHOD);
+
+ Assert.assertEquals(f1, f2);
+ Assert.assertEquals(f1.hashCode(), f2.hashCode());
+ }
+
+
+ @Test
+ public void testNotEqualsDifferentLocalName() {
+ FunctionMapperImpl.Function f1 = new FunctionMapperImpl.Function("fn",
"convert", METHOD);
+ FunctionMapperImpl.Function f2 = new FunctionMapperImpl.Function("fn",
"other", METHOD);
+
+ Assert.assertNotEquals(f1, f2);
+ }
+
+
+ @Test
+ public void testNotEqualsDifferentPrefix() {
+ FunctionMapperImpl.Function f1 = new
FunctionMapperImpl.Function("fn1", "convert", METHOD);
+ FunctionMapperImpl.Function f2 = new
FunctionMapperImpl.Function("fn2", "convert", METHOD);
+
+ Assert.assertNotEquals(f1, f2);
+ }
+
+
+ @Test
+ public void testNotEqualsHashCollidingLocalNames() {
+ // "Aa" and "BB" are a well known pair of strings with the same hash
code
+ Assert.assertEquals("Aa".hashCode(), "BB".hashCode());
+
+ FunctionMapperImpl.Function f1 = new FunctionMapperImpl.Function("fn",
"Aa", METHOD);
+ FunctionMapperImpl.Function f2 = new FunctionMapperImpl.Function("fn",
"BB", METHOD);
+
+ Assert.assertNotEquals(f1, f2);
+ }
+
+
+ @Test
+ public void testNotEqualsPrefixLocalNameBoundary() {
+ // Same concatenation of prefix and local name but different functions
+ FunctionMapperImpl.Function f1 = new FunctionMapperImpl.Function("ab",
"c", METHOD);
+ FunctionMapperImpl.Function f2 = new FunctionMapperImpl.Function("a",
"bc", METHOD);
+
+ Assert.assertNotEquals(f1, f2);
+
+ Set<FunctionMapperImpl.Function> functions = new HashSet<>();
+ functions.add(f1);
+ functions.add(f2);
+ Assert.assertEquals(2, functions.size());
+ }
+
+
+ @Test
+ public void testNotEqualsOtherType() {
+ FunctionMapperImpl.Function f1 = new FunctionMapperImpl.Function("fn",
"convert", METHOD);
+
+ Assert.assertNotEquals(f1, "fnconvert");
+ Assert.assertNotEquals(f1, null);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]