content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you 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 org.apache.causeway.core.metamodel.commons;
  
  import java.util.Map;
  
  import org.apache.causeway.commons.internal.base._Strings;
  import org.apache.causeway.commons.internal.context._Context;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  
  import org.jspecify.annotations.NonNull;
  import lombok.experimental.UtilityClass;
  
  @UtilityClass
  public final class ClassUtil {
  
      private final Map<String, Class<?>> primitiveByName = Map.of(
              void.class.getName(), void.class,
              boolean.class.getName(), boolean.class,
              char.class.getName(), char.class,
              byte.class.getName(), byte.class,
              short.class.getName(), short.class,
              int.class.getName(), int.class,
              long.class.getName(), long.class,
              float.class.getName(), float.class,
              double.class.getName(), double.class);
  
      final Map<Class<?>, Object> defaultByPrimitive = Map.of(
              boolean.class, false,
              byte.class, (byte)0,
              short.class, (short)0,
              int.class, 0,
              long.class, 0L,
              float.class, 0.0f,
              double.class, 0.0,
              char.class, (char)0);
  
      //XXX supposedly use Spring's ClassUtils instead
      final Map<Class<?>, Class<?>> wrapperByPrimitive = Map.of(
              boolean.class, Boolean.class,
              byte.class, Byte.class,
              char.class, Character.class,
              short.class, Short.class,
              int.class, Integer.class,
              long.class, Long.class,
              float.class, Float.class,
              double.class, Double.class,
              void.class, Void.class);
  
      final Map<Class<?>, Class<?>> primitiveByWrapper = Map.of(
              Boolean.class, boolean.class,
              Byte.class, byte.class,
              Character.class, char.class,
              Short.class, short.class,
              Integer.class, int.class,
              Long.class, long.class,
              Float.class, float.class,
              Double.class, double.class,
              Void.class, void.class);
  
      // //////////////////////////////////////
  
      /**
       * Unbox the given class if it is a primitive wrapper class,
       * returning the corresponding primitive type instead.
       * @param clazz the class to check
       * @return the original class, or a primitive for the original primitive wrapper type
       */
      public Class<?> unboxPrimitiveIfNecessary(final @NonNull Class<?> clazz) {
          // an optimization, to skip the lookup if possible
          var firstPass = clazz.isPrimitive()
                  || !clazz.getPackageName().equals("java.lang")
                  ? clazz
                  : primitiveByWrapper.get(clazz); //null-able
          return firstPass!=null
                  ? firstPass
                  : clazz;
      }
  
      public Class<?> getBuiltIn(final String name) {
          return primitiveByName.get(name);
      }
  
      /**
       * Returns the supplied Class so long as it implements (or is a subclass of)
       * the required class, and also has either a constructor accepting the
       * specified param type, or has a no-arg constructor.
       */
      public Class<?> implementingClassOrNull(final String classCandidateName, final Class<?> requiredClass, final Class<?> constructorParamType) {
          if (classCandidateName == null) {
              return null;
          }
          Class<?> classCandidate = null;
          try {
              classCandidate = _Context.loadClass(classCandidateName);
              return ClassExtensions.implementingClassOrNull(classCandidate, requiredClass, constructorParamType);
          } catch (final ClassNotFoundException e) {
              return null;
          }
      }
  
      public boolean directlyImplements(final Class<?> cls, final Class<?> interfaceType) {
          for (final Class<?> directlyImplementedInterface : cls.getInterfaces()) {
              if (directlyImplementedInterface == interfaceType) {
                  return true;
              }
          }
          return false;
      }
  
      public Class<?> forNameElseFail(final String fullName) {
          _Strings.requireNonEmpty(fullName, "Full Name");
          final Class<?> builtIn = ClassUtil.getBuiltIn(fullName);
          if (builtIn != null) {
              return builtIn;
          }
          try {
              return _Context.loadClass(fullName);
          } catch (final ClassNotFoundException e) {
              throw _Exceptions.unrecoverable(e);
          }
      }
  
      public Class<?> forNameElseNull(final String fullName) {
          if (_Strings.isNullOrEmpty(fullName)) {
              return null;
          }
          var primitiveClass = primitiveByName.get(fullName);
          if(primitiveClass!=null) {
              return primitiveClass;
          }
          try {
              return _Context.loadClass(fullName);
          } catch (final ClassNotFoundException e) {
              return null;
          }
      }
  
      /**
       * Returns {@code cls.getCanonicalName()} if present.
       * Otherwise resorts to fully qualified class-name, with any '$' replaced by '.$'.
       * @param cls
       * @return non-null
       */
      public String getCanonicalName_friendlyToInnerClasses(final @NonNull Class<?> cls) {
          var name = cls.getCanonicalName();
          if(name==null) {
              return cls.getName().replace("$", ".$").replace("..", ".");
          }
          return name;
      }
  
  }
  