blob: 9f5a41897cfbaee2e6f4f812d162d094114e967b [file] [log] [blame]
/*
* 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.springframework.lang.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);
}
}
}
}