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.spec.impl;
  
  import java.util.Optional;
  
  import jakarta.inject.Named;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.applib.id.LogicalType;
  import org.apache.causeway.applib.services.bookmark.Bookmark;
  import org.apache.causeway.applib.services.metamodel.BeanSort;
  import org.apache.causeway.commons.internal.base._Strings;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutor;
  import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
  import org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationMutable.IntrospectionState;
  import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
  
  import org.jspecify.annotations.NonNull;
  
  interface SpecificationLoaderInternal extends SpecificationLoader {
      /**
       * Return the specification for the specified class of object.
       *
       * <p>
       * It is possible for this method to return <tt>null</tt>, for example if
       * any of the configured {@link ClassSubstitutor}s has filtered out the class.
       *
       * @return {@code null} if {@code domainType==null}, or if the type should be ignored.
       */
      @Nullable
      ObjectSpecification loadSpecification(@Nullable Class<?> domainType, @NonNull IntrospectionState upTo);
      
      // -- SUPPORT FOR LOOKUP BY LOGICAL TYPE NAME
  
      /**
       * The lookup may also fail (result with null), when there is no concrete or abstract resolvable type,
       * that matches given {@code logicalTypeName}. Eg. when using {@link Named} on an interface,
       * while overriding with a different logical-type-name on the concrete or abstract type.
       */
      @Nullable
      default ObjectSpecification loadSpecification(
              final @Nullable String logicalTypeName,
              final @NonNull  IntrospectionState introspectionState) {
  
          if(_Strings.isNullOrEmpty(logicalTypeName)) {
              return null;
          }
          return lookupLogicalType(logicalTypeName)
              .map(logicalType->
                      loadSpecification(logicalType.correspondingClass(), introspectionState))
              .orElse(null);
      }
      
      // -- SHORTCUTS - 1
  
      default Optional<ObjectSpecification> specForLogicalTypeName(
              final @Nullable String logicalTypeName) {
          return Optional.ofNullable(
                  loadSpecification(logicalTypeName, IntrospectionState.FULLY_INTROSPECTED));
      }
  
      default Optional<ObjectSpecification> specForLogicalType(
              final @Nullable LogicalType logicalType) {
          return Optional.ofNullable(logicalType)
                  .map(LogicalType::correspondingClass)
                  .flatMap(this::specForType);
      }
  
      default Optional<ObjectSpecification> specForType(
              final @Nullable Class<?> domainType) {
          return Optional.ofNullable(
                  loadSpecification(domainType, IntrospectionState.FULLY_INTROSPECTED));
      }
  
      default Optional<ObjectSpecification> specForBookmark(
              final @Nullable Bookmark bookmark) {
          return Optional.ofNullable(bookmark)
                  .map(Bookmark::logicalTypeName)
                  .flatMap(this::specForLogicalTypeName);
      }
  
      // -- SHORTCUTS - 2
  
      default ObjectSpecification specForLogicalTypeNameElseFail(
              final @Nullable String logicalTypeName) {
          return specForLogicalTypeName(logicalTypeName)
                  .orElseThrow(()->_Exceptions.noSuchElement(
                          "meta-model is not aware of an object-type named '%s'",
                          _Strings.nullToEmpty(logicalTypeName)));
      }
  
      default ObjectSpecification specForLogicalTypeElseFail(
              final @Nullable LogicalType logicalType) {
          return specForLogicalType(logicalType)
                  .orElseThrow(()->_Exceptions.noSuchElement(
                          "meta-model is not aware of an object-type '%s'",
                          logicalType));
      }
  
      default ObjectSpecification specForTypeElseFail(
              final @Nullable Class<?> domainType) {
          return specForType(domainType)
                  .orElseThrow(()->_Exceptions.noSuchElement(
                          "meta-model is not aware of a type '%s'",
                          domainType));
      }
  
      default ObjectSpecification specForBookmarkElseFail(
              final @Nullable Bookmark bookmark) {
          return specForBookmark(bookmark)
                  .orElseThrow(()->_Exceptions.noSuchElement(
                          "meta-model is not aware of a bookmark's (%s) object-type",
                          bookmark));
      }
  
      // -- CAUTION! (use only during meta-model initialization)
  
      default @Nullable ObjectSpecification loadSpecification(
              final @Nullable Class<?> domainType) {
          return loadSpecification(domainType, IntrospectionState.TYPE_INTROSPECTED);
      }
      
      @Override
      default Optional<BeanSort> lookupBeanSort(final @Nullable LogicalType logicalType) {
          if(logicalType==null) return Optional.empty();
          var spec = loadSpecification(logicalType.correspondingClass(), IntrospectionState.NOT_INTROSPECTED);
          return spec != null 
                  ? Optional.of(spec.getBeanSort())
                  : Optional.empty();
      }
  }
  