Vaadin DropDown/Select/Spinner best practice? - java

I am looking at the Vaadin components and it seems like the "favoured" Select-component in Vaadin does not run on indexing.
Using this code:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("A");
Select<String> select = new Select<>();
select.setItems(list);
So when I have the following list:
A
B
A
And I choose the third option, A, it would appear as the first option, also A, in the list. Is there a Vaadin component or library with java implementation which runs on indexed values? or has there been a workaround.

How would the end user distinguish those identical options?
The Select component relies on the equals and hashCode methods to distinguish items. In your case, the two strings are equal to each other, and as such they are the same from the component's point of view.
So if you have a valid use case for this, you will have to pass items where equals is properly implemented for the use case.
There are several ways to do this: making a custom class, passing in a map of values, passing in an enum etc. In all cases you will probably want to use a custom item label generator.
With a map, it would look something like this:
Map<Integer, String> values = new HashMap<>();
values.put(1, "A");
values.put(2, "B");
values.put(3, "A");
Select<Integer> select = new Select<>();
select.setItems(values.keySet());
select.setItemLabelGenerator(values::get);
add(select);
Or with an enum, as cfrick suggested:
enum Option {
FIRST_A("A"), B("B"), SECOND_A("A");
private final String name;
Option(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
...
Select<Option> select = new Select<>();
select.setItems(Option.values());
select.setItemLabelGenerator(Option::getName);
add(select);

Related

Dynamic order by with java with list

I want to sort my list(taken from the db I can't sort using query), but I don't know for what parameters, the parameters are chosen by the user. The following code sorts by first and last name in desc, but how do I create a dynamic one? I have made many attempts, but I have failed.
myList.sort(Comparator.comparing(DTO::getName)
.thenComparing(DTO::getLastName).reversed());
To create a customized order depending on the user params, I suggest something like :
1- Create an enumeration of the possible sorting orders which will be populated by the parameters in the user query, say that, for example, I have 3 orders BY_ID, BY_NAME, BY_LAST_NAME.
2- Then create a list of this enumeration;
3- After that create a map, where the key is the enumeration above, and the value is a comparator.
4- populate the map.
5-Derive a reduced final comparator from both the list of orders you had in (2), and the map in (3).
6- Sort your list by that final comparator.
Putting all the steps together
The enumeration of the sort orders :
public enum SortOrder {
BY_ID,
BY_NAME,
BY_LAST_NAME;
}
The main test method :
public static void main(String[] args) {
/**dummy objects for test purposes**/
DTO firstDTO = new DTO();
firstDTO.setId(1);
firstDTO.setName("John");
firstDTO.setLastName("Doe");
DTO secondDTO = new DTO();
secondDTO.setId(2);
secondDTO.setName("John");
secondDTO.setLastName("Doe");
DTO thirdDTO = new DTO();
thirdDTO.setId(2); // the id is repeated
thirdDTO.setName("Alice");
thirdDTO.setLastName("Bob");
final List<SortOrder> sortOrders = createSortOrdersFromInputParams(/*the params passed as args*/);
final List<DTO> myList = Arrays.asList(firstDTO, secondDTO, thirdDTO); // your list that is supposed to come from a DB
final Map<SortOrder, Comparator<DTO>> map = new EnumMap<>(SortOrder.class);
final Comparator<DTO> defaultComparator = Comparator.nullsLast(Comparator.comparing(DTO::getId)); // you'all always need a default comparator , say by ID
map.put(SortOrder.BY_ID, defaultComparator);
map.put(SortOrder.BY_NAME, Comparator.nullsLast(Comparator.comparing(DTO::getName)));
map.put(SortOrder.BY_LAST_NAME, Comparator.nullsLast(Comparator.comparing(DTO::getLastName)));
final Comparator<DTO> finalComparator = sortOrders.stream()
.map(map::get)
.filter(Objects::nonNull)
.reduce(Comparator::thenComparing)
.orElse(defaultComparator);
myList.sort(finalComparator);
System.out.println(myList);
}
The method that turns parameters of the user input into sort orders
private static List<SortOrder> createSortOrdersFromInputParams(/*params passed as args*/) {
return Arrays.asList(SortOrder.BY_ID, SortOrder.BY_NAME, SortOrder.BY_LAST_NAME);
}
I hope this will respond to your case.

Create list of an element of an Object from another list of Objects

The model:
public class MyModel{
private int id;
private String name;
....
....
//getters and setters
}
I have a list of MyModel object:
//First Object to be added to list
MyModel myModelObject1 = new MyModel();
myModelObject1.setId(1);
myModelObject1.setName("abc");
//Second Object to be added to list
MyModel myModelObject2 = new MyModel();
myModelObject1.setId(2);
myModelObject1.setName("pqr");
List<MyModel> myModelList = new ArrayList<MyModel>();
myModelList.add(myModelObject1);
myModelList.add(myModelObject2);
I want to get a list of names present in the MyModel List i.e. I want to create a list of names (List<String> in this case) from myModelList. So, I want my list to have:
{"abc", "pqr"}
There is always a way to iterate and create another list but is there any better way to do that? (Not necessarily to be efficient but if it can be done in a line using streams, foreach e.t.c.).
EDIT:
The answers worked for me but I have some modifications in my use case: If I want to add a condition that only name which contains character 'a' should be added to the list and I want to add a logger message to debug for each element then how should I approach this?
I tried doing the following using a method (charAPresent()) which checks that if the passed String contains character 'a' but it didn't work:
List<String> modelNameList = myModelList.stream()
.map(model -> {
if (charAPresent(model.getName)) {
model.getName()
}
})
.collect(Collectors.toList());
Let me know if I am missing something.
Using Java 8 Stream:
List<String> modelNameList = myModelList.stream()
.map(Model::getName)
.collect(Collectors.toList());
Model::getName is called as method reference. It equivalents to model -> model.getName()
You can use streams and map your object to its name and collect to a list as:
List<String> names = myModelList.stream()
.map(MyModel::getName)
.collect(Collectors.toList());
There is always a way to iterate and create another list but is there
any better way to do that
Even with the use of streams, you would have to iterate the complete collection. There is no better way of doing it than iterating the complete collection.
You can use Stream.map() to get only the names of your model. Use Stream.filter() to get only the names matching your charAPresent() method. To log the entries before collecting you can use Stream.peek():
List<String> modelNameList = myModelList.stream()
.map(Model::getName) // map only the name
.filter(YourClass::charAPresent) // filter the items
.peek(System.out::println) // print all items which are in the filter
.collect(Collectors.toList());
You can also use foreach like this:
public static List<String> modelNames(List<MyModel> myModelList)
List<String> list = new ArrayList<String>();
for(MyModel mm : myModelList) {
if(mm.getName().contains("a") {
list.add(mm.getName());
}
}
return list;
}

Creating a list of booleans on the fly

Imagine we are pulling data about people and their favourite foods.
The data would come to us in the format: "Name, FavFood1, FavFood2..FavFoodn".
e.g. "James, Beans, Chicken".Notice how we do not know how many foods a person will favour.
From this data we create an instance of a Person object which captures the person's name and favourite foods. After we have pulled data on every person, we want to create a spreadsheet whose columns would be: Name|Potato|Chicken|Beans|Curry etc.
All of the values to the right of the person's name will be simple boolean values representing whether or not that food was one of the person's favourites.
The problem is: we do not know in advance; all the foods that someone could possibly favour, and as such cannot just set up boolean instance variables in the Person class.
I've given this some thought, implementing sets,hash-sets and hash-maps, however every solution I think of ends up being horribly inelegant and so I've turned to the genius of stackoverflow for help on this one.
My question is: What design pattern / approach can I use to cleanly achieve the outcome I desire? Whilst this is a language-agnostic question I am programming this in Java, so if there's anything in the Java API or elsewhere built for this, do let me know.
Thanks in advance!
Try this. It generates data in csv form.
class Person {
final String name;
final Set<String> foods;
Person(String name, Set<String> foods) {
this.name = name;
this.foods = foods;
}
Stream<Boolean> getBooleans(List<String> foods) {
return foods.stream().map(food -> this.foods.contains(food));
}
#Override
public String toString() {
return "Person(" + name + ", " + foods +")";
}
}
class Test {
public static void main(String[] args) throws Exception {
List<String> data = Arrays.asList(
"James, Beans, Chicken",
"Emily, Potato, Curry",
"Clara, Beans, Curry"
);
List<String> foodNames = Arrays.asList(
"Potato", "Chicken", "Beans", "Curry"
);
Stream<Person> persons = data.stream().map(d -> {
String[] split = d.split(",");
for(int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
}
String name = split[0];
Set<String> foods = Stream.of(split).skip(1).collect(Collectors.toSet());
return new Person(name, foods);
});
Stream<String> csvData = persons.map(p ->
p.name + ", " + p.getBooleans(foodNames)
.map(b -> b.toString())
.collect(Collectors.joining(", "))
);
csvData.forEach(System.out::println);
}
}
First of all, I highly recommend that whatever you do it in a separate class with methods like addFavoriteFood(String food) and boolean isFavoriteFood(String food) getFavorites(String food).
Personally I think the implementation of this class should contain both an instance HashSet (to hold the foods this person likes) and a SortedSet that is common to all the foods that can contain a list of ALL foods. (See notes at end)
Add would add it to both sets, getFavorites would return those in the first Hash set.
Hmm, it may also need a static getAllFavorites() method to return the SortedSet
Since your FavoiteFoods class knows the master list AND the person's favorites, you could even have it do most of the work by having a getFormattedRow() and static getFormattedHeaderRow() method. then your implementaiton is just:
System.out.println(FavoriteFoods.getFormattedHeaderRow());
for(Person person:people)
System.out.println(person.favoriteFood.getFormattedRow());
Again, the best thing here is that you can just use the Simplest Thing That Could Possibly Work for your implementation and re-do it later if need be since, being isolated in another class, it doesn't infect all your code with nasty implementation-specific sets, classes, booleans, etc.
Notes about the master list: This master list could naively be implemented as a Static but that's a bad idea--optimally the same masterList SortedSet would be passed into each instance on construction. Also since it is shared among all instances and is mutable it brings in issues if your solution is threaded!
What is so inelegant about this pseudocode?
Set<String> allFoods = new TreeSet<String>();
List<Person> allPersons = new ArrayList<Person>();
while (hasMorePersons()) {
Person person = getNextPerson();
allPersons.add(person);
allFoods.addAll(person.getFoods());
}
spreadSheet.writeHeader("Name", allFoods);
for (Person person : allPersons) {
spreadSheet.writeName(person.getName());
for (String food : allFoods) {
// assume getFoods() return a Set<String>,
// not necessarily ordered (could be a HashSet)
boolean yourBooleanHere = person.getFoods().contains(food);
spreadSheet.writeBoolean(yourBooleanHere);
}
spreadSheet.nextLine();
}
If you need a table of booleans or whatever else, you can easily store them anywhere you want during the second loop.
Note: TreeSet orders foods according to the natural order (that is, alphabetically). To output them in the order they are encountered, use a LinkedHashSet instead.

Order values by separate string

Sorry about the non-descriptive title, I couldn't think of a way to explain it better short of 100 words. What I would like to be able to do is sort a list of strings into "boxes" based on a string associated with the main string and an array of strings "order".
For my current setup I am using a HashMap to store the string and it's associated "place-in-the-order" string.
I am aware that my explanation is truly crap so I have made an image which I hope will explain it better:
The variables are initialised as follows:
private final String[] order = new String[] {"Severe", "Warning", "Info"};
private final Box[] boxes = new Box[] {new Box(1), new Box(2), new Box(3), new Box(4)};
private final Map<String, String> texts = new HashMap<String, String>();
texts.put("Server on fire!", "Severe");
texts.put("All is good!", "Info");
texts.put("Monkeys detected!", "Warning");
texts.put("Nuke activated!", "Severe");
This shouldn't be too hard to implement but the only way I can think of doing it is by using 3 loops which seems a bit wasteful and would be slow if there was large numbers of any of the inputs.
Here is some example code which will hopefully show what I have come up with so far and perhaps explain the problem, I have not tested it and don't have an IDE handy so have probably overlooked something.
Set<Box> usedBoxes = new HashSet<Box>();
for(String curOrder : order) {
for (String text : texts) {
if (texts.get(text).equals(order)) {
for (Box box : boxes) {
if (!usedBoxes.contains(box)) {
box.setText(text);
usedBoxes.add(box);
break;
}
}
}
}
}
I'm not sure I fully understand what you want to achieve, but I feel that there are two things that would make your design much simpler:
Don't use Strings for your severity levels. Use enums instead. Enums have a name, may have other fields and methods, and are naturally ordered using their order of definition. And there is no way to make a typo and introduce an unknown severity: they're type-safe
enum Severity {
SEVERE, WARNING, INFO
}
Don't store things in parallel arrays or associate them with maps. Define a class containing the information of your objects:
public class Box {
private String text;
private Severity severity;
}
Now that you have these, you can simply create a List<Box>, and sort it using a Comparator<Box> which sorts them by severity, for example:
List<Box> boxes = Arrays.asList(new Box("Server is on fire", Severity.SEVERE),
new Box("All is good", Severity.INFO),
...);
Collections.sort(boxes, new Comparator<Box>() {
#Override
public int compare(Box b1, Box b2) {
return b1.getSeverity().compareTo(b2.getSeverity());
}
}
or even simpler, with Java 8:
boxes.sort(Comparator.comparing(Box::getSeverity));
You should make your "statuses" (Severe, Info etc) into an Enum.
public enum StatusLevel {
Severe,
Warning,
Info;
}
You can then sort by StatusLevel natively as long as you define the in a top to bottom order.
If you want to supply your Box object directly insead of pulling out the StatusLevel or have a secondary sort by another property like time or alphabetically you should implement your own Comparator
Also you may want to look into SortedMap or other Map that keeps its order so you don't have to resort a HashMap every time as it does not guarantee order.

How do I add to a set based on a specific predicate in Java?

I have a set in Java containing people:
Set<Person> uniquePeople = new HashSet<Person>();
I also have a list of a ton of people (of whom some possess the same name, eg. there is more than one "Bob" in the world).
List<Person> theWorld = // ... a BIG list of people
I want to iterate through this list and add a person to the uniquePeople set if and only if their name doesn't exist in the set, eg:
for (Person person : theWorld) {
uniquePeople.add(person IFF uniquePeople.doesNotContain(person.name));
}
Is there an easy way to do this in Java? Also, Guava might do this (?) but I haven't used it at all so I would appreciate a point in the right direction.
A better option would be to abandon using a Set and instead use a Map<String, Person> (keyed off of the name).
If you want to use a set, I suggest you use a new object type (that will just contain a name and maybe a reference to a Person).
Make sure you override equals so that it will only compare the names and then you can get a set of all unique people.
You could also subclass person to override the equals to do what you want.
Sets by definition will not do what you want with just a person since they depend entirely on using equals so these are your workaround options. You could also implement (or find online) a set that takes a comparator to use instead of relying on equals but I don't think such a class exists in standard java.
Use Guava's Equivalence to wrap your objects if you don't want to (or can't) override equals and hashCode:
Set<Equivalence.Wrapper<Person>> set = Sets.newHashSet();
Equivalence<Person> personEquivalence = Equivalence.onResultOf(
new Function<Person, String>() {
#Override public String apply(Person p) {
return p.name;
}
});
set.add(personEquivalence.wrap(new Person("Joe", "Doe")));
set.add(personEquivalence.wrap(new Person("Joe", "Doe")));
set.add(personEquivalence.wrap(new Person("Jane", "Doe")));
System.out.println(set);
// [PersonEquivalence#8813f2.wrap(Person{firstName=Jane, lastName=Doe}),
// PersonEquivalence#8813f2.wrap(Person{firstName=Joe, lastName=Doe})]
#DanielWilliams has a good idea too, but using Equivalence.Wrapper is more self-documenting - after all you don't want to create new object other than wrapper.
I am not sure why people got downvoted here.
You absolutely want a Set. Not only do your requirements meet the definition and functionality of 'Set' but Set implementations are designed to quickly identify duplicates either via hash or Comparative identity.
Let's say you had a List implementation that took a deligate and a predicate:
List uniquePeople = new PredicatedList(new ArrayList(),UnqiuePersonPredicate.getInstance())
public class PredicatedList<T> implements List<T> {
private List<T> delegate = null;
private Predicate<T> predicate;
public PredicatedList<List<T> delegate, Predicate p) {
this.delegate = delegate;
this.predicate = p;
}
// implement list methods here and apply 'p' before calling your insertion functions
public boolean add(Person p) {
if(predicate.apply(p))
delegate.add(p);
}
}
For this to work you would need to have a predicate that iterates over the list to find an equal element. This is an O(N) operation. If you use HashSet then it's O(1) < n < O(N). Your amortized identity check is the load factor * N. And, usually much closer to O(1)
If you use TreeSet you will get O(log(n)) because the elements are sorted by identity and you need only log(n) time to binary search.
Define hashCode()/equals based on 'name' or whatever you want and use HashSet or use TreeSet and define Comparable/Comparator
If your return type MUST be a List then do:
Set uniquePeople = new HashSet();
uniquePeople.add(...);
List people = new LinkedList(uniquePeople);
You could do it with guava, the only thing is that Person is going to need an equals/hashcode method.
ImmutableSet<String> smallList = ImmutableSet.of("Eugene","Bob");
ImmutableSet<String> bigList = ImmutableSet.of("Eugene","Bob","Alex","Bob","Alex");
System.out.println(Iterables.concat(smallList, Sets.difference(bigList, smallList)));
//output is going to be : [Eugene, Bob, Alex]

Categories