Parsing csv via column order in spring batch - java

I've got a CSV in the format...
Kitten URL,Kitten Name,# of Reviews,Rating,Categories,,,,,,,,,,,,,
www.happykitten.com,happykittem.com,111746,7.8,Clothes & Fashion,Fashion Accessories,Ladies wear,Menswear,,,,,,,,,,
animedkitten.co.uk,Animed Kitten,33918,9.6,Pets,,,,,,,,,,,,,
So the first columns are Kitten URL,Kitten Name,# of Reviews,Rating then the rest are the possible categories listed as extra properties.
I'm trying to use Spring batch so I'm specifying the dumb object to represent this CSV. The first problem I've got is (using the example from Spring documentation I don't see how I can parse a CSV with spaces in the title. Is it possible to use Spring batch like this? Can I annotate each getting like in Hibernate with the title of the csv column?
My dumb object was going to be something like...
public class ImportDataObject {
private String kittenUrl;
private String kittenName;
private int numOfReviews;
public String getKittenUrl() {
return kittenUrl;
}
public void setKittenUrl(String kittenUrl) {
this.kittenUrl = kittenUrl;
}
public String getKittenName() {
return kittenName;
}
public void setKittenName(String kittenName) {
this.kittenName = kittenName;
}
public int getNumOfReviews() {
return numOfReviews;
}
public void setNumOfReviews(int numOfReviews) {
this.numOfReviews = numOfReviews;
}
}
I only really want to read the first 2 columns, append some strings, then persist the rest of the CSV.
I'm also considering the best approach to use for these multiple commas afterwards. Unfortunately this is how I've got the data and it's not something I can change.

You can implement FieldSetMapper for your object and then set it to your DefaultLineMapper. Your implementation of FieldSetMapper can work on positions and you can parse only first few positions and set it to your bean.
Here is suggestion based on code from URL you posted:
reader.setLineMapper(new DefaultLineMapper<ImportDataObject>());
setFieldSetMapper(new ImportDataObjectFieldSetMapper());
}});
setLinesToSkip(1); //skip header since read int will throw exception and I assume you do not need header info
}});
Then changes to POJO object to save list of categories:
public class ImportDataObject {
private String kittenUrl;
private String kittenName;
private int numOfReviews;
private int rating; //add getters and setters
private List<String> categories; //add getters and setters
public String getKittenUrl() {
return kittenUrl;
}
public void setKittenUrl(String kittenUrl) {
this.kittenUrl = kittenUrl;
}
public String getKittenName() {
return kittenName;
}
public void setKittenName(String kittenName) {
this.kittenName = kittenName;
}
public int getNumOfReviews() {
return numOfReviews;
}
public void setNumOfReviews(int numOfReviews) {
this.numOfReviews = numOfReviews;
}
}
And here is FieldSetMapper:
public class ImportDataObjectFieldSetMapper implements FieldSetMapper<ImportDataObject> {
#Override
public ImportDataObject mapFieldSet(final FieldSet fieldSet) throws BindException {
final ImportDataObject importDataObject = new ImportDataObject();
importDataObject.setKittenUrl(fieldSet.readString(0));
importDataObject.setKittenName(fieldSet.readString(1));
importDataObject.setNumOfReviews(fieldSet.readInt(2));
importDataObject.setRating(fieldSet.readInt(3));
importDataObject.setCategories(new ArrayList<String>());
for (int i = 4; i < fieldSet.getFieldCount(); i++) {
importDataObject.getCategories().add(fieldSet.readString(i));
}
return importDataObject;
}
}

Related

java : Arrange and retrieve data using HashMap and List

I have data like in following logical format:
FolderID-1
FileID-1
FileID-2
FolderID-2
FileID-3
FileID-4
FileID-5
FileID-6
FolderID-3
FileID-7
FileID-8
FileID-9
FileID-10
I have list of FileID object which have FoldeID
I need to update one field in this list and need to pass to this list in other method.
I need to get FileID object based on fileid & folderid in that method.
To achieve the same I know two way
1 HashMap<folderid,List<FileID>> OR
2 HashMap<folderid, HashMap<fileid ,FileID>
Is there any other efficient way to do the same?
Thanks for looking here.
Hi I read your cmnt you can go ahead with the string key (using fileid and folder id) that will work for you. But your data comes with a nice logical structure . file id and folder id will be unique as well as a single folder will contain file having the file id is consecutive. So, My approach to solve this entirely depends on this structure.
I made two Class FileIdObj and FolderIdObj thats contains the data redarding the file and folder respectively.
public static void fileIdBasedOnFileIdAndFolderId( List<FileIdObj> fileList)
{
Map<Integer,FolderIdObj> folderIdMap=new HashMap<Integer,FolderIdObj>();
Map<Integer,FileIdObj> fileIdMap=new HashMap<Integer,FileIdObj>();
for(int i=0;i<fileList.size();i++)
{
FileIdObj file=fileList.get(i);
fileIdMap.put(file.getFileId(), file);
int folderId=file.getFolderId();
FolderIdObj folder=new FolderIdObj();
if(folderIdMap.containsKey(folderId))
{
folder=folderIdMap.get(folderId);
folder.setEndFileId(file.getFileId());
}else
{
folder.setFolderId(folderId);
folder.setStartFileId(file.getFileId());
folder.setEndFileId(file.getFileId());
}
folderIdMap.put(folderId, folder);
}
Set<Integer> set=folderIdMap.keySet();
Iterator it=set.iterator();
while(it.hasNext())
{
FolderIdObj obj=folderIdMap.get(it.next());
System.out.println("folder id: "+obj.getFolderId()+" start fileId: "+obj.getStartFileId()+
" end fileId: "+obj.getEndFileId());
}
System.out.println();
System.out.println();
set=fileIdMap.keySet();
it=set.iterator();
while(it.hasNext())
{
FileIdObj obj=fileIdMap.get(it.next());
System.out.println("file id: "+obj.getFileId()+" folder id:"+obj.getFolderId());
}
}
the list on the argument contains the file object only.
Please see below for the details of the two class.
public class FileIdObj {
private int folderId;
private int fileId;
public int getFolderId() {
return folderId;
}
public void setFolderId(int folderId) {
this.folderId = folderId;
}
public int getFileId() {
return fileId;
}
public void setFileId(int fileId) {
this.fileId = fileId;
}
}
public class FolderIdObj {
private int folderId;
private int startFileId;
private int endFileId;
public int getFolderId() {
return folderId;
}
public void setFolderId(int folderId) {
this.folderId = folderId;
}
public int getStartFileId() {
return startFileId;
}
public void setStartFileId(int startFileId) {
this.startFileId = startFileId;
}
public int getEndFileId() {
return endFileId;
}
public void setEndFileId(int endFileId) {
this.endFileId = endFileId;
}
}

Looping over rest of tab while creating object Java

Sorry for my title which is unclear, but I don't know how to say it differently.
I have an object "Rapport" which takes a couple of parameters (3 Strings and one Object). This Object "SortingParameter" takes a number of boolean arguments "boolean... args". My goal is to read lines in a text file, create an ArrayList made of these "Rapport" objects. For this, I loop through the lines:
for (String line : Files.readAllLines(Paths.get("myTxt.txt"),Charset.forName("ISO-8859-1"))) {
String[] split = line.split(";");
if(split.length>3){
rapports.add(new Rapport(split[0],split[1],split[2],new SortingParameter(PROBLEM)));
}else{
rapports.add(new Rapport(split[0],split[1],split[2]));
}
I would like to add the rest of my split[ ] tab in the object dynamically. Does anybody know how this could be cleanly done?
The rest of my code:
Rapport.java
package model;
import static model.Constant.RESSOURCES;
public class Rapport {
private String nomListe;
private String nomRessource;
private String categorie;
private SortingParameter param;
private boolean hasParameter;
public Rapport(String nomListe, String nomRessource, String categorie, SortingParameter param){
this.nomListe = nomListe;this.nomRessource = RESSOURCES + nomRessource;
this.categorie = categorie;this.param = param;
this.hasParameter = true;
}
public Rapport(String nomListe, String nomRessource, String categorie){
this.nomListe = nomListe; this.nomRessource = RESSOURCES + nomRessource;
this.categorie = categorie; this.param = null; this.hasParameter = false;
}
/** Getters **/
public String getNomListe(){return nomListe;}
public String getNomRessource(){return nomRessource;}
public String getCategorie(){return categorie;}
public boolean hasParameter(){return hasParameter;}
public SortingParameter getParam(){return param;}
}
And my SortingParameter.java:
package model;
import java.util.ArrayList;
public class SortingParameter {
private ArrayList<Boolean> paramList;
public SortingParameter(boolean... args){
paramList = new ArrayList<>();
for (boolean arg : args) {
paramList.add(arg);
}
}
public ArrayList<Boolean> getParamList(){return paramList;}
}
As you say, you want to add the rest of your split[]-tab to a Rapport-instance.
Just provide a String-Array as an attribute in your Rapport-class:
public class Rapport {
//some attributes...
private String[] eitherColumns;
//getters and setters and so on....
}
Then, when you read a line from the file, use a setter or another constructor to add the rest of the tabs to your Rapport-Instance.
You can also use a separate constructor which only gets the String-Array read from the file and let the class decide what to do with the array-elements which would be more convenient so separate your business logic from your data.

ArrayList unintentionally appended at the end of GSON String each time I rotate the screen

I am trying to save a custom class containing ArrayLists to SharedPreferences using GSON. Each time I rotate the screen, the activity starts over and the string generated by gson seems to append the ArrayList to the previous string, instead of replacing it.
The SimpleUserValues class is just a class to store user info, with an empty constructor and private ArrayLists together with their corresponding setters and getters.
The code in my activity is as follows:
SimpleUserValues simpleUserValues = SimpleUserValues.newInstance();
simpleUserValues.setTheBooleans(userValues.getTheBooleans());
Gson gson = new Gson();
Type classType = new TypeToken<SimpleUserValues>() {
}.getType();
String insertedJSON = gson.toJson(simpleUserValues, classType);
if (getSharedPreferences(myAppKey, MODE_PRIVATE).contains("JSON")) {
Log.e("insertedJSON ", getSharedPreferences(myAppKey, MODE_PRIVATE)
.getString("JSON", ""));
getSharedPreferences(myAppKey, MODE_PRIVATE).edit().remove("JSON")
.commit();
getSharedPreferences(myAppKey, MODE_PRIVATE).edit()
.putString("JSON", insertedJSON).commit();
} else {
getSharedPreferences(myAppKey, MODE_PRIVATE).edit()
.putString("JSON", insertedJSON).commit();
}
The output I see from Log.e is like this:
insertedJSON﹕ {"theBooleans":[true,true,true,true,true,true],...
insertedJSON﹕ {"theBooleans":[true,true,true,true,true,true,true,true,true,true,true,true],...
insertedJSON﹕ {"theBooleans":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true],...
insertedJSON﹕ {"theBooleans":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true],...
Any ideas / known issues ? I am even deleting the previously saved string before writing, so I cannot understand how this appending is coming from. The input into SimpleUserValues is always the same. Thanks in advance :)
EDIT: here it is the custom class SimpleUserValues:
public class SimpleUserValues {
private int theCurrentFragment;
private int theCurrentGraph;
private ArrayList<Boolean> theBooleans;
private ArrayList<Integer> theIntegers;
public SimpleUserValues() {
}
public static SimpleUserValues newInstance(){
return new SimpleUserValues();
}
public ArrayList<Boolean> getTheBooleans() {
return theBooleans;
}
public void setTheBooleans(ArrayList<Boolean> theBooleans_) {
theBooleans = theBooleans_;
}
public ArrayList<Integer> getTheIntegers() {
return theIntegers;
}
public void setTheIntegers(ArrayList<Integer> theIntegers_) {
theIntegers = theIntegers_;
}
public int getTheCurrentFragment() {
return theCurrentFragment;
}
public void setTheCurrentFragment(int theCurrentFragment_) {
theCurrentFragment = theCurrentFragment_;
}
public int getTheCurrentGraph() {
return theCurrentGraph;
}
public void setTheCurrentGraph(int theCurrentGraph_) {
theCurrentGraph = theCurrentGraph_;
}
}
I had a bug in the arrays I was saving, adding more rows instead of replacing. Oops. Thanks #Kane for your comment

TableView doesn't refresh

I've got a project written in JavaFX and I'm trying to get a refresh on a tableview without result.
I've googled around and tried some examples I've found but it still doesn't work.
I populate a tableview with information each row in this table can have new comments added to by double click on the row. The a new Tabpane is opened and the new comment can be added there. On close of this tabpane I'd like the one I clicked from to be refreshed.
I must be doing something wrong. I just don't know what.
In my StoreController
private void populateTableView(List<Store> stores) {
ObservableList<Store> data = FXCollections.observableArrayList(stores);
storeNumberColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("id"));
storePhoneColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("phoneNbr"));
chainColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("chainId"));
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Store, ImageView>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Store, ImageView> p) {
Integer numberOfComments = p.getValue().getCommentsCount();
ReadOnlyObjectWrapper wrapper = null;
if (numberOfComments == 0) {
wrapper = null;
} else if (numberOfComments == 1) {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_SINGLE_FLAG_SOURCE));
} else {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_DOUBLE_FLAG_SOURCE));
}
return wrapper;
}
});
storeTable.setItems(data);
sortTable(storeTable, missedColumn);
}
#FXML
public void handleTableAction(MouseEvent event) {
if (event.getClickCount() == 2) {
showNewCommentStage();
}
}
private void showNewCommentStage() {
initCommentController();
Store store
= storeTable.getSelectionModel().selectedItemProperty().getValue();
commentController.showNewStage(commentPane, store);
}
It seems like the call-function doesn't get called when the commentpane is closed.
CommentController
public void showNewStage(Pane pane, Store store) {
this.store = store;
initStage(pane);
windowHandler = new WindowHandler(stage);
effectHandler.playEffect(pane);
constructCommentHeaders();
List<Comment> comments;
comments = commentService.listByStoreId(store.getId());
populateCommentTable(comments);
}
Like I said I've tried a lot of the solutions found here on Stackoverflow but with no results. The Tableview doesn't refresh. The Stores and the Comments are in different database tables if that's important
Can someone point me in the right direction?
Thanks!
****EDIT****
The Store.class
public class Store extends CommentEntity {
private String id;
private String chainId;
private String phoneNbr;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getPhoneNbr() {
return phoneNbr;
}
public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
#Override
public String toString() {
return "Store{" + "id=" + id + ", chainId=" + chainId + '}';
}
#Override
public String getCommentIdentifier() {
return id;
}
}
The CommentEntity.Class
public abstract class CommentEntity {
private int commentsCount;
public int getCommentsCount() {
return commentsCount;
}
public void setCommentsCount(int commentsCount) {
this.commentsCount = commentsCount;
}
public abstract String getCommentIdentifier();
}
Thank you for input, I hadn't even reflected over the ImageView / String.
Two issues:
First, you need to distinguish between the data the cells in your column are displaying, and the cells that actually display those data. The cellValueFactory determines the data that are displayed. The PropertyValueFactory is a cellValueFactory implementation that references a JavaFX Property, so when you call
storeNumberColumn.setCellValueFactory(new PropertyValueFactory<Store, String>("id"));
it effectively tells the cells in the storeNumberColumn to call the idProperty() method on the Store object in the current row to get the data for the cell. (If no such method exists, it will try to use getId() as a backup plan.)
By default, you get a cellFactory that displays text resulting from calling toString() on the data generated by the cellValueFactory. In the case where your data are simply Strings, this is usually what you need. In other cases, you often need to provide a cellFactory of your own to get the correct way to display the data.
In your case, the data for the commentColumn are simply the number of comments. You are going to display that by choosing an image based on that numeric value.
So you should have
TableColumn<Store, Number> commentColumn = new TableColumn<>("Comments");
For the cellValueFactory, you can just use
commentColumn.setCellValueFactory(new PropertyValueFactory<>("commentsCount"));
Then you need a cellFactory that displays the appropriate ImageView:
commentColumn.setCellFactory(new Callback<TableColumn<Store, Number>, new TableCell<Store, Number>>() {
#Override
public TableCell<Store, Number>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(Number numberOfComments, boolean empty) {
super.updateItem(count, empty) ;
if (empty) {
setGraphic(null);
} else {
if (numberOfComments.intValue() == 0) {
setGraphic(null);
} else if (numberOfComments.intValue() == 1) {
imageView.setImage(new Image(COMMENT_SINGLE_FLAG_SOURCE));
setGraphic(imageView);
} else {
imageView.setImage(new Image(COMMENT_DOUBLE_FLAG_SOURCE));
setGraphic(imageView);
}
}
}
}
});
The second issue is actually about the update. A TableView keeps its contents "live" by observing JavaFX properties that are provided by the cellValueFactory as ObservableValues. If the value might change while the table is displayed, you must provide an actual property that can be observed: using a ReadOnlyObjectWrapper is no good (because it's read only, so it's wrapped value will not change). The PropertyValueFactory will also return a ReadOnlyObjectWrapper if you do not have JavaFX property accessor methods (i.e. if it is only using getXXX() methods to access the data). So your model class must provide JavaFX Properties.
You can make an immediate fix to this by updating CommentEntity to use an IntegerProperty:
public abstract class CommentEntity {
private final IntegerProperty commentsCount = new SimpleIntegerProperty();
public final int getCommentsCount() {
return commentsCountProperty().get();
}
public final void setCommentsCount(int commentsCount) {
commentsCountProperty().set(commentsCount);
}
public IntegerProperty commensCountProperty() {
return commentsCount ;
}
public abstract String getCommentIdentifier();
}
I would also strongly recommend updating the Store class to use JavaFX Properties in a similar manner.

Creating a list out of my DTO

I have a year object. For now lets say only two years and its getters and setters
private String mYearOne;
private String mYearTwo;
public String getmYearOne() {
return mYearOne; }
public void setmYearOne(String mYearOne) {
this.mYearOne = mYearOne; }
public String getmYearTwo() {
return mYearTwo; }
public void setmYearTwo(String mYearTwo) {
this.mYearTwo = mYearTwo; }
Then each year has three insurance plans. And its getters and setters.
private String healthPlan;
private String carPlan;
private String housePlan;
private String healthPlanTwo;
private String carPlanTwo;
private String housePlanTwo;
public String getHealthPlan() {
return healthPlan; }
public void setHealthPlan(String healthPlan) {
this.healthPlan = healthPlan; }
public String getCarPlan() {
return carPlan; }
public void setCarPlan(String carPlan) {
this.carPlan = carPlan; }
public String getHousePlan() {
return housePlan; }
public void setHousePlan(String housePlan) {
this.housePlan = housePlan; }
public String getHealthPlan() { //For the second year
return healthPlan; }
public void setHealthPlan(String healthPlan) {
this.healthPlan = healthPlan; }
public String getCarPlan() {
return carPlan; }
public void setCarPlan(String carPlan) {
this.carPlan = carPlan; }
public String getHousePlan() {
return housePlan; }
public void setHousePlan(String housePlan) {
this.housePlan = housePlan; }
public String getHealthPlanTwo() {
return healthPlanTwo; }
public void setHealthPlanTwo(String healthPlanTwo) {
this.healthPlanTwo = healthPlanTwo; }
public String getCarPlanTwo() {
return carPlanTwo; }
public void setCarPlanTwo(String carPlanTwo) {
this.carPlanTwo = carPlanTwo; }
public String getHousePlanTwo() {
return housePlanTwo; }
public void setHousePlanTwo(String housePlanTwo) {
this.housePlanTwo = housePlanTwo; }
You will notice the code is bulky. I need to define them in a <list> of year. So that if 10 years are considered, I would have 10 multiplied
by 3 = 30 plans and its getters and setters respectively.
How could this be done?
I think your best bet will be to maintain a count of number of years and arraylists for the insurance plans. This way, you can get the arraylist once and get the insurance plan details for whatever year you actually want. This will be characterized by a single insurance plan arraylist and a single arraylist for years.
private ArrayList mYear;
private ArrayList healthPlan;
private ArrayList carPlan;
private ArrayList housePlan;
public String getHousePlanForYear(String year){
return housePlan.get(mYear.indexOf(year));
}
public void setHousePlanForYear(String housePlan, String year){
this.housePlan.set(mYear.indexOf(year), housePlan);
}
Similarly for the other plans. Of course, all this is assuming that the year is always present and other boundary conditions. Just add your boundary checks in these getters and setters and you will be good to go. :)
I see a design/domain modelling problem here. A person can ideally have multiple "plans" and "riders" attached to each plan. This should clearly be abstracted away properly by creating a "PlanCollection" class or simply maintaining a list of "plans" which all extend/implement a common "Plan" class/interface.
Each plan can have a "plan" duration and a start date. Also, logically, you don't attach plans to "year" but the timeline information is encapsulated in the Plan itself (like start and duration as mentioned above).
Take a look at enums and maps. The enum would specify car, house etc.
You could create a map that takes an enum as key and a List of years as the key. Don't be tempted to create YearThree etc.
On a note of style: if you intend to use m to prefix fields, take the m out of the setter. E.g. setYearOne not setmYearOne.
Choose your types wisely, don't use a String if an int is better.

Categories