All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.commons.lang.EnumHelper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.commons.lang;

import java.util.function.Predicate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.CGlobal;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.CommonsHashMap;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsMap;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.id.IHasID;
import com.helger.commons.id.IHasIntID;
import com.helger.commons.name.IHasName;
import com.helger.commons.state.EChange;
import com.helger.commons.string.StringHelper;

/**
 * Some enum utility methods.
 *
 * @author Philip Helger
 */
@Immutable
public final class EnumHelper
{
  private static final Logger LOGGER = LoggerFactory.getLogger (EnumHelper.class);
  private static final Object [] NOT_CACHABLE = ArrayHelper.EMPTY_OBJECT_ARRAY;
  private static final SimpleReadWriteLock RW_LOCK_INTCACHE = new SimpleReadWriteLock ();
  @GuardedBy ("RW_LOCK_INTCACHE")
  private static final ICommonsMap  INT_CACHE = new CommonsHashMap <> ();

  @PresentForCodeCoverage
  private static final EnumHelper INSTANCE = new EnumHelper ();

  private EnumHelper ()
  {}

  @Nullable
  public static > ENUMTYPE findFirst (@Nonnull final Class  aClass,
                                                                       @Nullable final Predicate  aFilter)
  {
    return findFirst (aClass, aFilter, null);
  }

  @Nullable
  public static > ENUMTYPE findFirst (@Nonnull final Class  aClass,
                                                                       @Nullable final Predicate  aFilter,
                                                                       @Nullable final ENUMTYPE eDefault)
  {
    return ArrayHelper.findFirst (aClass.getEnumConstants (), aFilter, eDefault);
  }

  @Nonnull
  @ReturnsMutableCopy
  public static > ICommonsList  getAll (@Nonnull final Class  aClass,
                                                                                   @Nullable final Predicate  aFilter)
  {
    return CommonsArrayList.createFiltered (aClass.getEnumConstants (), aFilter);
  }

  /**
   * Get the enum value with the passed ID
   *
   * @param 
   *        The ID type
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param aID
   *        The ID to search
   * @return null if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasID > ENUMTYPE getFromIDOrNull (@Nonnull final Class  aClass,
                                                                                                         @Nullable final KEYTYPE aID)
  {
    return getFromIDOrDefault (aClass, aID, null);
  }

  /**
   * Get the enum value with the passed ID
   *
   * @param 
   *        The ID type
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param aID
   *        The ID to search
   * @param eDefault
   *        The default value to be returned, if the ID was not found.
   * @return The default parameter if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasID > ENUMTYPE getFromIDOrDefault (@Nonnull final Class  aClass,
                                                                                                            @Nullable final KEYTYPE aID,
                                                                                                            @Nullable final ENUMTYPE eDefault)
  {
    ValueEnforcer.notNull (aClass, "Class");

    if (aID == null)
      return eDefault;
    return findFirst (aClass, x -> x.getID ().equals (aID), eDefault);
  }

  /**
   * Get the enum value with the passed ID. If no such ID is present, an
   * {@link IllegalArgumentException} is thrown.
   *
   * @param 
   *        The ID type
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param aID
   *        The ID to search
   * @return The enum item with the given ID. Never null.
   * @throws IllegalArgumentException
   *         if no enum item with the given ID is present
   */
  @Nonnull
  public static  & IHasID > ENUMTYPE getFromIDOrThrow (@Nonnull final Class  aClass,
                                                                                                          @Nullable final KEYTYPE aID)
  {
    final ENUMTYPE aEnum = getFromIDOrNull (aClass, aID);
    if (aEnum == null)
      throw new IllegalArgumentException ("Failed to resolve ID " + aID + " within class " + aClass);
    return aEnum;
  }

  /**
   * Get the enum value with the passed string ID case insensitive
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sID
   *        The ID to search
   * @return null if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasID > ENUMTYPE getFromIDCaseInsensitiveOrNull (@Nonnull final Class  aClass,
                                                                                                              @Nullable final String sID)
  {
    return getFromIDCaseInsensitiveOrDefault (aClass, sID, null);
  }

  /**
   * Get the enum value with the passed string ID case insensitive
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sID
   *        The ID to search
   * @param eDefault
   *        The default value to be returned, if the ID was not found.
   * @return The default parameter if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasID > ENUMTYPE getFromIDCaseInsensitiveOrDefault (@Nonnull final Class  aClass,
                                                                                                                 @Nullable final String sID,
                                                                                                                 @Nullable final ENUMTYPE eDefault)
  {
    ValueEnforcer.notNull (aClass, "Class");

    if (sID == null)
      return eDefault;
    return findFirst (aClass, x -> x.getID ().equalsIgnoreCase (sID), eDefault);
  }

  /**
   * Get the enum value with the passed string ID (case insensitive). If no such
   * ID is present, an {@link IllegalArgumentException} is thrown.
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sID
   *        The ID to search
   * @return The enum item with the given ID. Never null.
   * @throws IllegalArgumentException
   *         if no enum item with the given ID is present
   */
  @Nonnull
  public static  & IHasID > ENUMTYPE getFromIDCaseInsensitiveOrThrow (@Nonnull final Class  aClass,
                                                                                                               @Nullable final String sID)
  {
    final ENUMTYPE aEnum = getFromIDCaseInsensitiveOrNull (aClass, sID);
    if (aEnum == null)
      throw new IllegalArgumentException ("Failed to resolve ID " + sID + " within class " + aClass);
    return aEnum;
  }

  /**
   * Get the enum value with the passed ID
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param nID
   *        The ID to search
   * @return null if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasIntID> ENUMTYPE getFromIDOrNull (@Nonnull final Class  aClass,
                                                                                         final int nID)
  {
    return getFromIDOrDefault (aClass, nID, null);
  }

  /**
   * Get the enum value with the passed ID
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param nID
   *        The ID to search
   * @param eDefault
   *        The default value to be returned, if the ID was not found.
   * @return The default parameter if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasIntID> ENUMTYPE getFromIDOrDefault (@Nonnull final Class  aClass,
                                                                                            final int nID,
                                                                                            @Nullable final ENUMTYPE eDefault)
  {
    ValueEnforcer.notNull (aClass, "Class");

    final String sCacheKey = aClass.getName ();
    Object [] aCachedData = RW_LOCK_INTCACHE.readLockedGet ( () -> INT_CACHE.get (sCacheKey));
    if (aCachedData == null)
    {
      aCachedData = RW_LOCK_INTCACHE.writeLockedGet ( () -> {
        // Try again in write lock
        Object [] aWLCachedData = INT_CACHE.get (sCacheKey);
        if (aWLCachedData == null)
        {
          // Create new cache entry
          int nMinID = Integer.MAX_VALUE;
          int nMaxID = Integer.MIN_VALUE;
          for (final ENUMTYPE aElement : aClass.getEnumConstants ())
          {
            final int nElementID = aElement.getID ();
            if (nElementID < nMinID)
              nMinID = nElementID;
            if (nElementID > nMaxID)
              nMaxID = nElementID;
          }
          if (nMinID >= 0 && nMaxID <= CGlobal.MAX_BYTE_VALUE)
          {
            // Cachable!
            aWLCachedData = new Object [nMaxID + 1];
            for (final ENUMTYPE aElement : aClass.getEnumConstants ())
              aWLCachedData[aElement.getID ()] = aElement;
          }
          else
          {
            // Enum not cachable - too many items
            aWLCachedData = NOT_CACHABLE;
          }
          INT_CACHE.put (sCacheKey, aWLCachedData);
        }
        return aWLCachedData;
      });
    }

    if (aCachedData != NOT_CACHABLE)
    {
      if (nID < 0 || nID >= aCachedData.length)
        return eDefault;
      return GenericReflection.uncheckedCast (aCachedData[nID]);
    }

    // Object is not cachable - traverse as usual
    return findFirst (aClass, x -> x.getID () == nID, eDefault);
  }

  /**
   * Get the enum value with the passed ID. If no such ID is present, an
   * {@link IllegalArgumentException} is thrown.
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param nID
   *        The ID to search
   * @return The enum item with the given ID. Never null.
   * @throws IllegalArgumentException
   *         if no enum item with the given ID is present
   */
  @Nonnull
  public static  & IHasIntID> ENUMTYPE getFromIDOrThrow (@Nonnull final Class  aClass,
                                                                                          final int nID)
  {
    final ENUMTYPE aEnum = getFromIDOrNull (aClass, nID);
    if (aEnum == null)
      throw new IllegalArgumentException ("Failed to resolve ID " + nID + " within class " + aClass);
    return aEnum;
  }

  /**
   * Get the enum value with the passed name
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @return null if no enum item with the given name is present.
   */
  @Nullable
  public static  & IHasName> ENUMTYPE getFromNameOrNull (@Nonnull final Class  aClass,
                                                                                          @Nullable final String sName)
  {
    return getFromNameOrDefault (aClass, sName, null);
  }

  /**
   * Get the enum value with the passed name
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @param eDefault
   *        The default value to be returned, if the name was not found.
   * @return The default parameter if no enum item with the given name is
   *         present.
   */
  @Nullable
  public static  & IHasName> ENUMTYPE getFromNameOrDefault (@Nonnull final Class  aClass,
                                                                                             @Nullable final String sName,
                                                                                             @Nullable final ENUMTYPE eDefault)
  {
    ValueEnforcer.notNull (aClass, "Class");

    if (StringHelper.hasNoText (sName))
      return eDefault;
    return findFirst (aClass, x -> x.getName ().equals (sName), eDefault);
  }

  /**
   * Get the enum value with the passed name. If no such name is present, an
   * {@link IllegalArgumentException} is thrown.
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @return The enum item with the given name. Never null.
   * @throws IllegalArgumentException
   *         if no enum item with the given name is present
   */
  @Nonnull
  public static  & IHasName> ENUMTYPE getFromNameOrThrow (@Nonnull final Class  aClass,
                                                                                           @Nullable final String sName)
  {
    final ENUMTYPE aEnum = getFromNameOrNull (aClass, sName);
    if (aEnum == null)
      throw new IllegalArgumentException ("Failed to resolve name " + sName + " within class " + aClass);
    return aEnum;
  }

  /**
   * Get the enum value with the passed name case insensitive
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @return null if no enum item with the given ID is present.
   */
  @Nullable
  public static  & IHasName> ENUMTYPE getFromNameCaseInsensitiveOrNull (@Nonnull final Class  aClass,
                                                                                                         @Nullable final String sName)
  {
    return getFromNameCaseInsensitiveOrDefault (aClass, sName, null);
  }

  /**
   * Get the enum value with the passed name case insensitive
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @param eDefault
   *        The default value to be returned, if the name was not found.
   * @return The default parameter if no enum item with the given name is
   *         present.
   */
  @Nullable
  public static  & IHasName> ENUMTYPE getFromNameCaseInsensitiveOrDefault (@Nonnull final Class  aClass,
                                                                                                            @Nullable final String sName,
                                                                                                            @Nullable final ENUMTYPE eDefault)
  {
    ValueEnforcer.notNull (aClass, "Class");

    if (StringHelper.hasNoText (sName))
      return eDefault;
    return findFirst (aClass, x -> x.getName ().equalsIgnoreCase (sName), eDefault);
  }

  /**
   * Get the enum value with the passed name (case insensitive). If no such name
   * is present, an {@link IllegalArgumentException} is thrown.
   *
   * @param 
   *        The enum type
   * @param aClass
   *        The enum class
   * @param sName
   *        The name to search
   * @return The enum item with the given name. Never null.
   * @throws IllegalArgumentException
   *         if no enum item with the given name is present
   */
  @Nonnull
  public static  & IHasName> ENUMTYPE getFromNameCaseInsensitiveOrThrow (@Nonnull final Class  aClass,
                                                                                                          @Nullable final String sName)
  {
    final ENUMTYPE aEnum = getFromNameCaseInsensitiveOrNull (aClass, sName);
    if (aEnum == null)
      throw new IllegalArgumentException ("Failed to resolve name " + sName + " within class " + aClass);
    return aEnum;
  }

  /**
   * Get the unique name of the passed enum entry.
   *
   * @param aEnum
   *        The enum to use. May not be null.
   * @return The unique ID as a combination of the class name and the enum entry
   *         name. Never null.
   */
  @Nonnull
  public static String getEnumID (@Nonnull final Enum  aEnum)
  {
    // No explicit null check, because this method is used heavily in
    // locale resolving, so we want to spare some CPU cycles :)
    return aEnum.getClass ().getName () + '.' + aEnum.name ();
  }

  @Nonnull
  public static EChange clearCache ()
  {
    return RW_LOCK_INTCACHE.writeLockedGet ( () -> {
      if (INT_CACHE.isEmpty ())
        return EChange.UNCHANGED;
      INT_CACHE.clear ();

      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("Cache was cleared: " + EnumHelper.class.getName ());
      return EChange.CHANGED;
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy