activeProfiles);
@@ -49,16 +50,20 @@ public interface Profiles {
* {@code "production"}) or a profile expression. A profile expression allows
* for more complicated profile logic to be expressed, for example
* {@code "production & cloud"}.
- * The following operators are supported in profile expressions:
+ *
The following operators are supported in profile expressions.
*
- * - {@code !} - A logical not of the profile
- * - {@code &} - A logical and of the profiles
- * - {@code |} - A logical or of the profiles
+ * - {@code !} - A logical NOT of the profile or profile expression
+ * - {@code &} - A logical AND of the profiles or profile expressions
+ * - {@code |} - A logical OR of the profiles or profile expressions
*
* Please note that the {@code &} and {@code |} operators may not be mixed
* without using parentheses. For example {@code "a & b | c"} is not a valid
* expression; it must be expressed as {@code "(a & b) | c"} or
* {@code "a & (b | c)"}.
+ *
As of Spring Framework 5.1.17, two {@code Profiles} instances returned
+ * by this method are considered equivalent to each other (in terms of
+ * {@code equals()} and {@code hashCode()} semantics) if they are created
+ * with identical profile strings.
* @param profiles the profile strings to include
* @return a new {@link Profiles} instance
*/
diff --git a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
index 1ac87617061e..5d4dd0c86b41 100644
--- a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
+++ b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,10 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Predicate;
@@ -30,6 +33,7 @@
* Internal parser used by {@link Profiles#of}.
*
* @author Phillip Webb
+ * @author Sam Brannen
* @since 5.1
*/
final class ProfilesParser {
@@ -56,6 +60,7 @@ private static Profiles parseExpression(String expression) {
private static Profiles parseTokens(String expression, StringTokenizer tokens) {
return parseTokens(expression, tokens, Context.NONE);
}
+
private static Profiles parseTokens(String expression, StringTokenizer tokens, Context context) {
List elements = new ArrayList<>();
Operator operator = null;
@@ -145,12 +150,12 @@ private enum Context {NONE, INVERT, BRACKET}
private static class ParsedProfiles implements Profiles {
- private final String[] expressions;
+ private final Set expressions = new LinkedHashSet<>();
private final Profiles[] parsed;
ParsedProfiles(String[] expressions, Profiles[] parsed) {
- this.expressions = expressions;
+ Collections.addAll(this.expressions, expressions);
this.parsed = parsed;
}
@@ -164,10 +169,31 @@ public boolean matches(Predicate activeProfiles) {
return false;
}
+ @Override
+ public int hashCode() {
+ return this.expressions.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ParsedProfiles that = (ParsedProfiles) obj;
+ return this.expressions.equals(that.expressions);
+ }
+
@Override
public String toString() {
- return StringUtils.arrayToDelimitedString(this.expressions, " or ");
+ return StringUtils.collectionToDelimitedString(this.expressions, " or ");
}
+
}
}
diff --git a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java
index 60f1b8b95267..88cd281a2c00 100644
--- a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java
+++ b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -292,11 +292,72 @@ public void malformedExpressions() {
@Test
public void sensibleToString() {
- assertEquals("spring & framework or java | kotlin",
- Profiles.of("spring & framework", "java | kotlin").toString());
+ assertEquals("spring", Profiles.of("spring").toString());
+ assertEquals("(spring & framework) | (spring & java)", Profiles.of("(spring & framework) | (spring & java)").toString());
+ assertEquals("(spring&framework)|(spring&java)", Profiles.of("(spring&framework)|(spring&java)").toString());
+ assertEquals("spring & framework or java | kotlin", Profiles.of("spring & framework", "java | kotlin").toString());
+ assertEquals("java | kotlin or spring & framework", Profiles.of("java | kotlin", "spring & framework").toString());
}
- private void assertMalformed(Supplier supplier) {
+ @Test
+ public void sensibleEquals() {
+ assertEqual("(spring & framework) | (spring & java)");
+ assertEqual("(spring&framework)|(spring&java)");
+ assertEqual("spring & framework", "java | kotlin");
+
+ // Ensure order of individual expressions does not affect equals().
+ String expression1 = "A | B";
+ String expression2 = "C & (D | E)";
+ Profiles profiles1 = Profiles.of(expression1, expression2);
+ Profiles profiles2 = Profiles.of(expression2, expression1);
+ assertEquals(profiles1, profiles2);
+ assertEquals(profiles2, profiles1);
+ }
+
+ private void assertEqual(String... expressions) {
+ Profiles profiles1 = Profiles.of(expressions);
+ Profiles profiles2 = Profiles.of(expressions);
+ assertEquals(profiles1, profiles2);
+ assertEquals(profiles2, profiles1);
+ }
+
+ @Test
+ public void sensibleHashCode() {
+ assertHashCode("(spring & framework) | (spring & java)");
+ assertHashCode("(spring&framework)|(spring&java)");
+ assertHashCode("spring & framework", "java | kotlin");
+
+ // Ensure order of individual expressions does not affect hashCode().
+ String expression1 = "A | B";
+ String expression2 = "C & (D | E)";
+ Profiles profiles1 = Profiles.of(expression1, expression2);
+ Profiles profiles2 = Profiles.of(expression2, expression1);
+ assertEquals(profiles1.hashCode(), profiles2.hashCode());
+ }
+
+ private void assertHashCode(String... expressions) {
+ Profiles profiles1 = Profiles.of(expressions);
+ Profiles profiles2 = Profiles.of(expressions);
+ assertEquals(profiles1.hashCode(), profiles2.hashCode());
+ }
+
+ @Test
+ public void equalsAndHashCodeAreNotBasedOnLogicalStructureOfNodesWithinExpressionTree() {
+ Profiles profiles1 = Profiles.of("A | B");
+ Profiles profiles2 = Profiles.of("B | A");
+
+ assertTrue(profiles1.matches(activeProfiles("A")));
+ assertTrue(profiles1.matches(activeProfiles("B")));
+ assertTrue(profiles2.matches(activeProfiles("A")));
+ assertTrue(profiles2.matches(activeProfiles("B")));
+
+ assertNotEquals(profiles1, profiles2);
+ assertNotEquals(profiles2, profiles1);
+ assertNotEquals(profiles1.hashCode(), profiles2.hashCode());
+ }
+
+
+ private static void assertMalformed(Supplier supplier) {
try {
supplier.get();
fail("Not malformed");
@@ -305,7 +366,7 @@ private void assertMalformed(Supplier supplier) {
assertTrue(ex.getMessage().contains("Malformed"));
}
}
-
+
private static Predicate activeProfiles(String... profiles) {
return new MockActiveProfiles(profiles);
}