Skip to content

Commit

Permalink
DocumentQueryOrderBy: nullsLast support
Browse files Browse the repository at this point in the history
  • Loading branch information
teosarca committed Dec 5, 2017
1 parent 9363c24 commit d440b3f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 39 deletions.
106 changes: 70 additions & 36 deletions src/main/java/de/metas/ui/web/window/model/DocumentQueryOrderBy.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.adempiere.util.Check;
Expand All @@ -12,6 +11,8 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;

import lombok.Builder;
import lombok.ToString;
import lombok.Value;

/*
Expand Down Expand Up @@ -39,21 +40,17 @@
@Value
public final class DocumentQueryOrderBy
{
public static final DocumentQueryOrderBy byFieldName(final String fieldName, final boolean ascending)
{
return new DocumentQueryOrderBy(fieldName, ascending);
}

public static final DocumentQueryOrderBy byFieldName(final String fieldName)
{
final boolean ascending = true;
return new DocumentQueryOrderBy(fieldName, ascending);
final boolean nullsLast = getDefaultNullsLastByAscending(ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}

public static final DocumentQueryOrderBy byFieldNameDescending(final String fieldName)
public static final DocumentQueryOrderBy byFieldName(final String fieldName, final boolean ascending)
{
final boolean ascending = false;
return new DocumentQueryOrderBy(fieldName, ascending);
final boolean nullsLast = getDefaultNullsLastByAscending(ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}

/**
Expand Down Expand Up @@ -83,28 +80,42 @@ private static final DocumentQueryOrderBy parseOrderBy(final String orderByStr)
if (orderByStr.charAt(0) == '+')
{
final String fieldName = orderByStr.substring(1);
return DocumentQueryOrderBy.byFieldName(fieldName, true);
final boolean ascending = true;
final boolean nullsLast = getDefaultNullsLastByAscending(ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}
else if (orderByStr.charAt(0) == '-')
{
final String fieldName = orderByStr.substring(1);
return DocumentQueryOrderBy.byFieldName(fieldName, false);
final boolean ascending = false;
final boolean nullsLast = getDefaultNullsLastByAscending(ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}
else
{
final String fieldName = orderByStr;
return DocumentQueryOrderBy.byFieldName(fieldName, true);
final boolean ascending = true;
final boolean nullsLast = getDefaultNullsLastByAscending(ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}
}

private static final boolean getDefaultNullsLastByAscending(final boolean ascending)
{
return true; // always nulls last
}

private final String fieldName;
private final boolean ascending;
private final boolean nullsLast;

private DocumentQueryOrderBy(final String fieldName, final boolean ascending)
@Builder
private DocumentQueryOrderBy(final String fieldName, final Boolean ascending, final Boolean nullsLast)
{
Check.assumeNotEmpty(fieldName, "fieldName is not empty");
this.fieldName = fieldName;
this.ascending = ascending;
this.ascending = ascending != null ? ascending : true;
this.nullsLast = nullsLast != null ? nullsLast : getDefaultNullsLastByAscending(this.ascending);
}

public DocumentQueryOrderBy copyOverridingFieldName(final String fieldName)
Expand All @@ -113,53 +124,76 @@ public DocumentQueryOrderBy copyOverridingFieldName(final String fieldName)
{
return this;
}
return new DocumentQueryOrderBy(fieldName, ascending);
return new DocumentQueryOrderBy(fieldName, ascending, nullsLast);
}

public <T> Comparator<T> asComparator(final BiFunction<T, String, Object> fieldValueExtractor)
public <T> Comparator<T> asComparator(final FieldValueExtractor<T> fieldValueExtractor)
{
final Function<T, Object> keyExtractor = obj -> fieldValueExtractor.apply(obj, fieldName);
Comparator<T> cmp = Comparator.comparing(keyExtractor, ValueComparator.instance);

if (!ascending)
{
cmp = cmp.reversed();
}
final Function<T, Object> keyExtractor = obj -> fieldValueExtractor.getFieldValue(obj, fieldName);
Comparator<? super Object> keyComparator = ValueComparator.ofAscendingAndNullsLast(ascending, nullsLast);
return Comparator.comparing(keyExtractor, keyComparator);
}

return cmp;
@FunctionalInterface
public static interface FieldValueExtractor<T>
{
Object getFieldValue(T object, String fieldName);
}

@ToString
private static final class ValueComparator implements Comparator<Object>
{
public static final transient ValueComparator instance = new ValueComparator();
public static final ValueComparator ofAscendingAndNullsLast(final boolean ascending, final boolean nullsLast)
{
if (ascending)
{
return nullsLast ? ASCENDING_NULLS_LAST : ASCENDING_NULLS_FIRST;
}
else
{
return nullsLast ? DESCENDING_NULLS_LAST : DESCENDING_NULLS_FIRST;
}
}

public static final transient ValueComparator ASCENDING_NULLS_FIRST = new ValueComparator(true, false);
public static final transient ValueComparator ASCENDING_NULLS_LAST = new ValueComparator(true, true);
public static final transient ValueComparator DESCENDING_NULLS_FIRST = new ValueComparator(false, false);
public static final transient ValueComparator DESCENDING_NULLS_LAST = new ValueComparator(false, true);

private final boolean ascending;
private final boolean nullsLast;

private ValueComparator()
private ValueComparator(final boolean ascending, final boolean nullsLast)
{
super();
this.ascending = ascending;
this.nullsLast = nullsLast;
}

@Override
public int compare(final Object o1, final Object o2)
{
if (o1 instanceof Comparable)
if (o1 == o2)
{
@SuppressWarnings("unchecked")
final Comparable<Object> o1cmp = (Comparable<Object>)o1;
return o1cmp.compareTo(o2);
return 0;
}
else if (o1 == null)
{
return o2 == null ? 0 : -1;
return nullsLast ? +1 : -1;
}
else if (o2 == null)
{
return +1;
return nullsLast ? -1 : +1;
}
else if (o1 instanceof Comparable)
{
@SuppressWarnings("unchecked")
final Comparable<Object> o1cmp = (Comparable<Object>)o1;
return o1cmp.compareTo(o2) * (ascending ? +1 : -1);
}
else
{
return o1.toString().compareTo(o2.toString());
return o1.toString().compareTo(o2.toString()) * (ascending ? +1 : -1);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private final IStringExpression buildSqlOrderBy(final DocumentQueryOrderBy order
{
final String fieldName = orderBy.getFieldName();
final IStringExpression sqlExpression = bindings.getFieldOrderBy(fieldName);
return SqlDocumentOrderByBuilder.buildSqlOrderBy(sqlExpression, orderBy.isAscending());
return SqlDocumentOrderByBuilder.buildSqlOrderBy(sqlExpression, orderBy.isAscending(), orderBy.isNullsLast());
}

/**
Expand All @@ -86,15 +86,17 @@ private final IStringExpression buildSqlOrderBy(final DocumentQueryOrderBy order
* @param ascending
* @return ORDER BY SQL or empty
*/
private static final IStringExpression buildSqlOrderBy(final IStringExpression sqlExpression, final boolean ascending)
private static final IStringExpression buildSqlOrderBy(final IStringExpression sqlExpression, final boolean ascending, final boolean nullsLast)
{
if (sqlExpression.isNullExpression())
{
return sqlExpression;
}

return IStringExpression.composer()
.append("(").append(sqlExpression).append(")").append(ascending ? " ASC" : " DESC")
.append("(").append(sqlExpression).append(")")
.append(ascending ? " ASC" : " DESC")
.append(nullsLast ? " NULLS LAST" : " NULLS FIRST")
.build();
}

Expand Down

0 comments on commit d440b3f

Please sign in to comment.