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.applib.util;
  
  import java.util.Comparator;
  import java.util.Objects;
  import java.util.function.Function;
  
  import org.apache.causeway.commons.internal.base._Casts;
  import org.apache.causeway.commons.internal.base._NullSafe;
  
  import lombok.extern.log4j.Log4j2;
  
  /**
   * Provides fluent composition for Objects' equals, hashCode and toString.
   * @since 1.x revised for 2.0 {@index}
   */
  @Log4j2
  public final class ObjectContracts {
  
      private ObjectContracts() {}
  
      public static <T> ToString<T> toString(final String name, final Function<T, ?> getter) {
          return ToString.toString(name, getter);
      }
  
      public static <T> Equality<T> checkEquals(final Function<T, ?> getter) {
          return Equality.checkEquals(getter);
      }
  
      public static <T> Hashing<T> hashing(final Function<T, ?> getter) {
          return Hashing.hashing(getter);
      }
  
      /**
       * WARNING Possible misuse because of forgetting respectively the last method
       * argument with {@code equals}, [@code hashCode} and {@code toString}!
       *
       * @since 2.0
       * @param <T>
       */
      public static interface ObjectContract<T> {
  
          public int compare(T obj, T other);
  
          public boolean equals(T obj, Object other);
  
          public int hashCode(T obj);
  
          public String toString(T obj);
  
          // -- TO STRING EVALUATION
  
          /**
           * True 'wither' (each call returns a new instance of ObjectContract)!
           * @param valueToStringFunction
           * @return ObjectContract with valueToStringFunction to apply to property values when
           * processing the toString algorithm.
           */
          public ObjectContract<T> withValueToStringFunction(Function<Object, String> valueToStringFunction);
  
          // -- COMPOSITION
  
          /**
           * Contract composition. (Any valueToStringFunction is 'copied over'.)
           *
           * @param propertyLabel a label to use for property to string output
           * @param getter function extracting the property value of an object
           * @param valueComparator
           */
          public <U> ObjectContract<T> thenUse(
                  String propertyLabel,
                  Function<? super T, ? extends U> getter,
                  Comparator<? super U> valueComparator);
  
          /**
           * Contract composition using the naturalOrder comparator.
           * (Any valueToStringFunction is 'copied over'.)
           *
           * @param propertyLabel a label to use for property to string output
           * @param getter function extracting the property value of an object
           */
          public default <U extends Comparable<? super U>> ObjectContract<T> thenUse(
                  final String propertyLabel,
                  final Function<? super T, ? extends U> getter){
              return thenUse(propertyLabel, getter, Comparator.<U>naturalOrder());
          }
  
          public static <T> ObjectContract<T> empty(final Class<T> objectClass) {
              return new ObjectContract_Empty<>(objectClass);
          }
  
      }
  
      // -- COMPOSITION ENTRY POINTS
  
      public static <T> ObjectContract<T> contract(final Class<T> objectClass) {
          return ObjectContract.empty(objectClass);
      }
  
      public static <T> ObjectContract<T> parse(final Class<T> target, final String propertyNames) {
          return ObjectContract_Parser.parse(target, propertyNames);
      }
  
      // -- BACKWARDS COMPATIBILITY TO-STRING EVALUATOR
  
      public interface ToStringEvaluator {
  
          public boolean canEvaluate(Object o);
          public String evaluate(Object o);
  
          public static Function<Object, String> combineToFunction(final ToStringEvaluator ... evaluators){
              return value -> {
                  if(value == null) {
                      return null;
                  }
                  if(!_NullSafe.isEmpty(evaluators)) {
                      for (ToStringEvaluator evaluator : evaluators) {
                          if(evaluator.canEvaluate(value)) {
                              return evaluator.evaluate(value);
                          }
                      }
                  }
                  return value.toString();
              };
          }
      }
  
      // -- BACKWARDS COMPATIBILITY
  
      @Deprecated // uses reflection on each call
      public static <T> String toString(final T obj, final String propertyNames) {
          Objects.requireNonNull(obj, "obj required, otherwise undecidable");
  
          return parse(_Casts.uncheckedCast(obj.getClass()), propertyNames)
                  .toString(obj);
      }
  
      @Deprecated // uses reflection on each call
      public static <T> boolean equals(final T obj, final Object other, final String propertyNames) {
  
          if(obj==null && other==null) {
              if(log.isWarnEnabled()) {
                  log.warn("potential misuse of <T> ObjectContracts::equals(T obj, Object other, "
                          + "String propertyNames). First argument is not expected to be null!");
              }
              return true;
          }
  
          Objects.requireNonNull(obj, "obj required, otherwise undecidable");
  
          return parse(_Casts.uncheckedCast(obj.getClass()), propertyNames)
                  .equals(obj, other);
      }
  
      @Deprecated // uses reflection on each call
      public static int hashCode(final Object obj, final String propertyNames) {
          Objects.requireNonNull(obj, "obj required, otherwise undecidable");
  
          return parse(_Casts.uncheckedCast(obj.getClass()), propertyNames)
                  .hashCode(obj);
      }
  
      @Deprecated // uses reflection on each call
      public static <T> int compare(final T obj, final T other, final String propertyNames) {
          Objects.requireNonNull(obj, "obj required, otherwise undecidable");
  
          return parse(_Casts.uncheckedCast(obj.getClass()), propertyNames)
                  .compare(obj, other);
      }
  
  }
  