package fr.upem.query;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import fr.upem.query.Donor;
import fr.upem.query.Query.With;
import fr.upem.query.Query.With.Operator;
/**
* A query that can be executed on a list of {@link Donor}.
*
* By example, the queries:
* <ul>
* <li>all limit 10
* <br>
* is a Query object: selector=ALL, withs=[], sortedBy=NO_SORT, limit=10
* <li>name sorted by amount
* <br>
* is a Query object: selector=name, withs=[], sortedBy=amount, limit=NO_LIMIT
* <li>all with name less than john
* <br>
* is a Query object: selector=ALL, withs=[name LESS_THAN john], sortedBy=NO_SORT, limit=NO_LIMIT
* </ul>
*/
public class Query {
// name of the field of {@link Donor} among
// { name, gender, company, amount } or ALL
// used to return only the value of the selected field for each {@link Donor}.
private final String selector;
// a list of {@link With} object used to filter the donor list
private final ArrayList<With> withs;
// name of the field of {@link Donor} among
// { name, gender, company, amount }
// used to sort by or NO_SORT if there is no sort.
private final String sortedBy;
// number of result of a query or NO_LIMIT if there is no limit
private final int limit;
static final String ALL = "all";
static final String NO_SORT ="NO_SORT";
static final int NO_LIMIT = -1;
/**
* Represent a filter of the query.
*/
static class With {
enum Operator {
GREATER_THAN, LESS_THAN, EQUALS_TO
}
// name of the field of {@link Donor} among
// { name, gender, company, amount }
private final String field;
// an operator among {GREATER_THAN, LESS_THAN, EQUALS_TO}
private final Operator operator;
// a constant value (as a String)
private final String constant;
With(String field, Operator operator, String constant) {
this.field = Objects.requireNonNull(field);
this.operator = Objects.requireNonNull(operator);
this.constant = Objects.requireNonNull(constant);
}
}
Query(String selector, ArrayList<With> withs, String sortedBy, int limit) {
if (limit < -1) {
throw new IllegalArgumentException("invalid limit");
}
this.selector = Objects.requireNonNull(selector);
this.withs = Objects.requireNonNull(withs);
this.sortedBy = Objects.requireNonNull(sortedBy);
this.limit = limit;
}
public static Query parse(String query) throws ParseException {
return QueryParser.parse(query);
}
public <T> List<T> execute(List<Donor> donors, Class<T> returnType) {
List<T> result;
result = retrieveBySelector(donors);
if (this.withs.size() > 0) {
for (With with : this.withs) {
result = processWith(result, with);
}
}
if (this.sortedBy != NO_SORT) {
result = (List<T>) sort((List<Donor>) result);
}
if (this.limit == NO_LIMIT) {
return result;
} else {
return result.subList(0, this.limit);
}
}
@SuppressWarnings("unchecked")
private <T> List<T> processWith(List<T> donors, With with) {
List<T> result = new ArrayList<>(donors);
switch (with.field) {
case "name":
result.stream()
.filter(d -> {
Donor donor = (Donor) d;
if (with.operator == Operator.GREATER_THAN) {
return (0 > donor.getName().compareTo(with.constant));
} else if (with.operator == Operator.LESS_THAN) {
return (0 < donor.getName().compareTo(with.constant));
} else {
return (0 == donor.getName().compareTo(with.constant));
}
});
break;
case "amount":
result.stream()
.filter(d -> {
Donor donor = (Donor) d;
if (with.operator == Operator.GREATER_THAN) {
return (Integer.getInteger(with.constant) > donor.getAmount());
} else if (with.operator == Operator.LESS_THAN) {
return (Integer.getInteger(with.constant) < donor.getAmount());
} else {
return (Integer.getInteger(with.constant) == donor.getAmount());
}
});
default:
throw new UnsupportedOperationException();
}
return result;
}
private List<Donor> sort(List<Donor> donors) {
List<Donor> result = new ArrayList<>(donors);
if (this.sortedBy == NO_SORT) {
System.out.println("no sort");
} else {
switch (this.sortedBy) {
case "name":
result.sort((donor1, donor2) -> {
return donor1.getName().compareTo(donor2.getName());
});
break;
case "gender":
throw new UnsupportedOperationException(
"Cannot compare donors by gender.");
case "company":
result.sort((donor1, donor2) -> {
return donor1.getCompany().compareTo(
donor2.getCompany());
});
break;
case "amount":
result.sort((donor1, donor2) -> {
if (donor1.getAmount() > donor2.getAmount()) return 1;
else return -1;
});
break;
default:
throw new UnsupportedOperationException();
}
}
return result;
}
@SuppressWarnings("unchecked")
private <T> List<T> retrieveBySelector(List<Donor> donors) {
List<T> result;
switch (this.selector) {
case ALL:
result = (List<T>) donors;
break;
case "name":
result = (List<T>) donors.stream()
.map(Donor::getName)
.collect(Collectors.<String>toList());
break;
case "gender":
result = (List<T>) donors.stream()
.map(Donor::getGender)
.collect(Collectors.<Gender>toList());
break;
case "company":
result = (List<T>) donors.stream()
.map(Donor::getCompany)
.collect(Collectors.<Company>toList());
break;
case "amount":
result = (List<T>) donors.stream()
.map(Donor::getAmount)
.collect(Collectors.<Long>toList());
break;
default:
throw new UnsupportedOperationException();
}
return result;
}
}