I have a requirement to write Java program where I should read daily inventory feeds from suppliers, extract the data and add to the database. The feeds are different for each supplier and only in csv, txt or excel formats. Some suppliers may have extra columns in the feed, but they all guarantee to provide one column containing the product ID and one column containing the quantity (whose column indices can vary from supplier to supplier). Each product ID appears only once in a file. Part of the problem statement is dealing with data, sometimes placed in different positions within the feed for various suppliers.
I wrote below code assuming the columns are always named “product” and “quantity”. But any suggestions on how to handle this part of problem in a better way so that it is flexible to handle continued increase in the number of suppliers and their feeds in the future?
package com.file.handling;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Scanner;
public class Inventory {
private ArrayList<String> fileNames = new ArrayList<>();
public Inventory(ArrayList<String> fileNames) {
this.fileNames = fileNames;
}
/**
* This is the main method that initiates file read to get data
* #return Nothing.
* #exception FileNotFoundException On file not found.
* #see FileNotFoundException
*/
public void addItems(){
for (String filename: fileNames){
try{
URL url = getClass().getResource(filename);
File myObj = new File(url.getPath());
Scanner myReader = new Scanner(myObj);
ArrayList<String> lines = new ArrayList<>();
while (myReader.hasNextLine()){
lines.add(myReader.nextLine());
}
extractData(lines, myObj.getName());
myReader.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
/**
* This is a private method which extracts productID, suppleirID and Quantity values from data passed
* #param lines arraylist of string data
* #param filename
* #return Nothing.
*/
private void extractData(ArrayList<String> lines, String filename) {
// Get fieldNames
String fieldNameString = lines.remove(0);
String[] fieldNames;
// Get supplierID and file type
String supplierID = filename.split("\\.")[0];
String fileType = filename.split("\\.")[1];
// Get index of fieldName product and quantity/inventory based on file type
int productIdx = -1;
int quantityIdx = -1;
if (fileType.equals("csv") || filename.equals("xlsb") || fileType.equals("xls") || fileType.equals("xlsx") || fileType.equals("xlsm")){
fieldNames = fieldNameString.split(",");
for (int i=0; i<fieldNames.length; i++){
String fieldName = fieldNames[i];
if (fieldName.toLowerCase().equals("product"))
productIdx = i;
else if (fieldName.toLowerCase().equals("inventory") || fieldName.toLowerCase().equals("quantity"))
quantityIdx = i;
}
// loop through remaining data to get productIDs and their quantities
for (String line: lines){
String productID = line.split(",")[productIdx];
int quantity = Integer.valueOf(line.split(",")[quantityIdx]);
System.out.println("ProductID: " + productID + " quantity: " + quantity + " SupplierID: " + supplierID);
// create SupplierProduct Object
SupplierProduct supplierProduct = new SupplierProduct(supplierID, productID, quantity);
// WriteToDB - use supplierProduct to write to database
}
} else if (fileType.equals("tsv") || fileType.equals("tab")) {
fieldNames = fieldNameString.split("\\s+");
for (int i=0; i<fieldNames.length; i++){
String fieldName = fieldNames[i];
if (fieldName.toLowerCase().equals("product"))
productIdx = i;
else if (fieldName.toLowerCase().equals("inventory") || fieldName.toLowerCase().equals("quantity"))
quantityIdx = i;
}
// loop through remaining data to get productIDs and their quantities
for (String line: lines){
String productID = line.split("\\s+")[productIdx];
int quantity = Integer.valueOf(line.split("\\s+")[quantityIdx]);
System.out.println("ProductID: " + productID + " Quantity: " + quantity + " SupplierID: " + supplierID);
// create SupplierProduct Object
SupplierProduct supplierProduct = new SupplierProduct(supplierID, productID, quantity);
// WriteToDB - use supplierProduct to write to database
}
}
}
}
class SupplierProduct {
private String supplierID;
private String productID;
private int quantity;
public SupplierProduct(String supplierID, String productID, int quantity) {
this.supplierID = supplierID;
this.productID = productID;
this.quantity = quantity;
}
}
Related
I have below DBImporter class which is working fine and also inserting data correctly in database table. I am trying to fetch data from .CSV file and inserting into Oracle table.
Till now i was processing only one file in my directory and which is working fine. Now i want to process more than one file. So during run the first file process correctly and inserted data, in second file it started reading data and throw an error as :
java.lang.IllegalArgumentException: SQL array must not be empty
Below is my DBimporter class. I think the error is during final commit batch somewhere in line here but not sure
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
#Service
public class DBImporter {
private final static Logger log = LoggerFactory.getLogger(DBImporter.class);
private static final List<String> NULL_VALUES = Arrays.asList("", "N.A", "N.A", "UNKNOWN");
private static final List<String> COL_HEADERS = Arrays.asList("ID", "NM", "TYE", "SA");
private static final int BATCH_SIZE = 50;
private boolean eof = false;
private String tableName;
#Autowired
private JdbcTemplate jdbcTemplate;
public void setTableName(String tableName) {
this.tableName = tableName;
}
#Transactional(rollbackFor = IOException.class)
public void processFile(BufferedReader reader, String tableName) {
this.tableName = tableName;
List<String> sqlBatch = new ArrayList<String>(BATCH_SIZE);
log.info("Starte auslesen der Daten");
long t1 = System.currentTimeMillis();
log.info("Start time: " + t1);
jdbcTemplate.execute("DELETE FROM " + tableName);
while (!eof) {
try {
Map<String, ColumnData> dbColumns = getDBColumns();
// Get a list of db column data related to the column headers.
List<ColumnData> columnData = COL_HEADERS.stream().map(dbColumns::get).collect(toList());
// Get the next valid data row if its starts from "FRO" or "BO".
List<String> dataRow = findNextLineStartingWith(reader, "R", "T");
String query = createSql(columnData, dataRow);
sqlBatch.add(query);
// Process batch.
if (sqlBatch.size() >= BATCH_SIZE) {
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
}
} catch (IllegalStateException e) {
break;
} catch (IOException e) {
log.error(e.getLocalizedMessage());
}
}
// Commit the final batch.
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
long delta = System.currentTimeMillis() - t1;
log.info("Total runtime : " + delta / 1000 + " seconds");
}
/**
* Create a SQL insert query using the data row.
*
* #param tableName Name of the table.
* #param columnData Column data list.
* #param dataRow Data row to be inserted.
* #return Generated SQL query string.
*/
private String createSql(List<ColumnData> columnData, List<String> dataRow) {
List<String> values = new ArrayList<>(columnData.size());
for (int i = 0; i < columnData.size(); i++) {
if (NULL_VALUES.contains(dataRow.get(i))) {
values.add("NULL");
} else if (columnData.get(i).getType() >= Types.NUMERIC && columnData.get(i).getType() <= Types.DOUBLE) {
values.add(dataRow.get(i));
} else {
values.add("'" + dataRow.get(i).replace("'", "''") + "'");
}
}
return "INSERT INTO " + tableName + " (" +
columnData.stream().filter(Objects::nonNull).map(ColumnData::getName).collect(joining(", ")) +
", SYSTEM_INSERTED_AT) VALUES (" +
values.stream().collect(joining(", ")) +
", CURRENT_TIMESTAMP)";
}
/**
* Find the next line starting with the given string and split it into columns.
*
* #param reader BufferedReader object to be used.
* #param prefixes A list of prefixes to look for in the string.
* #return List of data objects.
* #throws IOException
*/
private List<String> findNextLineStartingWith(BufferedReader reader, String... prefixes) throws IOException {
while (true) {
String line = readLineOrThrow(reader);
for (String prefix : prefixes)
if (line.startsWith(prefix)) {
ArrayList<String> data = new ArrayList<>();
// Split the line using the delimiter.
data.addAll(Arrays.asList(line.split(";")));
// Build the row to be inserted.
List<String> row = Arrays.asList(data.get(1), data.get(2).trim(), "", "");
return row;
}
}
}
/**
* Read a single line in the file.
*
* #param reader BufferedReader object to be used.
* #return
* #throws IOException
*/
private String readLineOrThrow(BufferedReader reader) throws IOException {
String line = reader.readLine();
if (line == null) {
this.eof = true;
throw new IllegalStateException("Unexpected EOF");
}
return line.trim();
}
/**
* Read database column metadata.
*
* #param tableName Name of the table to process.
* #return A map containing column information.
*/
private Map<String, ColumnData> getDBColumns() {
Map<String, ColumnData> result = new HashMap<>();
try (Connection connection = jdbcTemplate.getDataSource().getConnection()) {
ResultSet rs = connection.getMetaData().getColumns(null, null, tableName, null);
while (rs.next()) {
String columnName = rs.getString(4).toUpperCase();
int type = rs.getInt(5);
result.put(columnName, new ColumnData(columnName, type));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Please try below changes:
// Commit the final batch.
if (sqlBatch.size() > 0){
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
}
And
#Transactional(rollbackFor = IOException.class)
public void processFile(BufferedReader reader, String tableName) {
eof = false;
...
But if you want a more clear and safe solution do changes in your code as below:
public class DBImporter {
private final static Logger log = LoggerFactory.getLogger(DBImporter.class);
private static final List<String> NULL_VALUES = Arrays.asList("", "N.A", "N.A", "UNKNOWN");
private static final List<String> COL_HEADERS = Arrays.asList("USER_ID", "NAME", "TYPE", "SRC_DATA");
private static final int BATCH_SIZE = 50;
#Autowired
private JdbcTemplate jdbcTemplate;
#Transactional(rollbackFor = IOException.class)
public void processFile(BufferedReader reader, String tableName) {
AtomicBoolean eof = new AtomicBoolean(false);
List<String> sqlBatch = new ArrayList<String>(BATCH_SIZE);
log.info("Starte auslesen der Daten");
long t1 = System.currentTimeMillis();
log.info("Start time: " + t1);
jdbcTemplate.execute("DELETE FROM " + tableName);
while (!eof.get()) {
try {
Map<String, ColumnData> dbColumns = getDBColumns(tableName);
// Get a list of db column data related to the column headers.
List<ColumnData> columnData = COL_HEADERS.stream().map(dbColumns::get).collect(toList());
// Get the next valid data row if its starts from "R" or "T".
List<String> dataRow = findNextLineStartingWith(reader, eof, "R", "T");
String query = createSql(tableName, columnData, dataRow);
sqlBatch.add(query);
// Process batch.
if (sqlBatch.size() >= BATCH_SIZE) {
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
}
} catch (IllegalStateException e) {
break;
} catch (IOException e) {
log.error(e.getLocalizedMessage());
}
}
// Commit the final batch.
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
long delta = System.currentTimeMillis() - t1;
log.info("Total runtime : " + delta / 1000 + " seconds");
}
/**
* Create a SQL insert query using the data row.
*
* #param tableName Name of the table.
* #param columnData Column data list.
* #param dataRow Data row to be inserted.
* #return Generated SQL query string.
*/
private String createSql(String tableName, List<ColumnData> columnData, List<String> dataRow) {
List<String> values = new ArrayList<>(columnData.size());
for (int i = 0; i < columnData.size(); i++) {
if (NULL_VALUES.contains(dataRow.get(i))) {
values.add("NULL");
} else if (columnData.get(i).getType() >= Types.NUMERIC && columnData.get(i).getType() <= Types.DOUBLE) {
values.add(dataRow.get(i));
} else {
values.add("'" + dataRow.get(i).replace("'", "''") + "'");
}
}
return "INSERT INTO " + tableName + " (" +
columnData.stream().filter(Objects::nonNull).map(ColumnData::getName).collect(joining(", ")) +
", SYSTEM_INSERTED_AT) VALUES (" +
values.stream().collect(joining(", ")) +
", CURRENT_TIMESTAMP)";
}
/**
* Find the next line starting with the given string and split it into columns.
*
* #param reader BufferedReader object to be used.
* #param prefixes A list of prefixes to look for in the string.
* #return List of data objects.
* #throws IOException
*/
private List<String> findNextLineStartingWith(BufferedReader reader, AtomicBoolean eof, String... prefixes) throws IOException {
while (true) {
String line = readLineOrThrow(reader, eof);
for (String prefix : prefixes)
if (line.startsWith(prefix)) {
ArrayList<String> data = new ArrayList<>();
// Split the line using the delimiter.
data.addAll(Arrays.asList(line.split(";")));
// Build the row to be inserted.
List<String> row = Arrays.asList(data.get(1), data.get(2).trim(), "", "");
// Insert type depending on the prefix.
if (prefix.equals("R"))
row.set(2, "USER");
else if (prefix.equals("T"))
row.set(2, "PERM");
row.set(3, String.join(";", row.subList(0, 3)));
return row;
}
}
}
/**
* Read a single line in the file.
*
* #param reader BufferedReader object to be used.
* #return
* #throws IOException
*/
private String readLineOrThrow(BufferedReader reader, AtomicBoolean eof) throws IOException {
String line = reader.readLine();
if (line == null) {
eof.set(true);
throw new IllegalStateException("Unexpected EOF");
}
return line.trim();
}
/**
* Read database column metadata.
*
* #param tableName Name of the table to process.
* #return A map containing column information.
*/
private Map<String, ColumnData> getDBColumns(String tableName) {
Map<String, ColumnData> result = new HashMap<>();
try (Connection connection = jdbcTemplate.getDataSource().getConnection()) {
ResultSet rs = connection.getMetaData().getColumns(null, null, tableName, null);
while (rs.next()) {
String columnName = rs.getString(4).toUpperCase();
int type = rs.getInt(5);
result.put(columnName, new ColumnData(columnName, type));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
There is the possibility that your final batch is empty.
This is possible in case you just commited BATCH_SIZE entries and have cleared the sqlBatch. In case your while loop exits at this point of time,
there are no elements to commit.
You'll want to fix that by adding a size check, for example:
// Commit the final batch (only if there is something left)
if (sqlBatch.isEmpty() == false) {
jdbcTemplate.batchUpdate(sqlBatch.toArray(new String[sqlBatch.size()]));
sqlBatch.clear();
}
Edit:
As #Vasif pointed out you'll need to reset the eof between different calls of the method.
A simple solution (albeit somewhat hacky) would be
boolean eof = false
while (!eof) {
try {
} catch (IllegalStateException e) {
eof = true;
break;
} catch (IOException e) {
log.error(e.getLocalizedMessage());
}
}
A proper solution would be to refactor your code so that it does not rely on these exception being thrown.
Some tips:
Get rid of readLineOrThrow.
Remove the while(true) in findNextLineStartingWith and instead return an empty list if the next line is null.
Adjust the outside loop to handle this return value appropriately.
(Note: you might also need to break the loop if you get an IOException).
I want to get a little help with a school assignment. I have a method that isn't acting the way I thought it would. I'm pretty sure the problem is in nlpClassify(), but I'm not entirely sure. Currently, clpClassify() will only print out 0s for the variables. Any suggestions are welcome.
public class TweetHandler implements TweetHandlerInterface {
private Scanner inFile = null;
private String tweet = null;
private String[] temp = null;
private int counter = 0;
List<AbstractTweet> tweetList = new ArrayList<AbstractTweet>();
List<AbstractTweet> dataList = new ArrayList<AbstractTweet>();
List<String[]> dataBaseList = new ArrayList<String[]>();
#Override
public List<AbstractTweet> loadTweetsFromText(String filePath) {
MainApp main = null;
try {
inFile = new Scanner ( new FileReader(filePath));
}
catch( FileNotFoundException er ){
System.err.println(er);
main.printMenu();
}
String line = null;
//String[] data = null;
//int counter = 0;
while ( inFile.hasNextLine() ) {
line = inFile.nextLine();
parseTweetLine(line);
counter++;
}
System.out.println("Reading tweets from file..."
+ "\n" + counter + " tweets read.");
inFile.close();
return tweetList;
}
#Override
public AbstractTweet parseTweetLine(String tweetLine) {
temp = tweetLine.split("\",\"");
tweet = temp[5];
if( temp.length > 6 ){
for( int i = 0; i < temp.length; i++ ){
tweet += " " + temp[i];
}
}
temp[0] = temp[0].replaceAll("\"","");
tweet = tweet.replaceAll("\\p{Punct}", "");
temp[5] = tweet;
int target = Integer.parseInt(temp[0]);
int id = Integer.parseInt(temp[1]);
SimpleDateFormat format = new SimpleDateFormat("EE MMM dd HH:mm:ss zzz yyyy");
Date date = new Date();
try{
date = format.parse(temp[2]);
}
catch( ParseException e ){
System.err.println("Error with date parsing. " + e);
}
String flag = temp[3];
String user = temp[4];
String text = temp[5];
Tweet tweets = new Tweet(target,id,date,flag,user,text);
return tweets;
}
/**
* calls classifyTweet
*/
public void nlpClassify(){ //prints out accuracy
System.out.println("Classifying tweets...");
counter = 0;
int correct = 0,
wrong = 0;
float accuracy = 0.0f;
AbstractTweet tweets;
for( int i = 0; i < tweetList.size(); i++ ){
tweets = tweetList.get(i);
tweets.setPredictedPolarity(classifyTweet(tweets));
int target = tweets.getTarget();
int polarity = tweets.getPredictedPolarity();
System.out.println("Target: " + target );
System.out.println("Prediction: " + polarity );
for( int j = 0; j < 75; j++ ){
System.out.print("=");
}
System.out.println();
if( target == polarity ){
correct++;
}
else{
wrong++;
}
counter++;
accuracy = ( correct / counter ) * 100.0f;
}
System.out.println( "Classified " + counter + " tweets." );
System.out.println( "Correct tweets: " + correct );
System.out.println( "Wrong tweets: " + wrong );
System.out.println( "Accuracy: " + accuracy );
}
#Override
public int classifyTweet(AbstractTweet tweet) {
int calcPolarity = SentimentAnalyzer.getParagraphSentiment( tweet.getText() );
tweet.setPredictedPolarity(calcPolarity);
return calcPolarity;
}
#Override
public void addTweetsToDB(List<AbstractTweet> tweets) {
System.out.println("Adding files to database... ");
int i = 0;
while( i < tweets.size()){
dataList.add(tweets.get(i));
i++;
}
}
#Override
public void deleteTweet(int id) {
int i = 0,
temp = 0;
Tweet obj = null;
while( i < dataList.size() ){
temp = obj.getId();
if( id == temp ){
dataList.remove(i);
--counter;
}
++i;
}
}
#Override
public void saveSerialDB() {
try{
FileOutputStream fileOut = new FileOutputStream("DB.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(dataList);
out.close();
fileOut.close();
}
catch( IOException e ){
e.printStackTrace();
}
}
#Override
public void loadSerialDB() {
try{
FileInputStream fileIn = new FileInputStream("DB.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
dataList = (List<AbstractTweet>)in.readObject();
in.close();
fileIn.close();
}
catch( Exception e ){
e.printStackTrace();
}
}
#Override
public List<AbstractTweet> searchByUser(String user) {
int i = 0;
String temp = null;
Tweet obj = null;
while( i < dataList.size() ){
temp = obj.getUser();
if( user.equals(temp) )
dataList.add(obj);
++i;
}
return dataList;
}
Ignore searchByUser(String user), I know it's wrong and haven't gotten around to fixing it. If you have suggestions for it, that's cool too.
Here is the interface.
public interface TweetHandlerInterface {
/**
* Loads tweets from a CSV text file.
* #param filePath The path to the CSV file.
* #return A list of tweets as objects.
*/
List<AbstractTweet> loadTweetsFromText(String filePath);
/**
* Parses a single line from the CSV file and returns a tweet as an object.
* #param tweetLine A string containing the contents of a single line in the CSV file.
* #return A tweet as an object.
*/
AbstractTweet parseTweetLine(String tweetLine);
/**
* Classifies a tweet as negative, neutral, or positive by using the text of the tweet.
* #param tweet A tweet object.
* #return 0 = negative, 2 = neutral, 4 = positive.
*/
int classifyTweet(AbstractTweet tweet);
/**
* Adds a list of new tweets to the existing database.
* #param tweets A list of tweet objects.
*/
void addTweetsToDB(List<AbstractTweet> tweets);
/**
* It deletes ad tweet from the database, given its id.
* #param id The id value of the tweet.
*/
void deleteTweet(int id);
/**
* Saves the database in the working directory as a serialized object.
*/
void saveSerialDB();
/**
* Loads tweet database.
*/
void loadSerialDB();
/**
* Searches the tweet database by user name. It returns a list of all tweets
* matching the given user name.
* #param user The user name to search for.
* #return A list of tweet objects.
*/
List<AbstractTweet> searchByUser(String user);
/**
* Searches the tweet database for tweets posted on a given date.
* #param date The date to search for.
* #return A list of tweet objects.
*/
List<AbstractTweet> searchByDate(Date date);
/**
* Searches the tweet database for tweets matching a given flag.
* #param flag The flag to search for.
* #return A list of tweet objects.
*/
List<AbstractTweet> searchByFlag(String flag);
/**
* Searches the tweet database for tweets matching a given substring.
* #param substring The substring to search for.
* #return A list of tweet objects.
*/
List<AbstractTweet> searchBySubstring(String substring);
}
Here is my Main().
public class MainApp {
public static void main (String[] args){
TweetHandler handler = new TweetHandler();
MainApp obj = new MainApp();
Tweet tweetObj = null;
String choice;
do{
obj.printMenu();
System.out.print("Enter a choice from above: ");
Scanner inputConsole = new Scanner( System.in );
choice = inputConsole.next();
String filePath = null;
switch( choice ){
case "0":
System.exit(0);
break;
case "1":
Scanner file = new Scanner ( System.in );
System.out.print("Enter the input file path: ");
filePath = file.nextLine();
handler.loadTweetsFromText(filePath);
break;
case "2":
handler.classifyTweet(handler.parseTweetLine(filePath));
handler.nlpClassify();
break;
case "3":
System.out.println("Enter ID of the tweet you wish to change: ");
break;
case "4":
System.out.println("Adding tweets to database...");
handler.addTweetsToDB(handler.loadTweetsFromText(filePath));
break;
case "5":
Scanner removeChoice = new Scanner( System.in );
System.out.println("Enter ID of tweet you wish to delete: ");
int remove = removeChoice.nextInt();
handler.deleteTweet(remove);
break;
case "6":
Scanner searching = new Scanner( System.in );
System.out.println("Search by: 1 User, 2 Date, 3 Flag, 4 Substring.");
int search = searching.nextInt();
switch(search){
case 1:
case 2:
case 3:
case 4:
}
break;
}
} while ( !"0".equals(choice) );
}
void printMenu(){
System.out.println("0. Exit program.\n"
+ "1. Load new tweet text file.\n"
+ "2. Classify tweets using NLP library and report accuracy.\n"
+ "3. Manually change tweet class label.\n"
+ "4. Add new tweets to database.\n"
+ "5. Delete tweet from database (given its id).\n"
+ "6. Search tweets by user, date, flag, or a matching substring.\n");
}
}
You loop for with tweetList.size(). You can print out tweetList.size() to see the value. I guess it's 0. I didn't see any tweetList.add(obj) in your code. So tweetList is empty all the time.
I have a method to read a text file of five stocks using Scanner and delimiter. I also have a method which should take a double multiplier and an array of stock objects and change the price with that multiplier. The method which reads the stock file functions correctly, however, when I attempt to change the stock objects that have been assigned values with the filereading method, it does not change the price. After I use method updateStocks(), I test stock[1] to see if the value has changed and it has not.
After I run this:
public class Main {
public static void main (String args[]) {
// stock object with which to call methods
Stock theStock = new Stock("", "", 0);
// this array holds the stocks to be read
Stock[] stocks = new Stock[100];
// here is the multiplier I am attempting to use
double multiplier = 1/3;
// first I read in the stocks file
theStock.readStocksWithScanner(stocks);
// then I call the updateStockPrices method
theStock.updateStockPrices(multiplier,stocks);
// lastly, I test to see if the method has functioned correctly
System.out.println(stocks[1]);
}
}
This is my output:
Apple AAPLE 152.70
Alphabet GOOGLE 873.96
IBM IBM 194.37
Microsoft MSFT 65.67
Oracle ORCLE 62.82
Company name: Apple Stock symbol: AAPLE Stock Price: 152.7
Press any key to continue . . .
Here is the method updateStockPrices:
// this is the method which does not seem to function as intended
public void updateStockPrices(double multiplier, Stock[] objects)
{
// I loop to the length of the array in the parameter
for(int i = 1; i < objects.length; i++)
{
// try to manipulate the stock
try{
double subtractThis = stocks[i].getPrice() * multiplier;
objects[i].setPrice(stocks[i].getPrice() - subtractThis);
}// and catch any null objects
catch(NullPointerException e){
// if null I then increment the counter
i++;}
// Is code missing in order for this to function as intended? or have I made a mistake?
}
}
and here is stocks.java:
import java.util.Scanner ;
import java.util.InputMismatchException ;
import java.io.FileInputStream ;
import java.io.IOException ;
import java.io.FileNotFoundException ;
import java.io.PrintWriter ;
public class Stock {
private static final double MULTIPLIER = (1/3);
public static final String FILE_NAME = "stocks1.txt";
public String newFile = "stocks2.txt";
public static final String FORMAT = "%-10s%6s\t%.2f%n";
private PrintWriter writer = null;
private String name = "";
private String symbol = "";
private double price = 0;
protected Stock stocks[] = new Stock[100];
FileInputStream inputStream = null;
String workingDirectory = System.getProperty("user.dir");
String absolutePath = workingDirectory + "\\" + FILE_NAME;
public Stock(String aName, String aSymbol, double aPrice)
{
this.name = aName;
this.symbol = aSymbol;
this.price = aPrice;
}
// the fileReading method reads the stocks in
public void readStocksWithScanner(Stock[] stocks)
{
this.stocks = stocks;
String workingDirectory = System.getProperty("user.dir");
String absolutePath = workingDirectory + "\\" + FILE_NAME;
FileInputStream inputStream = null;
try{
inputStream = new FileInputStream(absolutePath);
}
catch (FileNotFoundException e)
{
System.out.println("File not found: " + FILE_NAME);
System.out.println("Exiting program.");
System.exit(0) ;
}
Scanner inputFile = new Scanner(inputStream);
int lineNumber = 1;
try{
while(inputFile.hasNextLine())
{
inputFile.useDelimiter(",");
String name = inputFile.next();
inputFile.useDelimiter(",");
String symbol = inputFile.next();
inputFile.useDelimiter("[,\\s]") ;
Double thePrice = inputFile.nextDouble();
inputFile.nextLine();
// here the stocks are given the values found in the text file and initialized above
stocks[lineNumber] = new Stock(name, symbol, thePrice);
// I print them out using a format constant, in order to ensure the method has functioned correcly
System.out.printf(FORMAT, name, symbol, thePrice);
// then I increment the lineNumber
lineNumber++;
}
// I close the stream and should be left with a partially filled array of stock items
inputStream.close();
}catch (IOException e) {
System.out.println("Error reading line " + lineNumber + " from file " + FILE_NAME) ;
System.exit(0) ;
}
catch(InputMismatchException e) {
System.out.println("Couldn't convert price to a number on line " + lineNumber) ;
System.exit(0) ;}
}
public void setName(String name)
{
this.name = name;
}
public void setSymbol(String symbol)
{
this.symbol = symbol;
}
public void setPrice(double aPrice)
{
this.price = aPrice;
}
public String getName()
{
return name;
}
public String getSymbol()
{
return symbol;
}
public double getPrice()
{
return price;
}
public boolean equals(Object other)
{
if(other.getClass() != getClass() || other == null )
{
return false;
}
Stock stock = (Stock) other;
{
if(stock.getName() == getName() && stock.getSymbol() == getSymbol() && stock.getPrice() == getPrice())
{
return true;
}
return false;
}
}
public String toString()
{
return "Company name: " + getName() + " Stock symbol: " + getSymbol() + " Stock Price: " + getPrice();
}
// this is the method which does not seem to function as intended
public void updateStockPrices(double multiplier, Stock[] objects)
{
// I loop to the length of the array in the parameter
for(int i = 1; i < objects.length; i++)
{
// try to manipulate the stock
try{
double subtractThis = stocks[i].getPrice() * multiplier;
objects[i].setPrice(stocks[i].getPrice() - subtractThis);
}// and catch any null objects
catch(NullPointerException e){
// if null I then increment the counter
i++;}
// Is code missing in order for this to function as intended? or have I made a mistake?
}
}
public void createStocks(int stockAmount)
{
Stock[] stocks = new Stock[stockAmount];
}
public void writeStocks(String fileName, Stock[] objects)
{
try{
writer = new PrintWriter(fileName);
}
catch(FileNotFoundException e){
System.out.println("Couldn't create file " + fileName);
System.exit(0);
}
for(Stock s: objects)
{
writer.printf(FORMAT, getName(), getSymbol(), getPrice());
if(objects == null)
writer.close() ;
}
}
public Stock[] getStocks()
{
return stocks;
}
}
simple test
double multiplier = 1/3;
System.out.println(multiplier);
compared to
double multiplier = 1/3f;
System.out.println(multiplier);
Code so far:
public class test1 {
public static void main(String[] args) throws IOException {
//declare reader and writer
BufferedReader reader = null;
PrintWriter writer = null;
//hash maps to store the data
HashMap<String, String> names = new HashMap<String, String>();
//read the first file and store the data
reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("IRStudents.txt"))));
String line;
String[] arg;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("-")) {
arg = line.split(" ");
names.put(arg[0], arg[1]);
}
}
reader.close();
//read the second file, merge the data and output the data to the out file
writer = new PrintWriter(new FileOutputStream(new File("File_2.txt")));
reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("Marks.txt"))));
while((line = reader.readLine()) != null){
arg = line.split(" ");
writer.println(arg[0] + " " + names.get(arg[0]));
writer.println("Marks: " + arg[1]);
writer.println("- - - - - -");
}
writer.flush();
writer.close();
reader.close();
}
}
So the output in the text file looks like:
25220 Fiona
Marks: 68.3
- - - - - -
25212 Greg
Marks: 70.5
- - - - - -
I have ANOTHER text file with another set of marks with the same layout as the first mark file.
Now I want to add a new set of marks to the set of data So it should look like this:
25220 Fiona
Marks: 68.3 Marks2: 21.2
- - - - - -
25212 Greg
Marks: 70.5 Marks2: 23.43
- - - - - -
So what can I do to add? I assume I have to add a new Hashmap for the new text document? But when I tried doing all of that it never fully works.
IR Student:
25987 Alan
25954 Betty
25654 Chris
25622 David
You could do the following too.
package toBeDeleted;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MarksProcessor {
private final Map<String, Record> map = new HashMap<>();
public static void main(String[] args) {
String fileName = "file1.txt"; // change it to your specific file.
MarksProcessor marksProcessor = new MarksProcessor();
marksProcessor.processFile(fileName, 0);
fileName = "file2.txt";
marksProcessor.processFile(fileName, 1);
marksProcessor.writeData();
}
private void processFile(String fileName, int marksIndex) {
try(/*specify your reader resources here*/) {
// read the first record and get rollNumber, name and marks.
String roll = "valueYouGot";
double value = 0.0; // the value you read.
Record record = map.get(roll);
// if record is null, you need to create one
// and put it into the map.
//record.updateMarks(marksndex, value);
}
}
private void writeData() {
// if this needs to be written to a file/stream, create a writer.
for (Map.Entry<String, Record> entry : map.entrySet()) {
String roll = entry.getKey();
Record record = entry.getValue();
if (record != null) {
String name = record.getName();
double marks1 = record.getMarks(0);
double marks2 = record.getMarks(1);
// Now you have all the values. Print them
// however you like. Wherever you like.
}
}
}
static class Record {
private String name;
private double[] marks = new double[2];
Record(String name) {
this.name = name;
}
public String getName() {
return name;
}
public double getMarks(int index) {
if (index < 0 || index > 1)
throw new IllegalArgumentException("index should be 0 or 1 but"
+ " the supplied index was " + index);
return marks[index];
}
public void updateMarks(int index, double value ) {
if (index < 0 || index > 1)
throw new IllegalArgumentException("index should be 0 or 1 but"
+ " the supplied index was " + index);
marks[index] = value;
}
#Override
public String toString() {
return "the way you want to type your output";
}
}
}
I think you are doing too much String manipulation. And if you will have more marks' files to process in a similar way, the String manipulation is likely going to increase which could make your code less readable and could give more room for errors. I think following would be a better approach.
You could create a MarksRecord class with the following structure.
public class MarksRecord {
private String subject; // or whatever this variable name should be.
// in your case it should hold value marks1.
private double marks;
}
Similarly you could create an immutable Student/similar class as follows. This could be a value class with equals and hashCode methods based on the first number you are reading in each file. I am guessing it is roll number or similar that can identify a student in a unique way.
public final class Student {
private final String rollNumber;
private final String name;
// equals, hashCode, and other methods.
}
Then in your main method you could have a
Map<Student, ArrayList<MarksRecord>>
. Alternatively you could also use a
Map<String, ArrayList<MarksRecord>>
where the first String is the roll number of a Student record.
This way every time you have a new file of marks, your data structure will be able to accomodate it.
When adding the new marks, use this to add them to the already existing ones:
String key = arg[0];
String secondMarks = arg[1];
String theMarks = names.get(key);
theMarks = theMarks + " Marks2: " + secondMarks;
names.put(key, theMarks);
I think I understand your problem, let me know if this is incorrect.
The requirement is to have all marks that the person receved on its own line...
Theres two print functions in the System.out stream.
print and println
arg = line.split(" ");
writer.println(arg[0] + " " + names.get(arg[0]));
writer.print("Marks: " + arg[1]);
for(int i = 2; i < args.length; i++){
writer.println(" Marks" + i + ": " + arg[i]);
}
writer.println("\n- - - - - -");
I am reading an Excel sheet using POI's XSSF and SAX (Event API). The Excel sheet has thousands of rows of user information like user name, email, address, age, department etc.
I need to read each row from Excel, convert it into a User object and add this User object to a List of User objects.
I can read the Excel sheet successfully, but I am not sure at what point while reading I should create an instance of the User object and populate it with the data from the Excel sheet.
Below is my entire working code.
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class ExcelSheetParser {
enum xssfDataType {
BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER,
}
int countrows = 0;
class XSSFSheetHandler extends DefaultHandler {
/**
* Table with styles
*/
private StylesTable stylesTable;
/**
* Table with unique strings
*/
private ReadOnlySharedStringsTable sharedStringsTable;
/**
* Destination for data
*/
private final PrintStream output;
private List<?> list = new ArrayList();
private Class clazz;
/**
* Number of columns to read starting with leftmost
*/
private final int minColumnCount;
// Set when V start element is seen
private boolean vIsOpen;
// Set when cell start element is seen;
// used when cell close element is seen.
private xssfDataType nextDataType;
// Used to format numeric cell values.
private short formatIndex;
private String formatString;
private final DataFormatter formatter;
private int thisColumn = -1;
// The last column printed to the output stream
private int lastColumnNumber = -1;
// Gathers characters as they are seen.
private StringBuffer value;
/**
* Accepts objects needed while parsing.
*
* #param styles
* Table of styles
* #param strings
* Table of shared strings
* #param cols
* Minimum number of columns to show
* #param target
* Sink for output
*/
public XSSFSheetHandler(StylesTable styles,
ReadOnlySharedStringsTable strings, int cols, PrintStream target, Class clazz) {
this.stylesTable = styles;
this.sharedStringsTable = strings;
this.minColumnCount = cols;
this.output = target;
this.value = new StringBuffer();
this.nextDataType = xssfDataType.NUMBER;
this.formatter = new DataFormatter();
this.clazz = clazz;
}
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
if ("inlineStr".equals(name) || "v".equals(name)) {
vIsOpen = true;
// Clear contents cache
value.setLength(0);
}
// c => cell
else if ("c".equals(name)) {
// Get the cell reference
String r = attributes.getValue("r");
int firstDigit = -1;
for (int c = 0; c < r.length(); ++c) {
if (Character.isDigit(r.charAt(c))) {
firstDigit = c;
break;
}
}
thisColumn = nameToColumn(r.substring(0, firstDigit));
// Set up defaults.
this.nextDataType = xssfDataType.NUMBER;
this.formatIndex = -1;
this.formatString = null;
String cellType = attributes.getValue("t");
String cellStyleStr = attributes.getValue("s");
if ("b".equals(cellType))
nextDataType = xssfDataType.BOOL;
else if ("e".equals(cellType))
nextDataType = xssfDataType.ERROR;
else if ("inlineStr".equals(cellType))
nextDataType = xssfDataType.INLINESTR;
else if ("s".equals(cellType))
nextDataType = xssfDataType.SSTINDEX;
else if ("str".equals(cellType))
nextDataType = xssfDataType.FORMULA;
else if (cellStyleStr != null) {
// It's a number, but almost certainly one
// with a special style or format
int styleIndex = Integer.parseInt(cellStyleStr);
XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
this.formatIndex = style.getDataFormat();
this.formatString = style.getDataFormatString();
if (this.formatString == null)
this.formatString = BuiltinFormats
.getBuiltinFormat(this.formatIndex);
}
}
}
public void endElement(String uri, String localName, String name)
throws SAXException {
String thisStr = null;
// v => contents of a cell
if ("v".equals(name)) {
// Process the value contents as required.
// Do now, as characters() may be called more than once
switch (nextDataType) {
case BOOL:
char first = value.charAt(0);
thisStr = first == '0' ? "FALSE" : "TRUE";
break;
case ERROR:
thisStr = "\"ERROR:" + value.toString() + '"';
break;
case FORMULA:
// A formula could result in a string value,
// so always add double-quote characters.
thisStr = '"' + value.toString() + '"';
break;
case INLINESTR:
// TODO: have seen an example of this, so it's untested.
XSSFRichTextString rtsi = new XSSFRichTextString(value
.toString());
thisStr = '"' + rtsi.toString() + '"';
break;
case SSTINDEX:
String sstIndex = value.toString();
try {
int idx = Integer.parseInt(sstIndex);
XSSFRichTextString rtss = new XSSFRichTextString(
sharedStringsTable.getEntryAt(idx));
thisStr = '"' + rtss.toString() + '"';
} catch (NumberFormatException ex) {
output.println("Failed to parse SST index '" + sstIndex
+ "': " + ex.toString());
}
break;
case NUMBER:
String n = value.toString();
if (this.formatString != null)
thisStr = formatter.formatRawCellContents(Double
.parseDouble(n), this.formatIndex,
this.formatString);
else
thisStr = n;
break;
default:
thisStr = "(TODO: Unexpected type: " + nextDataType + ")";
break;
}
// Output after we've seen the string contents
// Emit commas for any fields that were missing on this row
if (lastColumnNumber == -1) {
lastColumnNumber = 0;
}
for (int i = lastColumnNumber; i < thisColumn; ++i)
output.print(',');
// Might be the empty string.
output.print(thisColumn +" : "+thisStr);
// Update column
if (thisColumn > -1)
lastColumnNumber = thisColumn;
} else if ("row".equals(name)) {
// Print out any missing commas if needed
if (minColumns > 0) {
// Columns are 0 based
if (lastColumnNumber == -1) {
lastColumnNumber = 0;
}
for (int i = lastColumnNumber; i < (this.minColumnCount); i++) {
output.print(',');
}
}
// We're onto a new row
output.println();
output.println(countrows++);
lastColumnNumber = -1;
}
}
/**
* Captures characters only if a suitable element is open. Originally
* was just "v"; extended for inlineStr also.
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
if (vIsOpen)
value.append(ch, start, length);
}
/**
* Converts an Excel column name like "C" to a zero-based index.
*
* #param name
* #return Index corresponding to the specified name
*/
private int nameToColumn(String name) {
int column = -1;
for (int i = 0; i < name.length(); ++i) {
int c = name.charAt(i);
column = (column + 1) * 26 + c - 'A';
}
return column;
}
}
// /////////////////////////////////////
private OPCPackage xlsxPackage;
private int minColumns;
private PrintStream output;
private Class clazz;
/**
* Creates a new XLSX -> CSV converter
*
* #param pkg
* The XLSX package to process
* #param output
* The PrintStream to output the CSV to
* #param minColumns
* The minimum number of columns to output, or -1 for no minimum
*/
public ExcelSheetParser(OPCPackage pkg, PrintStream output, int minColumns, Class clazz) {
this.xlsxPackage = pkg;
this.output = output;
this.minColumns = minColumns;
this.clazz = clazz;
}
/**
* Parses and shows the content of one sheet using the specified styles and
* shared-strings tables.
*
* #param styles
* #param strings
* #param sheetInputStream
*/
public void processSheet(StylesTable styles,
ReadOnlySharedStringsTable strings, InputStream sheetInputStream)
throws IOException, ParserConfigurationException, SAXException {
InputSource sheetSource = new InputSource(sheetInputStream);
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxFactory.newSAXParser();
XMLReader sheetParser = saxParser.getXMLReader();
ContentHandler handler = new XSSFSheetHandler(styles, strings,
this.minColumns, this.output, this.clazz);
sheetParser.setContentHandler(handler);
sheetParser.parse(sheetSource);
}
/**
* Initiates the processing of the XLS workbook file to CSV.
*
* #throws IOException
* #throws OpenXML4JException
* #throws ParserConfigurationException
* #throws SAXException
*/
public void process() throws IOException, OpenXML4JException,
ParserConfigurationException, SAXException {
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(
this.xlsxPackage);
XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
StylesTable styles = xssfReader.getStylesTable();
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader
.getSheetsData();
int index = 0;
while (iter.hasNext()) {
InputStream stream = iter.next();
String sheetName = iter.getSheetName();
this.output.println(sheetName + " [index=" + index + "]:");
processSheet(styles, strings, stream);
stream.close();
++index;
}
}
}
What I'd probably do is start building the User object when the row starts. As you hit the cells in the row, you populate your User object. When the row ends, validate the User object, and if it's fine add it then. Because you're doing SAX parsing, you'll get the start and events for all of these, so you can attach your logic there.
I'd suggest you take a look at XLSX2CSV in the Apache POI Examples. It shows how to go about handling the different kinds of cell contents (which you'll need for populating your user object), how to do something when you reach the end of the row, as well as handling missing cells etc.
I think you can create a user object at following location in your code:
// We're onto a new row
output.println();
// Convert output to a new user object
// ....
// ....
First of all where you are saving value in thisStr variable, if this is a valid value then put this value in Map.
You should create USer object in endElement() method in
else if ("row".equals(name)) {
// use map create USER object here
}
and You can add Users object in global list and if you want to persist it then you can persist it sheet by sheet OR all data at a time.
while (iter.hasNext()) {
InputStream stream = iter.next();
String sheetName = iter.getSheetName();
this.output.println(sheetName + " [index=" + index + "]:");
processSheet(styles, strings, stream);
stream.close();
++index;
//for persisting USERS data sheet by sheet write your code here.........
}
// for persisting complete data of all sheets write your code here...
This is working for me.