Not able to filter elements using an enum - java-8

protected static double averagePrice( List<ComputerComponent> list ) {
return list.stream()
// .filter( line-> EnumUtils.isValidEnum(ComputerComponentCategory.class, line.getCategory()) )
// .filter( line-> isInEnum( line.getCategory(), ComputerComponentCategory.class) )
// .filter( line-> inEnum(line.getCategory(),EnumUtils.getEnumMap(ComputerComponentCategory.class ).keySet() ))
.filter( line ->
line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException:: new);
}
I have an enum as
public enum ComputerComponentCategory {
CPU("CPU"),
MONITOR("Monitor"),
KEYBOARD("Keyboard"),
MOUSE("Mouse"),
GPU("GPU"),
MEMORY("Memory"),
STORAGE("Storage"),
NULL("NOT DEFINED");
private String label;
ComputerComponentCategory(String label) {
this.label = label;
}
public String getLabel() {
return this.label;
}
public static ComputerComponentCategory getValue(String label) {
switch(label) {
case "CPU":
return CPU;
case "Monitor":
return MONITOR;
case "Keyboard":
return KEYBOARD;
case "Mouse":
return MOUSE;
case "GPU":
return GPU;
case "Memory":
return MEMORY;
case "Storage":
return STORAGE;
default:
return NULL ;
}
}
}
I pass a list of ComputerComponent class to the averagePrice() function which has two fields of price
which is of type double and
category which is of type String.
My list has 4 elements with categories as "CPU", "Mouse",
"Keyboard" and "Storage" with their respective prices as 34.0, 155.0, 23.0 and 75.0.
When I try to use inEnum(), isInEnum() or EnumUtils.isValidEnum() functions, I get the average price
as 34.0 which I think that they just return the price of the first element rather than the average.
But when I do filtering using
.filter( line ->
line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
I get the correct average value of 71.75.
The implementations that I have used for isInEnum() and inEnum() functions are the following:
public static <E extends Enum<E>> boolean isInEnum(String value, Class<E> enumClass) {
for (E e : enumClass.getEnumConstants()) {
if(e.name().contains(value)) { return true; }
}
return false;
}
public static boolean inEnum ( String category, Set<String> value ) {
for(String s: value ) {
if ( category.contains(s) ) {
return true ;
}
}
return false ;
}
How can I use enums correctly with java streams to filter by valid category names and get the correct
average value of price?
What mistake I am making when using streams and its functions ?

You could simply use your ComputerCategoryValue.getValue method and check for null, given the category of line:
public class EnumTest {
#Test
public void testBothMethods() {
final ComputerComponent c1 = new ComputerComponent(ComputerComponentCategory.CPU.getLabel(), 12.21);
final ComputerComponent c2 = new ComputerComponent(ComputerComponentCategory.MEMORY.getLabel(), 23.45);
final List<ComputerComponent> list = Arrays.asList(c1, c2);
assertEquals(averagePriceWithFilter(list), averagePriceWithInEnum(list), 0.01);
}
protected static double averagePriceWithFilter(final List<ComputerComponent> list) {
return list.stream()
.filter(line -> line.getCategory().contains("CPU")
|| line.getCategory().contains("GPU")
|| line.getCategory().contains("Monitor")
|| line.getCategory().contains("Keyboard")
|| line.getCategory().contains("Mouse")
|| line.getCategory().contains("Storage")
|| line.getCategory().contains("Memory"))
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException::new);
}
protected static double averagePriceWithInEnum(final List<ComputerComponent> list) {
return list.stream()
.filter(line -> ComputerComponentCategory.getValue(line.getCategory()) != null)
.mapToDouble(ComputerComponent::getPrice)
.average()
.orElseThrow(NoSuchElementException::new);
}
}
EDIT: explaining your mistakes:
EnumUtils.getEnumMap(ComputerComponentCategory.class).keySet()) returns a map of the enum name (not its label), so that the check will only work for CPU as there name and label are the same.
Same for the other method!
You need to use getLabel() instead of name() or use equalsIgnoreCase instead of contains.

Related

How to append specification based on condition in spring data jpa

I am creating a spring boot project and working on spring data jpa and currently I am using custom query to fetch data from db based on users selection and this is my page where user can select option based on their condition https://i.imgur.com/coO3BCJ.png
So, I googled it and found we can use specification but as I am very new to specification, so, I used specification and I want that based on users choice, it should keep adding specification, so, this is my conditional specification...
Specification<UserDetails> specification =
Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
if(!customSearch.getManglik().isBlank()) {
specification.and(UserDetailsSpecification.isManglik
(customSearch.getManglik()));
}
if(!customSearch.getMaritalStatus().isBlank()) {
specification.and(UserDetailsSpecification
.hasMaritalStatus(customSearch.getMaritalStatus()));
}
if(!customSearch.getReligion().isBlank()) {
specification.and(UserDetailsSpecification.hasReligion
(customSearch.getReligion()));
}
if(!customSearch.getCaste().isBlank()) {
specification.and(UserDetailsSpecification.hasCaste
(customSearch.getCaste()));
}
if(!customSearch.getLocation().isBlank()) {
specification.and(UserDetailsSpecification.hasLocation
(customSearch.getLocation()));
}
listOfCustomSearch=userDetailsRepository
.findAll(specification, pageable);
List<UserDetails> listOfAllSearchedUsers = listOfCustomSearch.getContent();
but it is not appending the specification and just filtering the data based on only
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
so, based on users selection, so, my final query should be something like this(If user has selected all fields):
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik()) .and(UserDetailsSpecification.hasMaritalStatus(customSearch.getMaritalStatus())) .and(UserDetailsSpecification.hasReligion(customSearch.getReligion()))
.and(UserDetailsSpecification.hasCaste(customSearch.getCaste()))
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
But suppose if user has selected only let suppose 3 or 4 fields, so, my final specification should be something like below:(This specification should be completely depends upon user selection)
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik())
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
Currently it is not appending specification based on users selection, so, please help me in adding specification based on users selections
Finally, I found the solution and we can create customized specification like this below:
public static Specification<UserDetails> getSpecs(String gender, int fromAge, int toAge, String manglikStatus, String maritalStatus, String religion, String caste, String location){
Specification<UserDetails> spec = null;
Specification<UserDetails> temp = null;
if(!gender.isBlank() && !gender.isEmpty() && gender!=null && !gender.contains("$")) {
spec = getSpecsForGenderDetails(gender);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(fromAge!=0 || toAge!=0) {
spec = isAgeBetween(fromAge, toAge);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!manglikStatus.isBlank() && !manglikStatus.isEmpty() && manglikStatus!=null && !manglikStatus.contains("$")) {
spec = isManglik(manglikStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!maritalStatus.isBlank() && !maritalStatus.isEmpty() && maritalStatus!=null && !maritalStatus.contains("$")) {
spec = hasMaritalStatus(maritalStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!religion.isBlank() && !religion.isEmpty() && religion!=null && !religion.contains("$")) {
spec = hasReligion(religion);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!caste.isBlank() && !caste.isEmpty() && caste!=null && !caste.equalsIgnoreCase("select") && !caste.contains("$")) {
spec = hasCaste(caste);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!location.isBlank() && !location.isEmpty() && location!=null && !location.contains("$")) {
spec = hasLocation(location);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
return temp;
}
And based on that we can define our method like this:
private static Specification<UserDetails> getSpecsForGenderDetails(String gender) {
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("gender"),gender);
});
}
private static Specification<UserDetails> isAgeBetween(int fromAge, int toAge){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.between(root.get("age"), fromAge, toAge);
});
}
private static Specification<UserDetails> isManglik(String manglikStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("manglikStatus")),"%" +manglikStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasMaritalStatus(String maritalStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("maritalStatus")),"%" +maritalStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasReligion(String religion){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("religion")),"%" +religion.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasCaste(String caste){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("caste")),"%" +caste.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasLocation(String presentState){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("presentState")),"%" +presentState.toLowerCase() +"%");
});
}

Refactor multiple or conditions with java8

I have a scenario to refactor multiple or conditions in if block with java8
private boolean isStatusAvailable(List<Hierarchy> hierarchyList, String status, int i) {
return isContainsStatus(hierarchyList.get(i+1), status) || isContainsStatus(hierarchyList.get(i+1), ResultStatus.SUCCESS) || isContainsStatus(hierarchyList.get(i+1), ResultStatus.PEND);
}
private boolean isContains(Hierarchy hierarchy, String status) {
return hierarchy.getStatus().contains(status);
}
public enum ResultStatus {
SUCCESS,
SUCCESS_PENDING,
SUCCESS_SUMMARY,
PEND
}
Is there any way to refactor the above code in java8?
There is always the possibility to introduce a loop like:
private boolean isStatusAvailable(List<Hierarchy> hierarchyList, ResultStatus status, int i) {
Hierarchy hierarchy = hierarchyList.get(i + 1);
for (ResultStatus s : Arrays.asList(status, SUCCESS, PEND)) {
if (isContainsStatus(hierarchy, s)) {
return true;
}
}
return false;
}
If you want to use Streams then you can write:
private boolean isStatusAvailable(List<Hierarchy> hierarchyList, ResultStatus status, int i) {
Hierarchy hierarchy = hierarchyList.get(i + 1);
return Stream.of(status, SUCCESS, PEND).anyMatch(s -> isContainsStatus(hierarchy, s));
}

Java 8: Stream and filter based on optional conditions

Example: Filter a list of products that have a price based on fromPrice and toPrice. They could either both be supplied, or just one.
Find all products whose price is greater than fromPrice
Find all products whose price is less than toPrice
Find all products whose price is between fromPrice and toPrice
Product:
public class Product {
private String id;
private Optional<BigDecimal> price;
public Product(String id, BigDecimal price) {
this.id = id;
this.price = Optional.ofNullable(price);
}
}
PricePredicate:
public class PricePredicate {
public static Predicate<? super Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
if (fromPrice != null && toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0 &&
product.getPrice().get().compareTo(toPrice) <= 0;
}
if (fromPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0;
}
if (toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0;
}
return null;
}
}
Filters:
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, null)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(null, toPrice)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, toPrice)).collect(Collectors.toList());
Is there a way to improve my Predicate instead of having the if not null checks? Anything that can be done with optionals?
No, Optional is not designed to replace null checks.
But your code can be improved by avoiding duplication, and by avoiding to return null (which is clearly not a valid value for a Predicate) if both arguments are null:
public static Predicate<Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
Predicate<Product> result = product -> true;
if (fromPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0);
}
if (toPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0);
}
return result;
}
You can use Apache Commons Lang, it offers null safe comparison:
ObjectUtils.compare(from, to)
null is assumed to be less than a non-value

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

SingleOrDefault: How to change the default values?

SingleOrDefault returns null, but what if I want to assign values to represent the object that wasn't found?
you can do something like
myStrings.DefaultIfEmpty("myDefaultString").Single()
check out here
?? operator. If the left argument is null, evaluate and return the second argument.
myCollection.SingleOrDefault() ?? new[]{new Item(...)}
This will only work with reference types (or nullables), but it would do what you're looking for very simply.
You could roll your own.
public static T SingleOrDefault<T>(this IEnumerable<T> enumerable, T defaultValue) {
if ( 1 != enumerable.Count() ) {
return defaultValue;
}
return enumerable.Single();
}
This can be a bit expensive though because Count() requires you to process the entire collection and can be fairly expensive to run. It would be better to either call Single, catch the InvalidOperationException or roll a IsSingle method
public static bool IsSingle<T>(this IEnumerable<T> enumerable) {
using ( var e = enumerable.GetEnumerator() ) {
return e.MoveNext() && !e.MoveNext();
}
}
public static T SingleOrDefault<T>(this IEnumerable<T> enumerable, T defaultValue) {
if ( !enumerable.IsSingle() ) {
if( enumerable.IsEmpty() ) {
return defaultValue;
}
throw new InvalidOperationException("More than one element");
}
return enumerable.Single();
}
You could create your own extension methods -- SingleOrNew.
public static class IEnumerableExtensions
{
public static T SingleOrNew<T>( this IEnumerable<T> enumeration, T newValue )
{
T elem = enumeration.SingleOrDefault();
if (elem == null)
{
return newValue;
}
return elem;
}
public static T SingleOrNew<T>( this IEnumerable<T> enumeration, Func<T,bool> predicate, T newValue )
{
T elem = enumeration.SingleOrDefault( predicate );
if (elem == null)
{
return newValue;
}
return elem;
}
}

Resources