Somewhat experienced with JavaFX, but this is my first potentially large JavaFX project that I recently started and wanted some feedback on anything that could be done better or more efficiently thus far, before continuing.
Note: I am aware that there are a few functions not in use, I'm just leaving them in for future use.
main
does nothing really special.
package Java.Application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("/Resources/MainWindow.fxml"));
primaryStage.setTitle("Finance Manager");
primaryStage.setScene(new Scene(root, 1200, 700));
//primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The MainController
, on run the application will determine which file in a directory was most recently used, the files information is then parsed and loaded into the UI. The directory is filled with .fmd files, which is just a custom little file format I created, which stands for finance manager data. This is the first time I've created a file format for an application and would especially enjoy thoughts on this part of the application. I will include the inside of a .fmd file at the end.
Specific thought: the setLabels()
method's readability is just awful, but maybe that is just me.
package Java.Controllers;
import Java.Application.*;
import javafx.collections.FXCollections;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.io.*;
import java.net.URL;
import java.text.ParseException;
import java.util.List;
import java.util.ResourceBundle;
public class MainController implements Initializable{
public Label profileName, yearlyIncome, monthlyIncome, weeklyIncome, dailyIncome,
yearlyAfterExpenses, monthlyAfterExpenses, weeklyAfterExpenses, dailyAfterExpenses;
public TableColumn<Statement, String> colExpense, colYearly, colMonthly, colWeekly, colDaily, colNotes;
public TableView<Statement> expenseTable;
private String PATH = "Profiles/"; // TODO: Allow profile path to be changed to a different default.
@Override
public void initialize(URL location, ResourceBundle resource){
setTableCellValues(); // Sets table columns with correct properties.
if(Directory.getSize(PATH) > 0){
try {
loadFile(FileTools.getMostRecentFile(PATH));
} catch (IOException ex) {
System.err.println("File Failed To Load.");
} catch (ParseException ex){
System.err.println("Date Could'nt Be Parsed.");
}
}
}
private void setTableCellValues() {
colExpense.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
colYearly.setCellValueFactory(cellData -> cellData.getValue().yearlyProperty());
colMonthly.setCellValueFactory(cellData -> cellData.getValue().monthlyProperty());
colWeekly.setCellValueFactory(cellData -> cellData.getValue().weeklyProperty());
colDaily.setCellValueFactory(cellData -> cellData.getValue().dailyProperty());
colNotes.setCellValueFactory(cellData -> cellData.getValue().noteProperty());
}
private void loadFile(File file){
try{
FmdParser parser = new FmdParser(file);
List<Statement> incomeStatements = parser.getAll("income");
List<Statement> expenseStatements = parser.getAll("expense");
setLabels(file, incomeStatements, expenseStatements); // Sets labels appropriately.
expenseTable.setItems(FXCollections.observableArrayList(expenseStatements));
} catch (IOException ex) {
System.err.println("File Failed To Load.");
}
}
private void setLabels(File file, List<Statement> incomes, List<Statement> expenses){
double yearly = StatementTools.getYearlySummation(incomes);
double monthly = StatementTools.getMonthlySummation(incomes);
double weekly = StatementTools.getWeeklySummation(incomes);
double daily = StatementTools.getDailySummation(incomes);
profileName.setText("Profile Name: " + FileTools.getFileName(file));
yearlyIncome.setText(String.format("Yearly Income: $%.2f", yearly));
monthlyIncome.setText(String.format("Monthly Income: $%.2f", monthly));
weeklyIncome.setText(String.format("Weekly Income: $%.2f", weekly));
dailyIncome.setText(String.format("Daily Income: $%.2f", daily));
yearlyAfterExpenses.setText(String.format("After Expenses: $%.2f", yearly - StatementTools.getYearlySummation(expenses)));
monthlyAfterExpenses.setText(String.format("After Expenses: $%.2f", monthly - StatementTools.getMonthlySummation(expenses)));
weeklyAfterExpenses.setText(String.format("After Expenses: $%.2f", weekly - StatementTools.getWeeklySummation(expenses)));
dailyAfterExpenses.setText(String.format("After Expenses: $%.2f", daily - StatementTools.getDailySummation(expenses)));
}
}
FmdParser
is used to parse the .fmd files information:
package Java.Application;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created on 8/4/2016.
*
*/
public class FmdParser{
File fmdFile;
public FmdParser(File file){
if(FileTools.getFileExtension(file).equals("fmd")){
fmdFile = file;
} else {
throw new IllegalArgumentException("Unsupported File Format");
}
}
public List<Statement> getAll(String type) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(fmdFile));
List<Statement> statements = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
String[] parsed = line.split(":");
if(parsed[0].equals(type)){
String[] attributes = parsed[1].split(",");
statements.add(new Statement(
attributes[0],
Double.valueOf(attributes[1]),
Double.valueOf(attributes[2]),
Double.valueOf(attributes[3]),
Double.valueOf(attributes[4]),
attributes[5]));
}
}
return statements;
}
}
The Statement
class groups all of the information that starts with expense:
or income:
in the .fmd file. The information in the class can then be calculated, or loaded into a TableView if need be.
package Java.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Created on 8/4/2016.
*
*/
public class Statement {
double yearly, monthly, weekly, daily;
private StringProperty nameProperty, yearlyProperty, monthlyProperty, weeklyProperty, dailyProperty, noteProperty;
public Statement(String name, double yearly, double monthly, double weekly, double daily, String note){
// Double variables, used in mathematical purposes.
this.yearly = yearly;
this.monthly = monthly;
this.weekly = weekly;
this.daily = daily;
// StringProperty variables, used for storing information in javafx view tables.
this.nameProperty = new SimpleStringProperty(name);
this.yearlyProperty = new SimpleStringProperty(String.valueOf(yearly));
this.monthlyProperty = new SimpleStringProperty(String.valueOf(monthly));
this.weeklyProperty = new SimpleStringProperty(String.valueOf(weekly));
this.dailyProperty = new SimpleStringProperty(String.valueOf(daily));
this.noteProperty = new SimpleStringProperty(note);
}
/**
* Used to return the specified variable of the Statement instance.
*
*/
public String getName() {
return nameProperty.get();
}
public double getYearly() {
return yearly;
}
public double getMonthly() {
return monthly;
}
public double getWeekly() {
return weekly;
}
public double getDaily() {
return daily;
}
public String getNote() {
return noteProperty.get();
}
public StringProperty nameProperty() {
return nameProperty;
}
public StringProperty yearlyProperty() {
return yearlyProperty;
}
public StringProperty monthlyProperty() {
return monthlyProperty;
}
public StringProperty weeklyProperty() {
return weeklyProperty;
}
public StringProperty dailyProperty() {
return dailyProperty;
}
public StringProperty noteProperty() {
return noteProperty;
}
}
StatementTools
is a class containing static methods that are used strictly for calculating statement information.
Specific thought: it's a shame to have so many methods doing pretty much the exact same thing, but I couldn't think of a way to compress the four down into one efficiently.
package Java.Application;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created on 8/5/2016.
*
*/
public class StatementTools {
public static double getDailySummation(List<Statement> statements) {
return statements.stream().collect(Collectors.summingDouble(Statement::getDaily));
}
public static double getMonthlySummation(List<Statement> statements) {
return statements.stream().collect(Collectors.summingDouble(Statement::getMonthly));
}
public static double getWeeklySummation(List<Statement> statements) {
return statements.stream().collect(Collectors.summingDouble(Statement::getWeekly));
}
public static double getYearlySummation(List<Statement> statements) {
return statements.stream().collect(Collectors.summingDouble(Statement::getYearly));
}
}
FileTools
is a class used for extracting data from files:
package Java.Application;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
/**
* Created on 8/4/2016.
*/
public class FileTools {
public static List<String> getFileContents(File file) throws IOException {
return Files.readAllLines(file.toPath(), Charset.forName("utf-8"));
}
// Only compatible with windows files.
public static String getFileExtension(File file){
String fileName = file.getName();
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
public static String getFileName(File file){
String fileName = file.getName();
return fileName.substring(0, fileName.lastIndexOf("."));
}
public static File getMostRecentFile(String path) throws IOException, ParseException {
File[] files = Directory.getFiles(path);
switch(files.length){
case 0 : return null;
case 1 : return files[0];
}
File mostRecentFile = files[0];
Date mostRecentDate = new Date(mostRecentFile.lastModified());
for(int i = 1; i < files.length; i++){
Date possibleMostRecentDate = new Date(files[i].lastModified());
if(mostRecentDate.before(possibleMostRecentDate)){
mostRecentFile = files[i];
mostRecentDate = new Date(mostRecentFile.lastModified());
}
}
return mostRecentFile;
}
public static String readLine(String path, int line) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
// Reads line until wanted line number is reached.
String l = reader.readLine();
for(int i = 1; i <= line; i++){
l = reader.readLine();
}
reader.close();
return l;
}
}
Directory
is a small class containing methods for working with directories:
package Java.Application;
import java.io.File;
/**
* Created on 8/4/2016.
*/
public class Directory {
public static int getSize(String path){
return new File(path).listFiles().length;
}
public static File[] getFiles(String path){
return new File(path).listFiles();
}
}
An example of the inside of a .fmd file:
File name: Bob Smith.fmd
income:Programming,100000,8333.33,1851.85,273.97,None income:Holidays,1000,83.33,19.8,2.8,None expense:Cell Phone,1200,100,23.81,3.40,None expense:Internet,1800,150,35.71,5.10,None
I didn't post the fxml file as I think I've posted more than enough already, but if you want to see it, just comment.