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.object;
  
  import java.util.function.UnaryOperator;
  
  import org.jspecify.annotations.Nullable;
  import org.springframework.util.ClassUtils;
  
  import org.apache.causeway.commons.internal.assertions._Assert;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  import org.apache.causeway.commons.semantics.CollectionSemantics;
  import org.apache.causeway.core.metamodel.commons.ClassUtil;
  import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
  
  import org.jspecify.annotations.NonNull;
  import lombok.experimental.UtilityClass;
  
  @UtilityClass
  public class MmAssertionUtils {
  
      public void assertExactTypeSpecification(final @Nullable ManagedObject obj) {
          if(ManagedObjects.isNullOrUnspecifiedOrEmpty(obj)
                  || ManagedObjects.isPacked(obj)) {
              return;
          }
          var pojo = ManagedObjects.peekAtPojoOf(obj);
          var requiredType = pojo.getClass();
          var actualSpec = obj.objSpec();
          _Assert.assertEquals(requiredType, actualSpec.getCorrespondingClass());
      }
  
      public void assertExactType(
              final @Nullable ObjectSpecification requiredSpec,
              final @Nullable Object pojo) {
          if(pojo==null
                  || requiredSpec==null) {
              return;
          }
          var actualType = requiredSpec.isPrimitive()
                  ? ClassUtil.unboxPrimitiveIfNecessary(pojo.getClass())
                  : pojo.getClass();
          var actualSpec = requiredSpec.getSpecificationLoader().specForType(actualType).orElse(null);
          _Assert.assertEquals(requiredSpec, actualSpec, ()->
              String.format("pojo's actual ObjectSpecification %s "
                      + "does not exaclty match %s%n", actualSpec, requiredSpec));
      }
  
      /**
       * Guard against incompatible type.
       */
      public @NonNull UnaryOperator<ObjectSpecification> assertTypeOf(
              final @NonNull ObjectSpecification requiredSpec) {
          return specUnderInvestigation -> {
              _Assert.assertNotNull(specUnderInvestigation);
              if(specUnderInvestigation.isOfTypeResolvePrimitive(requiredSpec)) {
                  return specUnderInvestigation;
              }
              throw _Exceptions.illegalArgument("Object has incompatible type %s, "
                      + "must be an instance of %s.",
                      specUnderInvestigation,
                      requiredSpec);
          };
      }
  
      /**
       * Guard against incompatible type.
       */
      public @NonNull UnaryOperator<ManagedObject> assertInstanceOf(
              final ObjectSpecification elementType) {
          return object -> {
              if(ManagedObjects.isInstanceOf(object, elementType)) {
                  return object;
              }
              var upperBound = ClassUtils.resolvePrimitiveIfNecessary(elementType.getCorrespondingClass());
              var objectActualType = ClassUtils.resolvePrimitiveIfNecessary(object.objSpec().getCorrespondingClass());
              throw _Exceptions.illegalArgument("Object has incompatible type %s, "
                      + "must be an instance of %s.",
                      objectActualType.getName(),
                      upperBound.getName());
          };
      }
  
      /**
       * eg. in order to prevent wrapping an object that is already wrapped
       */
      public void assertPojoNotWrapped(final @Nullable Object pojo) {
          // can do this check only when the pojo is not null, otherwise is always considered valid
          if(pojo==null) {
              return;
          }
  
          if(pojo instanceof ManagedObject) {
              throw _Exceptions.illegalArgument(
                      "Cannot adapt a pojo of type ManagedObject, " +
                              "pojo.getClass() = %s, " +
                              "pojo.toString() = %s",
                              pojo.getClass(), pojo.toString());
          }
      }
  
      public void assertPojoIsScalar(final @Nullable Object pojo) {
          if(pojo==null) {
              return;
          }
  
          _Assert.assertTrue(CollectionSemantics.valueOf(pojo.getClass()).isEmpty(),
                  ()->String.format("is scalar %s", pojo.getClass()));
      }
  
      /** check is free of side-effects */
      public void assertSpecifiedAndNotEmpty(final @Nullable ManagedObject adapter) {
          _Assert.assertFalse(ManagedObjects.isNullOrUnspecifiedOrEmpty(adapter),
                  ()->"object is null unspecified or empty");
      }
  
      /** check is free of side-effects - also fails on non-attached entities */
      public void assertIsBookmarkSupported(final @Nullable ManagedObject adapter) {
          assertSpecifiedAndNotEmpty(adapter);
          _Assert.assertFalse(adapter.specialization().getBookmarkPolicy().isNoBookmark(),
                  ()->String.format("object %s does not provide a bookmark", adapter));
      }
  
      /** check is NOT free of side-effects */
      public static void assertHasBookmark(final @Nullable ManagedObject adapter) {
          assertSpecifiedAndNotEmpty(adapter);
          _Assert.assertTrue(adapter.getBookmark().isPresent(),
                  ()->String.format("object %s does not provide a bookmark", adapter));
      }
  
  }
  