    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3;
    018
    019import java.util.ArrayList;
    020import java.util.Arrays;
    021import java.util.Collections;
    022import java.util.EnumSet;
    023import java.util.LinkedHashMap;
    024import java.util.List;
    025import java.util.Map;
    026
    027/**
    028 * <p>Utility library to provide helper methods for Java enums.</p>
    029 *
    030 * <p>#ThreadSafe#</p>
    031 *
    032 * @since 3.0
    033 */
    034public class EnumUtils {
    035
    036    private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted";
    037    private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits";
    038    private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type";
    039    private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined.";
    040
    041    /**
    042     * This constructor is public to permit tools that require a JavaBean
    043     * instance to operate.
    044     */
    045    public EnumUtils() {
    046    }
    047
    048    /**
    049     * <p>Gets the {@code Map} of enums by name.</p>
    050     *
    051     * <p>This method is useful when you need a map of enums by name.</p>
    052     *
    053     * @param <E> the type of the enumeration
    054     * @param enumClass  the class of the enum to query, not null
    055     * @return the modifiable map of enum names to enums, never null
    056     */
    057    public static <E extends Enum<E>> Map<String, E> getEnumMap(final Class<E> enumClass) {
    058        final Map<String, E> map = new LinkedHashMap<>();
    059        for (final E e: enumClass.getEnumConstants()) {
    060            map.put(e.name(), e);
    061        }
    062        return map;
    063    }
    064
    065    /**
    066     * <p>Gets the {@code List} of enums.</p>
    067     *
    068     * <p>This method is useful when you need a list of enums rather than an array.</p>
    069     *
    070     * @param <E> the type of the enumeration
    071     * @param enumClass  the class of the enum to query, not null
    072     * @return the modifiable list of enums, never null
    073     */
    074    public static <E extends Enum<E>> List<E> getEnumList(final Class<E> enumClass) {
    075        return new ArrayList<>(Arrays.asList(enumClass.getEnumConstants()));
    076    }
    077
    078    /**
    079     * <p>Checks if the specified name is a valid enum for the class.</p>
    080     *
    081     * <p>This method differs from {@link Enum#valueOf} in that checks if the name is
    082     * a valid enum without needing to catch the exception.</p>
    083     *
    084     * @param <E> the type of the enumeration
    085     * @param enumClass  the class of the enum to query, not null
    086     * @param enumName   the enum name, null returns false
    087     * @return true if the enum name is valid, otherwise false
    088     */
    089    public static <E extends Enum<E>> boolean isValidEnum(final Class<E> enumClass, final String enumName) {
    090        return getEnum(enumClass, enumName) != null;
    091    }
    092
    093    /**
    094     * <p>Checks if the specified name is a valid enum for the class.</p>
    095     *
    096     * <p>This method differs from {@link Enum#valueOf} in that checks if the name is
    097     * a valid enum without needing to catch the exception
    098     * and performs case insensitive matching of the name.</p>
    099     *
    100     * @param <E> the type of the enumeration
    101     * @param enumClass  the class of the enum to query, not null
    102     * @param enumName   the enum name, null returns false
    103     * @return true if the enum name is valid, otherwise false
    104     * @since 3.8
    105     */
    106    public static <E extends Enum<E>> boolean isValidEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
    107        return getEnumIgnoreCase(enumClass, enumName) != null;
    108    }
    109
    110    /**
    111     * <p>Gets the enum for the class, returning {@code null} if not found.</p>
    112     *
    113     * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
    114     * for an invalid enum name.</p>
    115     *
    116     * @param <E> the type of the enumeration
    117     * @param enumClass  the class of the enum to query, not null
    118     * @param enumName   the enum name, null returns null
    119     * @return the enum, null if not found
    120     */
    121    public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName) {
    122        if (enumName == null) {
    123            return null;
    124        }
    125        try {
    126            return Enum.valueOf(enumClass, enumName);
    127        } catch (final IllegalArgumentException ex) {
    128            return null;
    129        }
    130    }
    131
    132    /**
    133     * <p>Gets the enum for the class, returning {@code null} if not found.</p>
    134     *
    135     * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
    136     * for an invalid enum name and performs case insensitive matching of the name.</p>
    137     *
    138     * @param <E>         the type of the enumeration
    139     * @param enumClass   the class of the enum to query, not null
    140     * @param enumName    the enum name, null returns null
    141     * @return the enum, null if not found
    142     * @since 3.8
    143     */
    144    public static <E extends Enum<E>> E getEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
    145        if (enumName == null || !enumClass.isEnum()) {
    146            return null;
    147        }
    148        for (final E each : enumClass.getEnumConstants()) {
    149            if (each.name().equalsIgnoreCase(enumName)) {
    150                return each;
    151            }
    152        }
    153        return null;
    154    }
    155
    156    /**
    157     * <p>Creates a long bit vector representation of the given subset of an Enum.</p>
    158     *
    159     * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
    160     *
    161     * <p>Do not use this method if you have more than 64 values in your Enum, as this
    162     * would create a value greater than a long can hold.</p>
    163     *
    164     * @param enumClass the class of the enum we are working with, not {@code null}
    165     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
    166     * @param <E>       the type of the enumeration
    167     * @return a long whose value provides a binary representation of the given set of enum values.
    168     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
    169     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values,
    170     *                                  or if any {@code values} {@code null}
    171     * @since 3.0.1
    172     * @see #generateBitVectors(Class, Iterable)
    173     */
    174    public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final Iterable<? extends E> values) {
    175        checkBitVectorable(enumClass);
    176        Validate.notNull(values);
    177        long total = 0;
    178        for (final E constant : values) {
    179            Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED);
    180            total |= 1L << constant.ordinal();
    181        }
    182        return total;
    183    }
    184
    185    /**
    186     * <p>Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.</p>
    187     *
    188     * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
    189     *
    190     * <p>Use this method if you have more than 64 values in your Enum.</p>
    191     *
    192     * @param enumClass the class of the enum we are working with, not {@code null}
    193     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
    194     * @param <E>       the type of the enumeration
    195     * @return a long[] whose values provide a binary representation of the given set of enum values
    196     *         with least significant digits rightmost.
    197     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
    198     * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
    199     * @since 3.2
    200     */
    201    public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final Iterable<? extends E> values) {
    202        asEnum(enumClass);
    203        Validate.notNull(values);
    204        final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
    205        for (final E constant : values) {
    206            Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED);
    207            condensed.add(constant);
    208        }
    209        final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
    210        for (final E value : condensed) {
    211            result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
    212        }
    213        ArrayUtils.reverse(result);
    214        return result;
    215    }
    216
    217    /**
    218     * <p>Creates a long bit vector representation of the given array of Enum values.</p>
    219     *
    220     * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
    221     *
    222     * <p>Do not use this method if you have more than 64 values in your Enum, as this
    223     * would create a value greater than a long can hold.</p>
    224     *
    225     * @param enumClass the class of the enum we are working with, not {@code null}
    226     * @param values    the values we want to convert, not {@code null}
    227     * @param <E>       the type of the enumeration
    228     * @return a long whose value provides a binary representation of the given set of enum values.
    229     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
    230     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
    231     * @since 3.0.1
    232     * @see #generateBitVectors(Class, Iterable)
    233     */
    234    @SafeVarargs
    235    public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final E... values) {
    236        Validate.noNullElements(values);
    237        return generateBitVector(enumClass, Arrays.asList(values));
    238    }
    239
    240    /**
    241     * <p>Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.</p>
    242     *
    243     * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
    244     *
    245     * <p>Use this method if you have more than 64 values in your Enum.</p>
    246     *
    247     * @param enumClass the class of the enum we are working with, not {@code null}
    248     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
    249     * @param <E>       the type of the enumeration
    250     * @return a long[] whose values provide a binary representation of the given set of enum values
    251     *         with least significant digits rightmost.
    252     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
    253     * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
    254     * @since 3.2
    255     */
    256    @SafeVarargs
    257    public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final E... values) {
    258        asEnum(enumClass);
    259        Validate.noNullElements(values);
    260        final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
    261        Collections.addAll(condensed, values);
    262        final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
    263        for (final E value : condensed) {
    264            result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
    265        }
    266        ArrayUtils.reverse(result);
    267        return result;
    268    }
    269
    270    /**
    271     * <p>Convert a long value created by {@link EnumUtils#generateBitVector} into the set of
    272     * enum values that it represents.</p>
    273     *
    274     * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
    275     * @param enumClass the class of the enum we are working with, not {@code null}
    276     * @param value     the long value representation of a set of enum values
    277     * @param <E>       the type of the enumeration
    278     * @return a set of enum values
    279     * @throws NullPointerException if {@code enumClass} is {@code null}
    280     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
    281     * @since 3.0.1
    282     */
    283    public static <E extends Enum<E>> EnumSet<E> processBitVector(final Class<E> enumClass, final long value) {
    284        checkBitVectorable(enumClass).getEnumConstants();
    285        return processBitVectors(enumClass, value);
    286    }
    287
    288    /**
    289     * <p>Convert a {@code long[]} created by {@link EnumUtils#generateBitVectors} into the set of
    290     * enum values that it represents.</p>
    291     *
    292     * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
    293     * @param enumClass the class of the enum we are working with, not {@code null}
    294     * @param values     the long[] bearing the representation of a set of enum values, least significant digits rightmost, not {@code null}
    295     * @param <E>       the type of the enumeration
    296     * @return a set of enum values
    297     * @throws NullPointerException if {@code enumClass} is {@code null}
    298     * @throws IllegalArgumentException if {@code enumClass} is not an enum class
    299     * @since 3.2
    300     */
    301    public static <E extends Enum<E>> EnumSet<E> processBitVectors(final Class<E> enumClass, final long... values) {
    302        final EnumSet<E> results = EnumSet.noneOf(asEnum(enumClass));
    303        final long[] lvalues = ArrayUtils.clone(Validate.notNull(values));
    304        ArrayUtils.reverse(lvalues);
    305        for (final E constant : enumClass.getEnumConstants()) {
    306            final int block = constant.ordinal() / Long.SIZE;
    307            if (block < lvalues.length && (lvalues[block] & 1L << (constant.ordinal() % Long.SIZE)) != 0) {
    308                results.add(constant);
    309            }
    310        }
    311        return results;
    312    }
    313
    314    /**
    315     * Validate that {@code enumClass} is compatible with representation in a {@code long}.
    316     * @param <E> the type of the enumeration
    317     * @param enumClass to check
    318     * @return {@code enumClass}
    319     * @throws NullPointerException if {@code enumClass} is {@code null}
    320     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
    321     * @since 3.0.1
    322     */
    323    private static <E extends Enum<E>> Class<E> checkBitVectorable(final Class<E> enumClass) {
    324        final E[] constants = asEnum(enumClass).getEnumConstants();
    325        Validate.isTrue(constants.length <= Long.SIZE, CANNOT_STORE_S_S_VALUES_IN_S_BITS,
    326            Integer.valueOf(constants.length), enumClass.getSimpleName(), Integer.valueOf(Long.SIZE));
    327
    328        return enumClass;
    329    }
    330
    331    /**
    332     * Validate {@code enumClass}.
    333     * @param <E> the type of the enumeration
    334     * @param enumClass to check
    335     * @return {@code enumClass}
    336     * @throws NullPointerException if {@code enumClass} is {@code null}
    337     * @throws IllegalArgumentException if {@code enumClass} is not an enum class
    338     * @since 3.2
    339     */
    340    private static <E extends Enum<E>> Class<E> asEnum(final Class<E> enumClass) {
    341        Validate.notNull(enumClass, ENUM_CLASS_MUST_BE_DEFINED);
    342        Validate.isTrue(enumClass.isEnum(), S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE, enumClass);
    343        return enumClass;
    344    }
    345}