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 * @version $Id: EnumUtils.java 1557159 2014-01-10 15:41:57Z mbenson $
034 */
035public class EnumUtils {
036
037    private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted";
038    private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits";
039    private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type";
040    private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined.";
041
042    /**
043     * This constructor is public to permit tools that require a JavaBean
044     * instance to operate.
045     */
046    public EnumUtils() {
047    }
048
049    /**
050     * <p>Gets the {@code Map} of enums by name.</p>
051     *
052     * <p>This method is useful when you need a map of enums by name.</p>
053     *
054     * @param <E> the type of the enumeration
055     * @param enumClass  the class of the enum to query, not null
056     * @return the modifiable map of enum names to enums, never null
057     */
058    public static <E extends Enum<E>> Map<String, E> getEnumMap(final Class<E> enumClass) {
059        final Map<String, E> map = new LinkedHashMap<String, E>();
060        for (final E e: enumClass.getEnumConstants()) {
061            map.put(e.name(), e);
062        }
063        return map;
064    }
065
066    /**
067     * <p>Gets the {@code List} of enums.</p>
068     *
069     * <p>This method is useful when you need a list of enums rather than an array.</p>
070     *
071     * @param <E> the type of the enumeration
072     * @param enumClass  the class of the enum to query, not null
073     * @return the modifiable list of enums, never null
074     */
075    public static <E extends Enum<E>> List<E> getEnumList(final Class<E> enumClass) {
076        return new ArrayList<E>(Arrays.asList(enumClass.getEnumConstants()));
077    }
078
079    /**
080     * <p>Checks if the specified name is a valid enum for the class.</p>
081     *
082     * <p>This method differs from {@link Enum#valueOf} in that checks if the name is
083     * a valid enum without needing to catch the exception.</p>
084     *
085     * @param <E> the type of the enumeration
086     * @param enumClass  the class of the enum to query, not null
087     * @param enumName   the enum name, null returns false
088     * @return true if the enum name is valid, otherwise false
089     */
090    public static <E extends Enum<E>> boolean isValidEnum(final Class<E> enumClass, final String enumName) {
091        if (enumName == null) {
092            return false;
093        }
094        try {
095            Enum.valueOf(enumClass, enumName);
096            return true;
097        } catch (final IllegalArgumentException ex) {
098            return false;
099        }
100    }
101
102    /**
103     * <p>Gets the enum for the class, returning {@code null} if not found.</p>
104     *
105     * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
106     * for an invalid enum name.</p>
107     *
108     * @param <E> the type of the enumeration
109     * @param enumClass  the class of the enum to query, not null
110     * @param enumName   the enum name, null returns null
111     * @return the enum, null if not found
112     */
113    public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName) {
114        if (enumName == null) {
115            return null;
116        }
117        try {
118            return Enum.valueOf(enumClass, enumName);
119        } catch (final IllegalArgumentException ex) {
120            return null;
121        }
122    }
123
124    /**
125     * <p>Creates a long bit vector representation of the given subset of an Enum.</p>
126     *
127     * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
128     *
129     * <p>Do not use this method if you have more than 64 values in your Enum, as this
130     * would create a value greater than a long can hold.</p>
131     *
132     * @param enumClass the class of the enum we are working with, not {@code null}
133     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
134     * @param <E>       the type of the enumeration
135     * @return a long whose value provides a binary representation of the given set of enum values.
136     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
137     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values,
138     *                                  or if any {@code values} {@code null}
139     * @since 3.0.1
140     * @see #generateBitVectors(Class, Iterable)
141     */
142    public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final Iterable<? extends E> values) {
143        checkBitVectorable(enumClass);
144        Validate.notNull(values);
145        long total = 0;
146        for (final E constant : values) {
147            Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED);
148            total |= 1 << constant.ordinal();
149        }
150        return total;
151    }
152
153    /**
154     * <p>Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.</p>
155     *
156     * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
157     *
158     * <p>Use this method if you have more than 64 values in your Enum.</p>
159     *
160     * @param enumClass the class of the enum we are working with, not {@code null}
161     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
162     * @param <E>       the type of the enumeration
163     * @return a long[] whose values provide a binary representation of the given set of enum values
164     *         with least significant digits rightmost.
165     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
166     * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
167     * @since 3.2
168     */
169    public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final Iterable<? extends E> values) {
170        asEnum(enumClass);
171        Validate.notNull(values);
172        final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
173        for (final E constant : values) {
174            Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED);
175            condensed.add(constant);
176        }
177        final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
178        for (final E value : condensed) {
179            result[value.ordinal() / Long.SIZE] |= 1 << (value.ordinal() % Long.SIZE);
180        }
181        ArrayUtils.reverse(result);
182        return result;
183    }
184
185    /**
186     * <p>Creates a long bit vector representation of the given array of Enum values.</p>
187     *
188     * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
189     *
190     * <p>Do not use this method if you have more than 64 values in your Enum, as this
191     * would create a value greater than a long can hold.</p>
192     *
193     * @param enumClass the class of the enum we are working with, not {@code null}
194     * @param values    the values we want to convert, not {@code null}
195     * @param <E>       the type of the enumeration
196     * @return a long whose value provides a binary representation of the given set of enum values.
197     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
198     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
199     * @since 3.0.1
200     * @see #generateBitVectors(Class, Iterable)
201     */
202    public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final E... values) {
203        Validate.noNullElements(values);
204        return generateBitVector(enumClass, Arrays.<E> asList(values));
205    }
206
207    /**
208     * <p>Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.</p>
209     *
210     * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
211     *
212     * <p>Use this method if you have more than 64 values in your Enum.</p>
213     *
214     * @param enumClass the class of the enum we are working with, not {@code null}
215     * @param values    the values we want to convert, not {@code null}, neither containing {@code null}
216     * @param <E>       the type of the enumeration
217     * @return a long[] whose values provide a binary representation of the given set of enum values
218     *         with least significant digits rightmost.
219     * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
220     * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
221     * @since 3.2
222     */
223    public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final E... values) {
224        asEnum(enumClass);
225        Validate.noNullElements(values);
226        final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
227        Collections.addAll(condensed, values);
228        final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
229        for (final E value : condensed) {
230            result[value.ordinal() / Long.SIZE] |= 1 << (value.ordinal() % Long.SIZE);
231        }
232        ArrayUtils.reverse(result);
233        return result;
234    }
235
236    /**
237     * <p>Convert a long value created by {@link EnumUtils#generateBitVector} into the set of
238     * enum values that it represents.</p>
239     *
240     * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
241     * @param enumClass the class of the enum we are working with, not {@code null}
242     * @param value     the long value representation of a set of enum values
243     * @param <E>       the type of the enumeration
244     * @return a set of enum values
245     * @throws NullPointerException if {@code enumClass} is {@code null}
246     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
247     * @since 3.0.1
248     */
249    public static <E extends Enum<E>> EnumSet<E> processBitVector(final Class<E> enumClass, final long value) {
250        checkBitVectorable(enumClass).getEnumConstants();
251        return processBitVectors(enumClass, value);
252    }
253
254    /**
255     * <p>Convert a {@code long[]} created by {@link EnumUtils#generateBitVectors} into the set of
256     * enum values that it represents.</p>
257     *
258     * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
259     * @param enumClass the class of the enum we are working with, not {@code null}
260     * @param values     the long[] bearing the representation of a set of enum values, least significant digits rightmost, not {@code null}
261     * @param <E>       the type of the enumeration
262     * @return a set of enum values
263     * @throws NullPointerException if {@code enumClass} is {@code null}
264     * @throws IllegalArgumentException if {@code enumClass} is not an enum class
265     * @since 3.2
266     */
267    public static <E extends Enum<E>> EnumSet<E> processBitVectors(final Class<E> enumClass, long... values) {
268        final EnumSet<E> results = EnumSet.noneOf(asEnum(enumClass));
269        values = ArrayUtils.clone(Validate.notNull(values));
270        ArrayUtils.reverse(values);
271        for (final E constant : enumClass.getEnumConstants()) {
272            final int block = constant.ordinal() / Long.SIZE;
273            if (block < values.length && (values[block] & 1 << (constant.ordinal() % Long.SIZE)) != 0) {
274                results.add(constant);
275            }
276        }
277        return results;
278    }
279
280    /**
281     * Validate that {@code enumClass} is compatible with representation in a {@code long}.
282     * @param <E> the type of the enumeration
283     * @param enumClass to check
284     * @return {@code enumClass}
285     * @throws NullPointerException if {@code enumClass} is {@code null}
286     * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
287     * @since 3.0.1
288     */
289    private static <E extends Enum<E>> Class<E> checkBitVectorable(final Class<E> enumClass) {
290        final E[] constants = asEnum(enumClass).getEnumConstants();
291        Validate.isTrue(constants.length <= Long.SIZE, CANNOT_STORE_S_S_VALUES_IN_S_BITS, constants.length,
292            enumClass.getSimpleName(), Long.SIZE);
293
294        return enumClass;
295    }
296
297    /**
298     * Validate {@code enumClass}.
299     * @param <E> the type of the enumeration
300     * @param enumClass to check
301     * @return {@code enumClass}
302     * @throws NullPointerException if {@code enumClass} is {@code null}
303     * @throws IllegalArgumentException if {@code enumClass} is not an enum class
304     * @since 3.2
305     */
306    private static <E extends Enum<E>> Class<E> asEnum(final Class<E> enumClass) {
307        Validate.notNull(enumClass, ENUM_CLASS_MUST_BE_DEFINED);
308        Validate.isTrue(enumClass.isEnum(), S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE, enumClass);
309        return enumClass;
310    }
311}