Commit 6fbcd07e authored by Arne Styve's avatar Arne Styve
Browse files

Merge branch 'release/v0.4'

# Conflicts:
#	.gitignore
#	Contacts.iml
Conflicts resolved.
Ready for release of v0.4
parents fec90081 e4c56b67
......@@ -2,4 +2,6 @@
addressbook.dat
.idea/sonarlint
/vpproject/
*.vpdm
/Contacts.vpdm/
/derby.log
/contactsdb/
\ No newline at end of file
<component name="libraryTable">
<library name="libs">
<CLASSES>
<root url="file://$PROJECT_DIR$/libs" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/libs" recursive="false" />
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="CheckStyle-IDEA-Module">
<option name="configuration">
<map />
</option>
<component name="FacetManager">
<facet type="jpa" name="JPA">
<configuration>
<setting name="validation-enabled" value="true" />
<datasource-mapping>
<factory-entry name="contacts-pu" />
</datasource-mapping>
<naming-strategy-map />
<deploymentDescriptor name="persistence.xml" url="file://$MODULE_DIR$/src/META-INF/persistence.xml" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
......@@ -12,5 +19,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="libs" level="project" />
</component>
</module>
\ No newline at end of file
# Contacts - an address book project
## Introduction
A JavaFX-project of a typical address book application. Used to demonstrate JavaFX, GUI design, use of the TableView-component and the mapping of the TabelView to the backend register. Also includes example of file handling.
A JavaFX-project of a typical address book application. Used to demonstrate JavaFX, GUI design, use of the
TableView-component and the mapping of the TableView to the backend register. Also includes example of file handling.
The project was developed for use in teaching in the course "IDATx2001 Programmering 2" at NTNU spring 2020.
![Screendump](Screendump.png)
![Screen dump](Screendump.png)
## Release notes
**Version** | **Description**
--------|------------
v0.4 | Added Relational Database support, using the embedded Apache Derby server. For details of the changes made, se below.
v0.3 | Adds object serialization of the entire address book.
v0.2 | Added import and export from/to CSV-file. A default CSV-file is provided (Addresses.csv)
v0.1 | First release with basic add, edit, delete functionality.
### Release v0.4
The following changes were made to enable support for a Relational Database server (using the **embedded** mode of
the [Apache Derby RDBMS](https://db.apache.org/derby/), using the [EclipseLink](https://www.eclipse.org/eclipselink/)
persistence provider (JPA):
* **Added libraries** - Added a new folder, called *"libs"* in the IntelliJ project. Copied the **eclipselink.jar**
and the **jakarta.persistence_2.2.3.jar** files to the *libs*-folder for the **EclipseLink**-JPA support. Added
**derby.jar** to the *libs*-folder for the Apache Derby embedded support.
* **persistence.xml** - Added this file (used by the JPA-framework EclipseLink) to a folder named "**META-INF**", which
is the same folder that the **MANIFEST.MF** should be.
* **ContactDetails**-class: Added ORM/JPA specific annotations, like *@Entity*, *@Id* etc. Also, it must provide a
default constructor with no parameters.
* **AddressBook** - interface: Added an Interface named *AddressBook*, renamed the exsisting *AddressBook* to
*AddressBookPlain*. (Used the IntelliJ *Refactoring*: Extract Interface..)
* Added a new class **AddressBookDAO** which implements the interface AddressBook. This is the **Data Access Object**
(DAO) providing the interface to the database. The class maps the methods defined in the interface to database
actions.
## Description of the project
The project is made to demonstrate a typical application with a graphical user interface (GUI) implemented in JavaFX.
It's a classic address book example, where you can create contacts to be added to the address book, edit existing
contacts, and delete contacts.
The project does **not** make use of FXML and SceneBulider, but builds the GUI from within the Java code.
The project does **not** make use of FXML and SceneBuilder, but builds the GUI from within the Java code.
#### JavaFX concepts demonstrated in the project
The project uses JDK 8 for simplicity, since JavaFX was bundeled with the JDK up to and including JDK 8. After JDK 8
The project uses JDK 8 for simplicity, since JavaFX was bundled with the JDK up to and including JDK 8. After JDK 8
, JavaFX (and other packages) were moved into *modules* making it a bit more complex to create JavaFX based
applications.
The following JavaFX concepts are demonstrated in this project:
* General JavaFX structure: Stage, Scene, Scenegraph, Nodes etc.
* Event handling, using Lamda
* Event handling, using Lambda
* Menu bar, toolbar, status bar
* Buttons with icons
* MenuItems with icon and keyboard shortcuts.
......
File added
Manifest-Version: 1.0
Created-By: 1.8.0_201 (Oracle Corporation)
Main-Class: no.ntnu.idata2001.contacts.views.ContactsApp
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="contacts-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:derby:contactsdb;create=true"/>
<property name="javax.persistence.jdbc.user" value="app"/>
<property name="javax.persistence.jdbc.password" value="app"/>
<property name="eclipselink.target-database" value="Derby"/>
<!-- Alternatives: create-tables, drop-and-create-tables-->
<property name="eclipselink.ddl-generation" value="create-tables"/>
<!-- Alternatives: FINE (logs all SQL), ALL, CONFIG, INFO, WARNING..., OFF -->
<property name="eclipselink.logging.level" value="FINE"/>
<!--property name="javax.persistence.sql-load-script-source" value="META-INF/sql/data.sql"/-->
</properties>
</persistence-unit>
</persistence>
\ No newline at end of file
INSERT INTO CONTACTDETAILS(Id, Name, Phone, Address) VALUES(1, 'Jan Jensen', '(+47) 342 32 456', 'Gate 5, 5700 Voss')
INSERT INTO CONTACTDETAILS(Id, Name, Phone, Address) VALUES(2, 'Ola Jensen', '(+47) 342 32 456', 'Gate 5, 5700 Voss')
INSERT INTO CONTACTDETAILS(Id, Name, Phone, Address) VALUES(3, 'Per Jensen', '(+47) 342 32 456', 'Gate 5, 5700 Voss')
\ No newline at end of file
......@@ -19,6 +19,7 @@ import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;
import javafx.util.Pair;
import no.ntnu.idata2001.contacts.model.AddressBook;
import no.ntnu.idata2001.contacts.model.AddressBookDAO;
import no.ntnu.idata2001.contacts.model.AddressBookFileHandler;
import no.ntnu.idata2001.contacts.model.ContactDetails;
import no.ntnu.idata2001.contacts.views.ContactDetailsDialog;
......@@ -205,6 +206,15 @@ public class MainController {
return AddressBookFileHandler.loadFromFile(inFile);
}
/**
* Loads an entire AddressBook from a database.
*
* @return an address book populated by contact details loaded from the database.
*/
public AddressBook loadAddressBookFromDB() {
return new AddressBookDAO();
}
/**
* Exit the application. Displays a confirmation dialog.
*/
......
......@@ -3,35 +3,8 @@ package no.ntnu.idata2001.contacts.model;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeMap;
/**
* Represents an Address book containing contacts with contact details.
* Based on the example in the book "Objects first with Java" by David J. Barnes
* and Michael Kölling.
*
* <p>Each contact is stored in a TreeMap using the phone number as the key.
*
* @author David J. Barnes and Michael Kölling and Arne Styve
* @version 2020.03.16
*/
public class AddressBook implements Serializable, Iterable<ContactDetails> {
// Storage for an arbitrary number of details.
// We have chosen to use TreeMap instead of HashMap in this example, the
// main difference being that a TreeMap is sorted. That is, the keys are sorted,
// so when retrieving an Iterator from a TreeMap-collection, the iterator will
// iterate in a sorted manner, which wil not be the case for a HashMap.
// TreeMap is a bit less efficient than a HashMap in terms of searching, du to the
// sorted order. For more details on the difference:
// https://javatutorial.net/difference-between-hashmap-and-treemap-in-java
private TreeMap<String, ContactDetails> book;
/**
* Creates an instance of the AddressBook, initialising the instance.
*/
public AddressBook() {
book = new TreeMap<>();
}
public interface AddressBook extends Serializable, Iterable<ContactDetails> {
/**
* Searches for a contact matching the phone number given by the parameter.
......@@ -41,9 +14,7 @@ public class AddressBook implements Serializable, Iterable<ContactDetails> {
* @param phoneNumber The number to be looked up.
* @return The details corresponding to the phone number.
*/
public ContactDetails findContactByPhoneNumber(String phoneNumber) {
return book.get(phoneNumber);
}
ContactDetails findContactByPhoneNumber(String phoneNumber);
/**
* Return whether or not the current phone number is in use.
......@@ -51,20 +22,14 @@ public class AddressBook implements Serializable, Iterable<ContactDetails> {
* @param phoneNumber The name or phone number to be looked up.
* @return true if the phoneNumber is in use, false otherwise.
*/
public boolean keyInUse(String phoneNumber) {
return book.containsKey(phoneNumber);
}
boolean keyInUse(String phoneNumber);
/**
* Add a new contact to the address book.
*
* @param contact The contact to be added.
*/
public void addContact(ContactDetails contact) {
if (contact != null) {
book.put(contact.getPhone(), contact);
}
}
void addContact(ContactDetails contact);
/**
* Change the contact previously stored under the given key.
......@@ -73,13 +38,8 @@ public class AddressBook implements Serializable, Iterable<ContactDetails> {
* This should be a key that is currently in use.
* @param contact The replacement contact.
*/
public void changeDetails(String oldKey,
ContactDetails contact) {
if (keyInUse(oldKey) && contact != null) {
removeContact(oldKey);
addContact(contact);
}
}
void changeDetails(String oldKey,
ContactDetails contact);
/**
* Return the number of entries currently in the
......@@ -87,33 +47,28 @@ public class AddressBook implements Serializable, Iterable<ContactDetails> {
*
* @return The number of entries.
*/
public int getNumberOfEntries() {
return this.book.size();
}
int getNumberOfEntries();
/**
* Remove the contact with the given phonenumber from the address book.
* Remove the contact with the given phone number from the address book.
* The phone number should be one that is currently in use.
*
* @param phoneNumber The phone number to the contact to remove
*/
public void removeContact(String phoneNumber) {
if (keyInUse(phoneNumber)) {
this.book.remove(phoneNumber);
}
}
void removeContact(String phoneNumber);
/**
* Returns all the contacts as a collection.
*
* @return all the contacts as a collection.
*/
public Collection<ContactDetails> getAllContacts() {
return this.book.values();
}
Collection<ContactDetails> getAllContacts();
/**
* Close the data source.
*/
void close();
@Override
public Iterator<ContactDetails> iterator() {
return this.book.values().iterator();
}
Iterator<ContactDetails> iterator();
}
package no.ntnu.idata2001.contacts.model;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class AddressBookDAO implements AddressBook {
// Note the 'transient'-keyword. This is to indicate that these fields
// should never be serialized. Since the AddressBook-interface
// implements Serializable (used for object serialisation), this class
// to is tagged to be serializable. But the two fields in this class is used to
// communicate with the database, and should hence not be serialized. Neither are
// the classes EntityManager nor EntityManagerFactory serializable.
private final transient EntityManagerFactory efact;
private final transient EntityManager eman;
/**
* Creates an instance of the AddressbookDAO.
*/
public AddressBookDAO() {
this.efact = Persistence.createEntityManagerFactory("contacts-pu");
this.eman = efact.createEntityManager();
}
@Override
public ContactDetails findContactByPhoneNumber(String phoneNumber) {
return null;
}
@Override
public boolean keyInUse(String phoneNumber) {
return false;
}
@Override
public void addContact(ContactDetails contact) {
this.eman.getTransaction().begin();
this.eman.persist(contact);
this.eman.getTransaction().commit();
}
@Override
public void changeDetails(String oldKey, ContactDetails contact) {
//TODO: To be implemented later....
}
@Override
public int getNumberOfEntries() {
return this.getAllContacts().size();
}
@Override
public void removeContact(String phoneNumber) {
//TODO: To be implemented later...
}
@Override
public Collection<ContactDetails> getAllContacts() {
List<ContactDetails> contactsList = null;
String sql = "SELECT c FROM ContactDetails c";
Query query = eman.createQuery(sql);
contactsList = query.getResultList();
return contactsList;
}
@Override
public void close() {
this.eman.close();
this.efact.close();
}
@Override
public Iterator<ContactDetails> iterator() {
return getAllContacts().iterator();
}
}
......@@ -154,7 +154,7 @@ public class AddressBookFileHandler {
} catch (IOException | ClassNotFoundException e) {
logger.log(Level.INFO, "Could not open file "
+ inFile.getName() + ". An empty AddressBook was returned.");
return new AddressBook();
return new AddressBookPlain();
}
}
}
package no.ntnu.idata2001.contacts.model;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeMap;
/**
* Represents an Address book containing contacts with contact details.
* Based on the example in the book "Objects first with Java" by David J. Barnes
* and Michael Kölling.
*
* <p>Each contact is stored in a TreeMap using the phone number as the key.
*
* @author David J. Barnes and Michael Kölling and Arne Styve
* @version 2020.03.16
*/
public class AddressBookPlain implements AddressBook {
// Storage for an arbitrary number of details.
// We have chosen to use TreeMap instead of HashMap in this example, the
// main difference being that a TreeMap is sorted. That is, the keys are sorted,
// so when retrieving an Iterator from a TreeMap-collection, the iterator will
// iterate in a sorted manner, which wil not be the case for a HashMap.
// TreeMap is a bit less efficient than a HashMap in terms of searching, du to the
// sorted order. For more details on the difference:
// https://javatutorial.net/difference-between-hashmap-and-treemap-in-java
private final TreeMap<String, ContactDetails> book;
/**
* Creates an instance of the AddressBook, initialising the instance.
*/
public AddressBookPlain() {
book = new TreeMap<>();
}
@Override
public ContactDetails findContactByPhoneNumber(String phoneNumber) {
return book.get(phoneNumber);
}
@Override
public boolean keyInUse(String phoneNumber) {
return book.containsKey(phoneNumber);
}
@Override
public void addContact(ContactDetails contact) {
if (contact != null) {
book.put(contact.getPhone(), contact);
}
}
@Override
public void changeDetails(String oldKey,
ContactDetails contact) {
if (keyInUse(oldKey) && contact != null) {
removeContact(oldKey);
addContact(contact);
}
}
@Override
public int getNumberOfEntries() {
return this.book.size();
}
@Override
public void removeContact(String phoneNumber) {
if (keyInUse(phoneNumber)) {
this.book.remove(phoneNumber);
}
}
@Override
public Collection<ContactDetails> getAllContacts() {
return this.book.values();
}
@Override
public void close() {
// Nothing needed to be done. Intentionally left empty.
}
@Override
public Iterator<ContactDetails> iterator() {
return this.book.values().iterator();
}
}
package no.ntnu.idata2001.contacts.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* Holds details about a contact, like name, address and phone number.
......@@ -10,7 +13,11 @@ import java.io.Serializable;
* @author David J. Barnes and Michael Kölling and Arne Styve
* @version 2020.03.16
*/
@Entity
public class ContactDetails implements Comparable<ContactDetails>, Serializable {
@Id
@GeneratedValue
private Integer id; // A unique ID
private String name;
private String phone;
private String address;
......@@ -39,6 +46,14 @@ public class ContactDetails implements Comparable<ContactDetails>, Serializable
this.address = address.trim();
}
/**
* Default constructor. Required according to the Java Beans standard,
* required by JPA.
*/
public ContactDetails() {
// Intentionally left empty.
}
/**
* Returns the name.
*
......
......@@ -32,7 +32,7 @@ import no.ntnu.idata2001.contacts.model.ContactDetails;
*/
public class ContactsApp extends Application {
private static final String VERSION = "0.3";
private static final String VERSION = "0.4";
private MainController mainController;
private AddressBook addressBook;
......@@ -41,7 +41,7 @@ public class ContactsApp extends Application {
private ObservableList<ContactDetails> addressBookListWrapper;
// Need to keep track of the TableView-instance since we need to access it
// from different places in our GUI (menu, doubleclicking, toolbar etc.)
// from different places in our GUI (menu, double clicking, toolbar etc.)
private TableView<ContactDetails> contactDetailsTableView;
/**
......@@ -61,7 +61,8 @@ public class ContactsApp extends Application {
this.mainController = new MainController();
// Initialise the Address Book from a file
this.addressBook = this.mainController.loadAddressBookFromFile();
//this.addressBook = this.mainController.loadAddressBookFromFile();
this.addressBook = this.mainController.loadAddressBookFromDB();
}
@Override
......@@ -70,9 +71,9 @@ public class ContactsApp extends Application {
BorderPane root = new BorderPane(); // Create the root node. The Menu will be placed at the top
VBox topContainer = new VBox(); //Creates a container to hold all Menu Objects.
MenuBar mainMenu = createMenus(); //Creates our main menu to hold our Sub-Menus.
ToolBar toolBar = createToolBar(); // Creates a toolbar below the menubar
ToolBar toolBar = createToolBar(); // Creates a toolbar below the menu bar
// Place the menubar in the topContainer
// Place the menu bar in the topContainer
topContainer.getChildren().add(mainMenu);
// Place the Toolbar
topContainer.getChildren().add(toolBar);
......@@ -105,7 +106,7 @@ public class ContactsApp extends Application {
@Override
public void stop() {
// Save the address book to file
this.mainController.saveAddressBookToFile(this.addressBook);
//this.mainController.saveAddressBookToFile(this.addressBook);
// Exit the application
System.exit(0);
}
......@@ -178,7 +179,7 @@ public class ContactsApp extends Application {
/**
* Create a statusbar to be added at the bottom of the GUI.
* Create a status bar to be added at the bottom of the GUI.
*
* @return a status bar
*/
......@@ -199,7 +200,7 @@ public class ContactsApp extends Application {
private ToolBar createToolBar() {
//Create some Buttons.
// Create the add new contact buton
// Create the add new contact button
Button addContactBtn = new Button();
//Set the icon/graphic for the ToolBar Buttons.
......@@ -238,7 +239,7 @@ public class ContactsApp extends Application {
/**
* Creates the menu bar to be placed above the toolbar.
*
* @return a menubar
* @return a menu bar
*/
private MenuBar createMenus() {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment