I basically am trying to populate a table with a class that contains a string field and a map with arraylist as value.
EDIT: So what I want in the table is is something like this:
Student name
Science
Bro Man
Passs
---
Excellent
The class
public class ItemList {
private String studentNameList;
private HashMap<String, ArrayList<SimpleStringProperty>> remarksList;
public String getStudentNameList() {
return studentNameList;
}
public void setStudentNameList(String studentNameList) {
this.studentNameList = studentNameList;
}
public HashMap<String, ArrayList<SimpleStringProperty>> getRemarksList() {
return remarksList;
}
public void setRemarksList(HashMap<String, ArrayList<SimpleStringProperty>> remarksList) {
this.remarksList = remarksList;
}
}
The controller class has a tableview that will hold the class entries
I have a loadTable method that loads the class into the tableview
The controller class
public class HelloController implements Initializable {
#FXML
private TableView tvTable;
// Observable object to hold the classes
private ObservableList<Item> tablelist = FXCollections.observableArrayList();
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// Item with arraylist as value
ItemList itemlist = new ItemList();
itemlist.setStudentNameList("Bro Man");
HashMap <String, ArrayList<SimpleStringProperty>> remarks = new HashMap<>();
ArrayList<SimpleStringProperty> remarksString = new ArrayList<>();
remarksString.add(new SimpleStringProperty("Pass"));
remarksString.add(new SimpleStringProperty("Excellent"));
remarks.put("Science",remarksString);
itemlist.setRemarksList(remarks);
tablelist.add(itemlist);
Method to load table call
loadTableList(tablelist);
}
The method
private void loadTableList(ObservableList<ItemList> remarks) {
tvTable.setItems(remarks);
tvTable.setVisible(true);
TableColumn<ItemList, String> column = new TableColumn<ItemList, String>("student name");
column.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue().getStudentNameList()));
tvTable.getColumns().add(column);
// The looping through the hashmap containing arraylist value
for (Map.Entry<String, ArrayList<SimpleStringProperty>> entry : remarks.get(0).getRemarksList().entrySet()) {
// Using the key of the map as column name
TableColumn<ItemList, Number> col = new TableColumn<ItemList, Number>(entry.getKey());
// What should the code here contains to loop through the arraylist of the values and use as the rows
}
}
How do I loop through the hashmap arraylist
Maybe the beginning of the loop itself NOT right
I might need a better solution
Related
I'm trying to create an ObservableMap that has ObservableList of custom objects as a value like this
Map<String, ObservableList<MyClass>> myMap = new HashMap<>();
ObservableMap<String, ObservableList<MyClass>> myObservableMap = FXCollections.observableMap(myMap);
After searching about binding of ObservableMap to tableview, I haven't found how it would be if the value is a collection. All that I've seen is binding of observable map that has a value of a custom class. I'm thinking that it should not be so different as I would only want the values of the keys to be binded to the tableview and keep track of the changes that would happen to the key and the values inside it.
Here's what I've done so far:
Map<String, ObservableList<DMSchedule>> myMap = new HashMap<>();
ObservableMap<String, ObservableList<DMSchedule>> myObservableMap = FXCollections.observableMap(myMap);
TableColumn<DMSchedule, String> dayColumn;
TableColumn<DMSchedule, String> subjectNameColumn;
TableColumn<DMSchedule, String> startTimeColumn;
TableColumn<DMSchedule, String> endTimeColumn;
#FXML
private TableView<DMSchedule> myTable;
#Override
public void initialize(URL url, ResourceBundle rb) {
final ListChangeListener<DMSchedule> listListener = new ListChangeListener<DMSchedule>() {
#Override
public void onChanged(ListChangeListener.Change<? extends DMSchedule> change) {
System.out.println("Detected a change!");
while (change.next()) {
System.out.println(change.getFrom());
System.out.println("Added on list? " + change.wasAdded());
System.out.println("Removed on list? " + change.wasRemoved());
}
}
};
dayColumn = new TableColumn<DMSchedule, String>("Day");
subjectNameColumn = new TableColumn<DMSchedule, String>("Subject");
startTimeColumn = new TableColumn<DMSchedule, String>("Start Time");
endTimeColumn = new TableColumn<DMSchedule, String>("End Time");
myObservableMap.addListener((MapChangeListener<String, ObservableList<DMSchedule>>) mapChange -> {
if (mapChange.wasAdded()) {
mapChange.getValueAdded().addListener(listListener);
}
if (mapChange.wasRemoved()) {
mapChange.getValueRemoved().addListener(listListener);
}
});
myObservableMap.put("M", FXCollections.observableArrayList());
myObservableMap.get("M").add(new DMSchedule("M", "07:00", "08:30", "testSubj", "testSection"));
myListOfMap.addAll(myObservableMap);
myTable.getColumns().addAll(dayColumn, subjectNameColumn, startTimeColumn, endTimeColumn);
myTable.setItems(myObservableMap);
And I'm kind of lost how will I set my ObservableList values to the table and columns.
I would want to interact with the table the same way as if I'm binding an ObservableList to the tableview. Like selecting tablerow would return MyClass objects(assuming that a row factory has been made). I have already figured out how to add listeners for the change in value of keys and values inside a map. The only problem that I have left is to bind the ObservableList<MyClass> of each keys inside myObservableMap. How could I possibly do it? I hope that you could help me. Thanks!
I've tried to make my code use generics, but I can't seem to get it to work using generics. Any help would be appreciated.
I have 3 classes: Classroom, Course, Teacher
I have the following working code 3 times: (With the small change of the class)
private ObservableList<Classroom> parseClassrooms() {
// create new Observable List
ObservableList<Classroom> classrooms = FXCollections.observableArrayList();
// get lines from file;
ArrayList<String> arrayList = fhClassroom.read();
for (String line : arrayList) {
classrooms.add(Classroom.fromString(line));
}
return classrooms;
}
Methods in my Classes:
#Override
public String toString() {
return name;
}
public static Classroom fromString(String line) {
return new Classroom(line);
}
Is it possible to make this method generic? and pass the class as parameter?
I would like something like the following:
private ObservableList<T> parseClassrooms(T, FileHelper fh) {
// create new Observable List
ObservableList<T> items = FXCollections.observableArrayList();
// get lines from file;
ArrayList<String> arrayList = fh.read();
for (String line : arrayList) {
items.add(T.fromString(line));
}
return items;
}
My best attempt:
import java.util.ArrayList;
import java.util.function.Function;
public class Helper {
public static <T> ObservableList<T> parseItems(Function<String, T> lineToItemFunction, FileHelper fh) {
// create new Observable List
ObservableList<T> items = FXCollections.observableArrayList();
// get lines from file;
ArrayList<String> arrayList = fh.read();
for (String line : arrayList) {
items.add(lineToItemFunction.apply(line));
}
return items;
}
}
And you call it this way:
ObservableList<ClassRoom> classRooms = Helper.parseItems(ClassRoom::fromLine, fileHelper);
I'm trying to push my resultset data onto a nested map. Honestly, I've been struggling with the logic of how to do it. Here's a sample of my resultset data,
ID Main Sub
1 Root Carrots
2 Root Beets
3 Root Turnips
4 Leafy Spinach
5 Leafy Celery
6 Fruits Apples
7 Fruits Oranges
I created a hashmap HashMap<Integer, HashMap<String, List<String>>>, in which I thought the innermap could hold the main col as key and the corresponding subs as the list of values. The outermap would contain the id as the key and the corresponding map as the value. I'm struggling to achieve this.
Any help would be appreciated.
I would suggest using a different structure.
You have unique Id's and sub, but your Main can be duplicate.
Thus I would suggest using the following structure:
HashMap>
where POJO has ID and sub.
the key of map would be main.
Thus you can easily do:
if (map.get(main)==null){
List<POJO> pojoList= new List<>();
pojolist.add(pojo);
}else{
List<POJO> pojoList=map.get(main);
pojoList.add(pojo);
}
But it ultimately depends if you need to do lookup using ID or main.
Below is the answer to your question. But the question is probably wrong. Since the ID is unique (just a guess) you're probably looking for
Map<Integer, DataObject> map = new HashMap<>();
where DataObject is a POJO containing the variabels main and sub. Adding data to such a structure is easy.
Answer to question (added to show you how Maps and Lists work):
private Map<Integer, Map<String, List<String>>> map = new HashMap<>();
public static void main(String[] args) {
new Tester().go();
}
private void go() {
add(1, "Root", "Carrots");
add(2, "Root", "Beets");
add(3, "Root", "Turnips");
add(4, "Leafy", "Spinach");
add(5, "Leafy", "Celery");
add(6, "Fruits", "Apples");
add(7, "Fruits", "Oranges");
}
private void add(int id, String main, String sub) {
if (!map.containsKey(id)) {
map.put(id, new HashMap<String, List<String>>());
}
ArrayList<String> list = new ArrayList<String>();
list.add(sub);
map.get(id).put(main, list);
}
There is no need to make nested hash maps because each row in the example is unique (each List in nested map will have only one value).
In any case here is algorithm example in Java 8 style for your particular need :
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<ResultSet> rows = new ArrayList<>();
rows.add(new ResultSet().setId(1).setMain("Root").setSub("Carrots"));
rows.add(new ResultSet().setId(2).setMain("Root").setSub("Beets"));
rows.add(new ResultSet().setId(3).setMain("Root").setSub("Turnips"));
rows.add(new ResultSet().setId(4).setMain("Leafy").setSub("Spinach"));
rows.add(new ResultSet().setId(5).setMain("Leafy").setSub("Celery"));
rows.add(new ResultSet().setId(6).setMain("Fruits").setSub("Apples"));
rows.add(new ResultSet().setId(7).setMain("Fruits").setSub("Oranges"));
HashMap<Integer, HashMap<String, List<String>>> result = new HashMap<>();
rows.forEach(row -> {
HashMap<String, List<String>> subsByMain = result.getOrDefault(row.getId(), new HashMap<>());
List<String> subs = subsByMain.getOrDefault(row.getMain(), new ArrayList<>());
subs.add(row.getSub());
subsByMain.put(row.getMain(), subs);
result.put(row.getId(), subsByMain);
});
}
static class ResultSet {
private Integer id;
private String main;
private String sub;
Integer getId() {
return id;
}
ResultSet setId(Integer id) {
this.id = id;
return this;
}
String getMain() {
return main;
}
ResultSet setMain(String main) {
this.main = main;
return this;
}
String getSub() {
return sub;
}
ResultSet setSub(String sub) {
this.sub = sub;
return this;
}
}
}
I have a list of a map of strings:
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
This gets populated with the following:
Map<String, String> action1 = new LinkedHashMap<>();
map.put("name", "CreateFirstName");
map.put("nextAction", "CreateLastName");
Map<String, String> action2 = new LinkedHashMap<>();
map.put("name", "CreateAddress");
map.put("nextAction", "CreateEmail");
Map<String, String> action3 = new LinkedHashMap<>();
map.put("name", "CreateLastName");
map.put("nextAction", "CreateAddress");
Map<String, String> action4 = new LinkedHashMap<>();
map.put("name", "CreateEmail");
list.add(action1);
list.add(action2);
list.add(action3);
list.add(action4);
action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action?
Question: How can I sort my list, so that the actions are in order?
ie: the nextAction of an action, is the same as the name of the next action in the list.
Although this seems to be a case of the XY-Problem, and this list of maps is certainly not a "nicely designed data model", and there is likely a representation that is "better" in many ways (although nobody can give recommendations about what the "best" model could be, as long as the overall goal is not known), this is the task that you have at hand, and here is how it could be solved:
First of all, you have to determine the first element of the sorted list. This is exactly the map that has a "name" entry that does not appear as the "nextAction" entry of any other map.
After you have this first map, you can add it to the (sorted) list. Then, determining the next element boils down to finding the map whose "name" is the same as the "nextAction" of the previous map. To quickly find these successors, you can build a map that maps each "name" entry to the map itself.
Here is a basic implementation of this sorting approach:
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SortListWithMaps
{
public static void main(String[] args)
{
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
Map<String, String> action1 = new LinkedHashMap<>();
action1.put("name", "CreateFirstName");
action1.put("nextAction", "CreateLastName");
Map<String, String> action2 = new LinkedHashMap<>();
action2.put("name", "CreateAddress");
action2.put("nextAction", "CreateEmail");
Map<String, String> action3 = new LinkedHashMap<>();
action3.put("name", "CreateLastName");
action3.put("nextAction", "CreateAddress");
Map<String, String> action4 = new LinkedHashMap<>();
action4.put("name", "CreateEmail");
list.add(action1);
list.add(action2);
list.add(action3);
list.add(action4);
// Make it a bit more interesting...
Collections.shuffle(list);
System.out.println("Before sorting");
for (Map<String, String> map : list)
{
System.out.println(map);
}
List<Map<String, String>> sortedList = sort(list);
System.out.println("After sorting");
for (Map<String, String> map : sortedList)
{
System.out.println(map);
}
}
private static List<Map<String, String>> sort(
List<Map<String, String>> list)
{
// Compute a map from "name" to the actual map
Map<String, Map<String, String>> nameToMap =
new LinkedHashMap<String, Map<String,String>>();
for (Map<String, String> map : list)
{
String name = map.get("name");
nameToMap.put(name, map);
}
// Determine the first element for the sorted list. For that,
// create the set of all names, and remove all of them that
// appear as the "nextAction" of another entry
Set<String> names =
new LinkedHashSet<String>(nameToMap.keySet());
for (Map<String, String> map : list)
{
String nextAction = map.get("nextAction");
names.remove(nextAction);
}
if (names.size() != 1)
{
System.out.println("Multiple possible first elements: " + names);
return null;
}
// Insert the elements, in sorted order, into the result list
List<Map<String, String>> result =
new ArrayList<Map<String, String>>();
String currentName = names.iterator().next();
while (currentName != null)
{
Map<String, String> element = nameToMap.get(currentName);
result.add(element);
currentName = element.get("nextAction");
}
return result;
}
}
Instead of using a Map to store the properties of an action (the name and the nextAction), create your own type that's composed of those properties:
class Action {
private String name;
//nextAction
public void perform() {
//do current action
//use nextAction to perform the next action
}
}
The nextAction can now be a reference to the next action:
abstract class Action implements Action {
private String name;
private Action nextAction;
public Action(String name) {
this.name = name;
}
public final void perform() {
perform(name);
nextAction.perform();
}
protected abstract void perform(String name);
}
You can now create your actions by subtyping the Action class:
class CreateFirstName extends Action {
public CreateFirstName(Action nextAction) {
super("CreateFirstName", nextAction);
}
protected final void perform(String name) {
System.out.println("Performing " + name);
}
}
And chain them together:
Action action = new CreateFirstName(new CreateLastName(new CreateEmail(...)));
The nested expressions can get pretty messy, but we'll get to that later. There's a bigger problem here.
action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action
The same problem applies to the code above.
Right now, every action must have a next action, due to the constructor Action(String, Action). We could take the easy route and pass in a placeholder for no next action (null being the easiest route):
class End extends Action {
public End() {
super("", null);
}
}
And do a null check:
//class Action
public void perform() {
perform(name);
if(nextAction != null) {
nextAction.perform(); //performs next action
}
}
But this would be a code smell. You can stop reading here and use the simple fix, or continue below for the more involved (and educational) route.
There's a good chance that when you do use null, you're falling victim to a code smell. Although it doesn't apply to all cases (due to Java's poor null safety), you should try to avoid null if possible. Instead, rethink your design as in this example. If all else fails, use Optional.
The last action is not the same as the other actions. It can still perform like the other, but it has different property requirements.
This means they could both share the same behavior abstraction, but must differ when it comes to defining properties:
interface Action {
void perform();
}
abstract class ContinuousAction implements Action {
private String name;
private Action nextAction;
public ContinuousAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
nextAction.perform();
}
protected abstract void perform(String name);
}
abstract class PlainAction implements Action {
private String name;
public PlainAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
}
protected abstract void perform(String name);
}
The last action would extend PlainAction, while the others would extend ContinuousAction.
Lastly, to prevent long chains:
new First(new Second(new Third(new Fourth(new Fifth(new Sixth(new Seventh(new Eighth(new Ninth(new Tenth())))))))))
You could specify the next action within each concrete action:
class CreateFirstName extends ContinuousAction {
public CreateFirstName() {
super("CreateFirstName", new CreateLastName());
}
//...
}
class CreateLastName extends ContinuousAction {
public CreateLastName() {
super("CreateLastName", new CreateEmail());
}
//...
}
class CreateEmail extends PlainAction {
public CreateEmail() {
super("CreateEmail");
}
//...
}
The ContinuousAction and PlainAction can be abstracted further. They are both named actions (they have names), and that property affects their contract in the samw way (passing it to the template method process(String)):
abstract class NamedAction implements Action {
private String name;
public NamedAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
}
protected abstract void perform(String name);
}
//class ContinuousAction extends NamedAction
//class PlainAction extends NamedAction
I have the following ObservableMap that I would like to display in a TableView:
private ObservableMap<String, Shape> myShapes = FXCollections.observableHashMap();
Where Shape is defined as follows:
public class Shape {
private StringProperty areaFormula = new SimpleStringProperty();
private IntegerProperty numSides = new SimpleIntegerProperty();
public Shape(String areaFormula, int numSides)
{
this.areaFormula.set(areaFormula);
this.numSides.set(numSides);
}
public String getAreaFormula() { return areaFormula.get(); }
public void setAreaFormula(String areaFormula) { this.areaFormula.set(areaFormula); }
public StringProperty areaFormulaProperty() { return this.areaFormula; }
public int getNumSides() { return numSides.get(); }
public void setNumSides(int sides) { this.numSides.set(sides); }
public IntegerProperty numSidesProperty() { return this.numSides; }
}
I would like the first column of the TableView to be the Map Key (call it key), the second column to be myShapes.get(key).getAreaFormula(), and the third column to be myShapes.get(key).getnumSides(). And I would like the TableView to automatically update when the Map is changed.
It would also be really nice to make the second and third columns of the TableView editable by the user, if possible, with those edits being updated in the Map.
I have already asked stackoverflow about populating a TableView with an ObservableMap such that it will update on changes here:
Populating a TableView with a HashMap that will update when HashMap changes . However as you can see in the discussion there, it does not handle a custom class like I have here.
Here is my solution so far:
ObservableMap<String, Shape> map = FXCollections.observableHashMap();
ObservableList<String> keys = FXCollections.observableArrayList();
map.addListener((MapChangeListener.Change<? extends String, ? extends Shape> change) -> {
boolean removed = change.wasRemoved();
if (removed != change.wasAdded()) {
// no put for existing key
if (removed) {
keys.remove(change.getKey());
} else {
keys.add(change.getKey());
}
}
});
map.put("square", new Shape("L^2", 4));
map.put("rectangle", new Shape("LW", 4));
map.put("triangle", new Shape("0.5HB", 3));
final TableView<String> table = new TableView<>(keys);
TableColumn<String, String> column1 = new TableColumn<>("Key");
column1.setCellValueFactory(cd -> Bindings.createStringBinding(() -> cd.getValue()));
TableColumn<String, String> column2 = new TableColumn<>("Value");
column2.setCellValueFactory( ... );
table.getColumns().setAll(column1, column2);
The place where I'm stuck is the second-to-last line column2.setCellValueFactory( ... ). I would like column2 to display the Shape's getAreaFormula() SimpleStringProperty but I don't know how to set up the CellValueFactory to do so.
Thanks for your help.
It's easiest, if you create a list of immutable key-value pairs instead of simply a list of keys. This drastically reduces the complexity of the listeners you need to use, since the MapChangeListener will always replace an item, if a value is updated and you don't need to add listeners to the Map in the cellValueFactorys:
Key Value pair class
public final class MapEntry<K, V> {
private final K key;
private final V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
#Override
public boolean equals(Object obj) {
// check equality only based on keys
if (obj instanceof MapEntry) {
MapEntry<?, ?> other = (MapEntry<?, ?>) obj;
return Objects.equals(key, other.key);
} else {
return false;
}
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
Table creation
ObservableMap<String, Shape> map = FXCollections.observableHashMap();
ObservableList<MapEntry<String, Shape>> entries = FXCollections.observableArrayList();
map.addListener((MapChangeListener.Change<? extends String, ? extends Shape> change) -> {
boolean removed = change.wasRemoved();
if (removed != change.wasAdded()) {
if (removed) {
// no put for existing key
// remove pair completely
entries.remove(new MapEntry<>(change.getKey(), (Shape) null));
} else {
// add new entry
entries.add(new MapEntry<>(change.getKey(), change.getValueAdded()));
}
} else {
// replace existing entry
MapEntry<String, Shape> entry = new MapEntry<>(change.getKey(), change.getValueAdded());
int index = entries.indexOf(entry);
entries.set(index, entry);
}
});
map.put("one", new Shape("a", 1));
map.put("two", new Shape("b", 2));
map.put("three", new Shape("c", 3));
final TableView<MapEntry<String, Shape>> table = new TableView<>(entries);
TableColumn<MapEntry<String, Shape>, String> column1 = new TableColumn<>("Key");
// display item value (= constant)
column1.setCellValueFactory(cd -> Bindings.createStringBinding(() -> cd.getValue().getKey()));
TableColumn<MapEntry<String, Shape>, String> column2 = new TableColumn<>("formula");
column2.setCellValueFactory(cd -> cd.getValue().getValue().areaFormulaProperty());
TableColumn<MapEntry<String, Shape>, Number> column3 = new TableColumn<>("sides");
column3.setCellValueFactory(cd -> cd.getValue().getValue().numSidesProperty());
table.getColumns().setAll(column1, column2, column3);