Skip to content

Commit f330597

Browse files
committed
HADOOP-19161. review feedback + copy() operation
* Mukund's feedback * Remove unused "throws Throwable" on test cases as appropriate Save enumClass constructor parameter of FlagSet * add accessor * compare in equals * use in a copy() operation which creates a mutable deep copy of an object. * explicit checks for null enumclass, prefix in constructor Change-Id: I8080d772bfd301ab6e3ed802d54a060a63feea78
1 parent d55ffeb commit f330597

File tree

4 files changed

+126
-27
lines changed

4 files changed

+126
-27
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/impl/FlagSet.java

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Objects;
26-
import java.util.Set;
2726
import java.util.concurrent.atomic.AtomicBoolean;
2827
import java.util.stream.Collectors;
2928
import javax.annotation.Nullable;
@@ -33,6 +32,7 @@
3332
import org.apache.hadoop.util.ConfigurationHelper;
3433
import org.apache.hadoop.util.Preconditions;
3534

35+
import static java.util.Objects.requireNonNull;
3636
import static org.apache.hadoop.util.ConfigurationHelper.mapEnumNamesToValues;
3737

3838
/**
@@ -47,10 +47,22 @@
4747
*/
4848
public final class FlagSet<E extends Enum<E>> implements StreamCapabilities {
4949

50+
/**
51+
* Class of the enum.
52+
* Used for duplicating the flags as java type erasure
53+
* loses this information otherwise.
54+
*/
55+
private final Class<E> enumClass;
56+
57+
/**
58+
* Prefix for path capabilities probe.
59+
*/
60+
private final String prefix;
61+
5062
/**
5163
* Set of flags.
5264
*/
53-
private final Set<E> flags;
65+
private final EnumSet<E> flags;
5466

5567
/**
5668
* Is the set immutable?
@@ -71,7 +83,8 @@ public final class FlagSet<E extends Enum<E>> implements StreamCapabilities {
7183
private FlagSet(final Class<E> enumClass,
7284
final String prefix,
7385
@Nullable final EnumSet<E> flags) {
74-
86+
this.enumClass = requireNonNull(enumClass, "null enumClass");
87+
this.prefix = requireNonNull(prefix, "null prefix");
7588
this.flags = flags != null
7689
? EnumSet.copyOf(flags)
7790
: EnumSet.noneOf(enumClass);
@@ -164,6 +177,22 @@ public void makeImmutable() {
164177
immutable.set(true);
165178
}
166179

180+
/**
181+
* Is the FlagSet immutable?
182+
* @return true iff the FlagSet is immutable.
183+
*/
184+
public boolean isImmutable() {
185+
return immutable.get();
186+
}
187+
188+
/**
189+
* Get the enum class.
190+
* @return the enum class.
191+
*/
192+
public Class<E> getEnumClass() {
193+
return enumClass;
194+
}
195+
167196
@Override
168197
public String toString() {
169198
return "{" +
@@ -183,11 +212,16 @@ public List<String> pathCapabilities() {
183212
.collect(Collectors.toList());
184213
}
185214

186-
187215
/**
188-
* Equality is based on the set.
216+
* Equality is based on the value of {@link #enumClass} and
217+
* {@link #prefix} and the contents of the set, which must match.
218+
* <p>
219+
* The immutability flag is not considered, nor is the
220+
* {@link #namesToValues} map, though as that is generated from
221+
* the enumeration and prefix, it is implicitly equal if the prefix
222+
* and enumClass fields are equal.
189223
* @param o other object
190-
* @return true iff the flags are equal.
224+
* @return true iff the equality condition is met.
191225
*/
192226
@Override
193227
public boolean equals(final Object o) {
@@ -198,7 +232,9 @@ public boolean equals(final Object o) {
198232
return false;
199233
}
200234
FlagSet<?> flagSet = (FlagSet<?>) o;
201-
return Objects.equals(flags, flagSet.flags);
235+
return Objects.equals(enumClass, flagSet.enumClass)
236+
&& Objects.equals(prefix, flagSet.prefix)
237+
&& Objects.equals(flags, flagSet.flags);
202238
}
203239

204240
/**
@@ -210,14 +246,22 @@ public int hashCode() {
210246
return Objects.hashCode(flags);
211247
}
212248

249+
/**
250+
* Create a copy of the FlagSet.
251+
* @return a new mutable instance with a separate copy of the flags
252+
*/
253+
public FlagSet<E> copy() {
254+
return new FlagSet<>(enumClass, prefix, flags);
255+
}
256+
213257
/**
214258
* Convert to a string which can be then set in a configuration.
215259
* This is effectively a marshalled form of the flags.
216260
* @return a comma separated list of flag names.
217261
*/
218262
public String toConfigurationString() {
219263
return flags.stream()
220-
.map(e -> e.name())
264+
.map(Enum::name)
221265
.collect(Collectors.joining(", "));
222266
}
223267

@@ -244,12 +288,17 @@ public static <E extends Enum<E>> FlagSet<E> createFlagSet(
244288
* @param <E> enum type
245289
* @return a mutable FlagSet
246290
*/
291+
@SafeVarargs
247292
public static <E extends Enum<E>> FlagSet<E> createFlagSet(
248293
final Class<E> enumClass,
249294
final String prefix,
250295
final E... enabled) {
251296
final FlagSet<E> flagSet = new FlagSet<>(enumClass, prefix, null);
252-
Arrays.stream(enabled).forEach(flagSet::enable);
297+
Arrays.stream(enabled).forEach(flag -> {
298+
if (flag != null) {
299+
flagSet.enable(flag);
300+
}
301+
});
253302
return flagSet;
254303
}
255304

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ConfigurationHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ private ConfigurationHelper() {
5555
* trim the list, map to enum values in the message (case insensitive)
5656
* and return the set.
5757
* Special handling of "*" meaning: all values.
58-
* @param key key for error messages.
58+
* @param key Configuration object key -used in error messages.
5959
* @param valueString value from Configuration
6060
* @param enumClass class of enum
6161
* @param ignoreUnknown should unknown values be ignored?
6262
* @param <E> enum type
63-
* @return a mutable set of enum values listed in the valueString, with any unknown
64-
* matches stripped.
63+
* @return a mutable set of enum values parsed from the valueString, with any unknown
64+
* matches stripped if {@code ignoreUnknown} is true.
6565
* @throws IllegalArgumentException if one of the entries was unknown and ignoreUnknown is false,
6666
* or there are two entries in the enum which differ only by case.
6767
*/

hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/impl/TestFlagSet.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public final class TestFlagSet extends AbstractHadoopTestBase {
5858
*/
5959
private enum SimpleEnum { a, b, c }
6060

61+
/**
62+
* Enum with a single value.
63+
*/
64+
private enum OtherEnum { a }
65+
6166
/**
6267
* Test that an entry can be enabled and disabled.
6368
*/
@@ -312,6 +317,50 @@ public void testInequality() {
312317
.isNotEqualTo(s2);
313318
}
314319

320+
@Test
321+
public void testClassInequality() {
322+
final FlagSet<?> s1 =
323+
createFlagSet(SimpleEnum.class, KEYDOT, noneOf(SimpleEnum.class));
324+
final FlagSet<?> s2 =
325+
createFlagSet(OtherEnum.class, KEYDOT, OtherEnum.a);
326+
Assertions.assertThat(s1)
327+
.describedAs("s1 == s2")
328+
.isNotEqualTo(s2);
329+
}
330+
331+
/**
332+
* The copy operation creates a new instance which is now mutable,
333+
* even if the original was immutable.
334+
*/
335+
@Test
336+
public void testCopy() throws Throwable {
337+
FlagSet<SimpleEnum> s1 =
338+
createFlagSet(SimpleEnum.class, KEYDOT, SimpleEnum.a, SimpleEnum.b);
339+
s1.makeImmutable();
340+
FlagSet<SimpleEnum> s2 = s1.copy();
341+
Assertions.assertThat(s2)
342+
.describedAs("copy of %s", s1)
343+
.isNotSameAs(s1);
344+
Assertions.assertThat(!s2.isImmutable())
345+
.describedAs("set %s is immutable", s2)
346+
.isTrue();
347+
Assertions.assertThat(s1)
348+
.describedAs("s1 == s2")
349+
.isEqualTo(s2);
350+
}
351+
352+
@Test
353+
public void testCreateNullEnumClass() throws Throwable {
354+
intercept(NullPointerException.class, () ->
355+
createFlagSet(null, KEYDOT, SimpleEnum.a));
356+
}
357+
358+
@Test
359+
public void testCreateNullPrefix() throws Throwable {
360+
intercept(NullPointerException.class, () ->
361+
createFlagSet(SimpleEnum.class, null, SimpleEnum.a));
362+
}
363+
315364
/**
316365
* Round trip a FlagSet.
317366
* @param flagset FlagSet to save to a configuration and retrieve.
@@ -354,7 +403,7 @@ private void assertFlags(final SimpleEnum... flags) {
354403
}
355404

356405
/**
357-
* Assert that a flagset contains an exclusive set of values.
406+
* Assert that a FlagSet contains an exclusive set of values.
358407
* @param flags flags which must be set.
359408
*/
360409
private void assertFlagSetMatches(

hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestConfigurationHelper.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,25 @@ private Configuration confWithKey(String value) {
8282
}
8383

8484
@Test
85-
public void testEnumParseAll() throws Throwable {
85+
public void testEnumParseAll() {
8686
assertEnumParse("*", SimpleEnum.class, false)
8787
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c, SimpleEnum.i);
8888
}
8989

9090
@Test
91-
public void testEnumParse() throws Throwable {
91+
public void testEnumParse() {
9292
assertEnumParse("a, b,c", SimpleEnum.class, false)
9393
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c);
9494
}
9595

9696
@Test
97-
public void testEnumCaseIndependence() throws Throwable {
97+
public void testEnumCaseIndependence() {
9898
assertEnumParse("A, B, C, I", SimpleEnum.class, false)
9999
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c, SimpleEnum.i);
100100
}
101101

102102
@Test
103-
public void testEmptyArguments() throws Throwable {
103+
public void testEmptyArguments() {
104104
assertEnumParse(" ", SimpleEnum.class, false)
105105
.isEmpty();
106106
}
@@ -119,25 +119,19 @@ public void testUnknownEnumNotIgnoredThroughConf() throws Throwable {
119119
}
120120

121121
@Test
122-
public void testUnknownEnumIgnored() throws Throwable {
122+
public void testUnknownEnumIgnored() {
123123
assertEnumParse("c, d", SimpleEnum.class, true)
124124
.containsExactly(SimpleEnum.c);
125125
}
126126

127-
@Test
128-
public void testStarEnum() throws Throwable {
129-
assertEnumParse("*", SimpleEnum.class, false)
130-
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c, SimpleEnum.i);
131-
}
132-
133127
@Test
134128
public void testUnknownStarEnum() throws Throwable {
135129
intercept(IllegalArgumentException.class, "unrecognized", () ->
136130
parseEnumSet("key", "*, unrecognized", SimpleEnum.class, false));
137131
}
138132

139133
@Test
140-
public void testUnknownStarEnumIgnored() throws Throwable {
134+
public void testUnknownStarEnumIgnored() {
141135
assertEnumParse("*, d", SimpleEnum.class, true)
142136
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c, SimpleEnum.i);
143137
}
@@ -157,7 +151,7 @@ public void testCaseConflictingEnumNotSupported() throws Throwable {
157151
}
158152

159153
@Test
160-
public void testEmptyEnumMap() throws Throwable {
154+
public void testEmptyEnumMap() {
161155
Assertions.assertThat(mapEnumNamesToValues("", EmptyEnum.class))
162156
.isEmpty();
163157
}
@@ -166,8 +160,15 @@ public void testEmptyEnumMap() throws Throwable {
166160
* A star enum for an empty enum must be empty.
167161
*/
168162
@Test
169-
public void testEmptyStarEnum() throws Throwable {
163+
public void testEmptyStarEnum() {
170164
assertEnumParse("*", EmptyEnum.class, false)
171165
.isEmpty();
172166
}
167+
168+
@Test
169+
public void testDuplicateValues() {
170+
assertEnumParse("a, a, c, b, c", SimpleEnum.class, true)
171+
.containsExactly(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c);
172+
}
173+
173174
}

0 commit comments

Comments
 (0)