View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.EnumSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.function.Function;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  
30  /**
31   * Utility library to provide helper methods for Java enums.
32   *
33   * <p>#ThreadSafe#</p>
34   *
35   * @since 3.0
36   */
37  public class EnumUtils {
38  
39      private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits";
40      private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined.";
41      private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted";
42      private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type";
43  
44      /**
45       * Validate {@code enumClass}.
46       * @param <E> the type of the enumeration
47       * @param enumClass to check
48       * @return {@code enumClass}
49       * @throws NullPointerException if {@code enumClass} is {@code null}
50       * @throws IllegalArgumentException if {@code enumClass} is not an enum class
51       * @since 3.2
52       */
53      private static <E extends Enum<E>> Class<E> asEnum(final Class<E> enumClass) {
54          Objects.requireNonNull(enumClass, ENUM_CLASS_MUST_BE_DEFINED);
55          Validate.isTrue(enumClass.isEnum(), S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE, enumClass);
56          return enumClass;
57      }
58  
59      /**
60       * Validate that {@code enumClass} is compatible with representation in a {@code long}.
61       * @param <E> the type of the enumeration
62       * @param enumClass to check
63       * @return {@code enumClass}
64       * @throws NullPointerException if {@code enumClass} is {@code null}
65       * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
66       * @since 3.0.1
67       */
68      private static <E extends Enum<E>> Class<E> checkBitVectorable(final Class<E> enumClass) {
69          final E[] constants = asEnum(enumClass).getEnumConstants();
70          Validate.isTrue(constants.length <= Long.SIZE, CANNOT_STORE_S_S_VALUES_IN_S_BITS,
71              Integer.valueOf(constants.length), enumClass.getSimpleName(), Integer.valueOf(Long.SIZE));
72  
73          return enumClass;
74      }
75  
76      /**
77       * Creates a long bit vector representation of the given array of Enum values.
78       *
79       * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
80       *
81       * <p>Do not use this method if you have more than 64 values in your Enum, as this
82       * would create a value greater than a long can hold.</p>
83       *
84       * @param enumClass the class of the enum we are working with, not {@code null}
85       * @param values    the values we want to convert, not {@code null}
86       * @param <E>       the type of the enumeration
87       * @return a long whose value provides a binary representation of the given set of enum values.
88       * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
89       * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
90       * @since 3.0.1
91       * @see #generateBitVectors(Class, Iterable)
92       */
93      @SafeVarargs
94      public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final E... values) {
95          Validate.noNullElements(values);
96          return generateBitVector(enumClass, Arrays.asList(values));
97      }
98  
99      /**
100      * Creates a long bit vector representation of the given subset of an Enum.
101      *
102      * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
103      *
104      * <p>Do not use this method if you have more than 64 values in your Enum, as this
105      * would create a value greater than a long can hold.</p>
106      *
107      * @param enumClass the class of the enum we are working with, not {@code null}
108      * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
109      * @param <E>       the type of the enumeration
110      * @return a long whose value provides a binary representation of the given set of enum values.
111      * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
112      * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values,
113      *                                  or if any {@code values} {@code null}
114      * @since 3.0.1
115      * @see #generateBitVectors(Class, Iterable)
116      */
117     public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final Iterable<? extends E> values) {
118         checkBitVectorable(enumClass);
119         Objects.requireNonNull(values, "values");
120         long total = 0;
121         for (final E constant : values) {
122             Objects.requireNonNull(constant, NULL_ELEMENTS_NOT_PERMITTED);
123             total |= 1L << constant.ordinal();
124         }
125         return total;
126     }
127 
128     /**
129      * Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.
130      *
131      * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
132      *
133      * <p>Use this method if you have more than 64 values in your Enum.</p>
134      *
135      * @param enumClass the class of the enum we are working with, not {@code null}
136      * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
137      * @param <E>       the type of the enumeration
138      * @return a long[] whose values provide a binary representation of the given set of enum values
139      *         with the least significant digits rightmost.
140      * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
141      * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
142      * @since 3.2
143      */
144     @SafeVarargs
145     public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final E... values) {
146         asEnum(enumClass);
147         Validate.noNullElements(values);
148         final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
149         Collections.addAll(condensed, values);
150         final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
151         for (final E value : condensed) {
152             result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
153         }
154         ArrayUtils.reverse(result);
155         return result;
156     }
157 
158     /**
159      * Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.
160      *
161      * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
162      *
163      * <p>Use this method if you have more than 64 values in your Enum.</p>
164      *
165      * @param enumClass the class of the enum we are working with, not {@code null}
166      * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
167      * @param <E>       the type of the enumeration
168      * @return a long[] whose values provide a binary representation of the given set of enum values
169      *         with the least significant digits rightmost.
170      * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
171      * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
172      * @since 3.2
173      */
174     public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final Iterable<? extends E> values) {
175         asEnum(enumClass);
176         Objects.requireNonNull(values, "values");
177         final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
178         values.forEach(constant -> condensed.add(Objects.requireNonNull(constant, NULL_ELEMENTS_NOT_PERMITTED)));
179         final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
180         for (final E value : condensed) {
181             result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
182         }
183         ArrayUtils.reverse(result);
184         return result;
185     }
186 
187     /**
188      * Gets the enum for the class, returning {@code null} if not found.
189      *
190      * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
191      * for an invalid enum name.</p>
192      *
193      * @param <E> the type of the enumeration
194      * @param enumClass  the class of the enum to query, not null
195      * @param enumName   the enum name, null returns null
196      * @return the enum, null if not found
197      */
198     public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName) {
199         return getEnum(enumClass, enumName, null);
200     }
201 
202     /**
203      * Gets the enum for the class, returning {@code defaultEnum} if not found.
204      *
205      * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
206      * for an invalid enum name.</p>
207      *
208      * @param <E> the type of the enumeration
209      * @param enumClass   the class of the enum to query, not null
210      * @param enumName    the enum name, null returns default enum
211      * @param defaultEnum the default enum
212      * @return the enum, default enum if not found
213      * @since 3.10
214      */
215     public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName, final E defaultEnum) {
216         if (enumName == null) {
217             return defaultEnum;
218         }
219         try {
220             return Enum.valueOf(enumClass, enumName);
221         } catch (final IllegalArgumentException ex) {
222             return defaultEnum;
223         }
224     }
225 
226     /**
227      * Gets the enum for the class, returning {@code null} if not found.
228      *
229      * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
230      * for an invalid enum name and performs case insensitive matching of the name.</p>
231      *
232      * @param <E>         the type of the enumeration
233      * @param enumClass   the class of the enum to query, not null
234      * @param enumName    the enum name, null returns null
235      * @return the enum, null if not found
236      * @since 3.8
237      */
238     public static <E extends Enum<E>> E getEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
239         return getEnumIgnoreCase(enumClass, enumName, null);
240     }
241 
242     /**
243      * Gets the enum for the class, returning {@code defaultEnum} if not found.
244      *
245      * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
246      * for an invalid enum name and performs case insensitive matching of the name.</p>
247      *
248      * @param <E>         the type of the enumeration
249      * @param enumClass   the class of the enum to query, not null
250      * @param enumName    the enum name, null returns default enum
251      * @param defaultEnum the default enum
252      * @return the enum, default enum if not found
253      * @since 3.10
254      */
255     public static <E extends Enum<E>> E getEnumIgnoreCase(final Class<E> enumClass, final String enumName,
256         final E defaultEnum) {
257         return getFirstEnumIgnoreCase(enumClass, enumName, Enum::name, defaultEnum);
258     }
259 
260     /**
261      * Gets the {@link List} of enums.
262      *
263      * <p>This method is useful when you need a list of enums rather than an array.</p>
264      *
265      * @param <E> the type of the enumeration
266      * @param enumClass  the class of the enum to query, not null
267      * @return the modifiable list of enums, never null
268      */
269     public static <E extends Enum<E>> List<E> getEnumList(final Class<E> enumClass) {
270         return new ArrayList<>(Arrays.asList(enumClass.getEnumConstants()));
271     }
272 
273     /**
274      * Gets the {@link Map} of enums by name.
275      *
276      * <p>This method is useful when you need a map of enums by name.</p>
277      *
278      * @param <E> the type of the enumeration
279      * @param enumClass  the class of the enum to query, not null
280      * @return the modifiable map of enum names to enums, never null
281      */
282     public static <E extends Enum<E>> Map<String, E> getEnumMap(final Class<E> enumClass) {
283         return getEnumMap(enumClass, E::name);
284     }
285 
286     /**
287      * Gets the {@link Map} of enums by name.
288      *
289      * <p>
290      * This method is useful when you need a map of enums by name.
291      * </p>
292      *
293      * @param <E>         the type of enumeration
294      * @param <K>         the type of the map key
295      * @param enumClass   the class of the enum to query, not null
296      * @param keyFunction the function to query for the key, not null
297      * @return the modifiable map of enums, never null
298      * @since 3.13.0
299      */
300     public static <E extends Enum<E>, K> Map<K, E> getEnumMap(final Class<E> enumClass, final Function<E, K> keyFunction) {
301         return Stream.of(enumClass.getEnumConstants()).collect(Collectors.toMap(keyFunction::apply, Function.identity()));
302     }
303 
304     /**
305      * Gets the enum for the class in a system property, returning {@code defaultEnum} if not found.
306      *
307      * <p>
308      * This method differs from {@link Enum#valueOf} in that it does not throw an exception for an invalid enum name.
309      * </p>
310      *
311      * @param <E> the type of the enumeration
312      * @param enumClass the class of the enum to query, not null
313      * @param propName the system property key for the enum name, null returns default enum
314      * @param defaultEnum the default enum
315      * @return the enum, default enum if not found
316      * @since 3.13.0
317      */
318     public static <E extends Enum<E>> E getEnumSystemProperty(final Class<E> enumClass, final String propName,
319         final E defaultEnum) {
320         return enumClass == null || propName == null ? defaultEnum
321             : getEnum(enumClass, System.getProperty(propName), defaultEnum);
322     }
323 
324     /**
325      * Gets the enum for the class, returning {@code defaultEnum} if not found.
326      *
327      * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
328      * for an invalid enum name and performs case insensitive matching of the name.</p>
329      *
330      * @param <E>         the type of the enumeration
331      * @param enumClass   the class of the enum to query, not null
332      * @param enumName    the enum name, null returns default enum
333      * @param stringFunction the function that gets the string for an enum for comparison to {@code enumName}.
334      * @param defaultEnum the default enum
335      * @return the enum, default enum if not found
336      * @since 3.13.0
337      */
338     public static <E extends Enum<E>> E getFirstEnumIgnoreCase(final Class<E> enumClass, final String enumName, final Function<E, String> stringFunction,
339         final E defaultEnum) {
340         if (enumName == null || !enumClass.isEnum()) {
341             return defaultEnum;
342         }
343         return Stream.of(enumClass.getEnumConstants()).filter(e -> enumName.equalsIgnoreCase(stringFunction.apply(e))).findFirst().orElse(defaultEnum);
344     }
345 
346     /**
347      * Checks if the specified name is a valid enum for the class.
348      *
349      * <p>This method differs from {@link Enum#valueOf} in that it checks if the name is
350      * a valid enum without needing to catch the exception.</p>
351      *
352      * @param <E> the type of the enumeration
353      * @param enumClass  the class of the enum to query, not null
354      * @param enumName   the enum name, null returns false
355      * @return true if the enum name is valid, otherwise false
356      */
357     public static <E extends Enum<E>> boolean isValidEnum(final Class<E> enumClass, final String enumName) {
358         return getEnum(enumClass, enumName) != null;
359     }
360 
361     /**
362      * Checks if the specified name is a valid enum for the class.
363      *
364      * <p>This method differs from {@link Enum#valueOf} in that it checks if the name is
365      * a valid enum without needing to catch the exception
366      * and performs case insensitive matching of the name.</p>
367      *
368      * @param <E> the type of the enumeration
369      * @param enumClass  the class of the enum to query, not null
370      * @param enumName   the enum name, null returns false
371      * @return true if the enum name is valid, otherwise false
372      * @since 3.8
373      */
374     public static <E extends Enum<E>> boolean isValidEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
375         return getEnumIgnoreCase(enumClass, enumName) != null;
376     }
377 
378     /**
379      * Convert a long value created by {@link EnumUtils#generateBitVector} into the set of
380      * enum values that it represents.
381      *
382      * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
383      * @param enumClass the class of the enum we are working with, not {@code null}
384      * @param value     the long value representation of a set of enum values
385      * @param <E>       the type of the enumeration
386      * @return a set of enum values
387      * @throws NullPointerException if {@code enumClass} is {@code null}
388      * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
389      * @since 3.0.1
390      */
391     public static <E extends Enum<E>> EnumSet<E> processBitVector(final Class<E> enumClass, final long value) {
392         checkBitVectorable(enumClass).getEnumConstants();
393         return processBitVectors(enumClass, value);
394     }
395 
396     /**
397      * Convert a {@code long[]} created by {@link EnumUtils#generateBitVectors} into the set of
398      * enum values that it represents.
399      *
400      * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
401      * @param enumClass the class of the enum we are working with, not {@code null}
402      * @param values     the long[] bearing the representation of a set of enum values, the least significant digits rightmost, not {@code null}
403      * @param <E>       the type of the enumeration
404      * @return a set of enum values
405      * @throws NullPointerException if {@code enumClass} is {@code null}
406      * @throws IllegalArgumentException if {@code enumClass} is not an enum class
407      * @since 3.2
408      */
409     public static <E extends Enum<E>> EnumSet<E> processBitVectors(final Class<E> enumClass, final long... values) {
410         final EnumSet<E> results = EnumSet.noneOf(asEnum(enumClass));
411         final long[] lvalues = ArrayUtils.clone(Objects.requireNonNull(values, "values"));
412         ArrayUtils.reverse(lvalues);
413         for (final E constant : enumClass.getEnumConstants()) {
414             final int block = constant.ordinal() / Long.SIZE;
415             if (block < lvalues.length && (lvalues[block] & 1L << (constant.ordinal() % Long.SIZE)) != 0) {
416                 results.add(constant);
417             }
418         }
419         return results;
420     }
421 
422     /**
423      * This constructor is public to permit tools that require a JavaBean
424      * instance to operate.
425      */
426     public EnumUtils() {
427     }
428 }