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.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Objects;
  import java.util.function.Function;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  import java.util.stream.Collectors;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.applib.util.ObjectContracts.ObjectContract;
  import org.apache.causeway.commons.internal._Constants;
  import org.apache.causeway.commons.internal.base._Strings;
  
  /**
   * Package private parser for ObjectContract.<br/><br/>
   *
   * Not public API! Use ObjectContracts.parse(String) instead.
   *
   *
   * @since 2.0
   */
  class ObjectContract_Parser<T> {
  
      /**
       * Parsing stringified property name lists like <pre>"invoice desc, productCode, quantity"</pre>.
       * @param cls
       * @param propertyNames
       */
      @SuppressWarnings("unchecked")
      public static <T> ObjectContract<T> parse(Class<T> cls, final @Nullable String propertyNames) {
  
          Objects.requireNonNull(cls);
  
          final List<Clause<T>> clauses = _Strings.splitThenStream(propertyNames, ",")
                  .map(String::trim)
                  .filter(p->!p.isEmpty())
                  .map(p->Clause.parse(cls, p))
                  .collect(Collectors.toList());
  
          ObjectContract<T> contract = ObjectContract.empty(cls);
  
          if(clauses.isEmpty()) {
              return contract;
          }
  
          for(Clause<T> clause : clauses) {
              @SuppressWarnings("rawtypes")
              final Function valueExtractor = x->clause.extractValue((T)x);
  
              contract = contract.thenUse(
                      clause.propertyName,
                      valueExtractor,
                      clause.direction.getOrdering() );
          }
  
          return contract;
      }
  
      private static class Clause<T> {
  
          private static Pattern pattern =
                  Pattern.compile("\\W*(\\w+)\\W*(asc|asc nullsFirst|asc nullsLast|desc|desc nullsFirst|desc nullsLast)?\\W*");
  
          private enum Direction {
              ASC {
                  @Override @SuppressWarnings({ "unchecked", "rawtypes" })
                  public Comparator<Comparable<?>> getOrdering() {
                      // legacy of Ordering.natural().nullsFirst();
                      return Comparator.nullsFirst(Comparator.<Comparable>naturalOrder());
                  }
              },
              ASC_NULLS_LAST {
                  @Override @SuppressWarnings({ "unchecked", "rawtypes" })
                  public Comparator<Comparable<?>> getOrdering() {
                      // legacy of Ordering.natural().nullsLast();
                      return Comparator.nullsLast(Comparator.<Comparable>naturalOrder());
                  }
              },
              DESC {
                  @Override
                  public Comparator<Comparable<?>> getOrdering() {
                      // legacy of Ordering.natural().nullsLast().reverse();
                      return ASC_NULLS_LAST.getOrdering().reversed();
                  }
              },
              DESC_NULLS_LAST {
                  @Override
                  public Comparator<Comparable<?>> getOrdering() {
                      // legacy of Ordering.natural().nullsFirst().reverse();
                      return ASC.getOrdering().reversed();
                  }
              };
  
              public abstract Comparator<Comparable<?>> getOrdering();
  
              public static Direction valueOfElseAsc(String str) {
                  if("asc".equals(str)) return ASC;
                  if("asc nullsFirst".equals(str)) return ASC;
                  if("asc nullsLast".equals(str)) return ASC_NULLS_LAST;
                  if("desc".equals(str)) return DESC;
                  if("desc nullsFirst".equals(str)) return DESC;
                  if("desc nullsLast".equals(str)) return DESC_NULLS_LAST;
                  return ASC;
              }
          }
  
          private final Class<T> objectClass;
          private final String propertyName;
          private final Direction direction;
          private final Method getterMethod;
  
          private static <X> Clause<X> parse(Class<X> cls, String input) {
              final Matcher matcher = pattern.matcher(input);
              if(!matcher.matches()) {
                  return null;
              }
              return new Clause<>(cls, matcher.group(1), Direction.valueOfElseAsc(matcher.group(2)));
          }
  
          private Clause(Class<T> cls, String propertyName, Direction direction) {
              this.objectClass = cls;
              this.propertyName = propertyName;
              this.direction = direction;
              this.getterMethod = findGetter();
          }
  
          public Method findGetter() {
              final String methodNameSuffix = _Strings.capitalize(propertyName);
              final String getMethodName = "get" + methodNameSuffix;
              try {
                  return objectClass.getMethod(getMethodName);
              } catch (NoSuchMethodException e) {
                  final String isMethodName = "is" + methodNameSuffix;
                  try {
                      return objectClass.getMethod(isMethodName);
                  } catch (NoSuchMethodException ex) {
                      throw new IllegalArgumentException("No such method ' " +
                              getMethodName + "' or '" + isMethodName + "'", e);
                  }
              }
          }
  
          public Object extractValue(T obj) {
              try {
                  return getterMethod.invoke(obj, _Constants.emptyObjects);
              } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                  throw new RuntimeException(e);
              }
          }
  
      }
  
  }
  