Skip to content

Commit

Permalink
[#384] Replace JAXB with AB1 Storage code (#388)
Browse files Browse the repository at this point in the history
Storage uses the JAXB module for storing and retrieving of the
address book data in a file.

However, we are trying to ensure Java 9 compatibility in #381,
but the JAXB module is deprecated from Java 9 onwards.

As use of deprecated modules is considered bad practice, we should
replace our storage needs with another library before moving on
to ensure Java 9 compatibility.

As suggested here[1], let's reuse the storage code from
addressbook-level1.

[1]: se-edu#382 (comment)
  • Loading branch information
Zhiyuan-Amos authored Aug 9, 2018
2 parents ba89e36 + 9ee7769 commit 7cb648d
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 323 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Default storage file for addressbook : don't need to cleanup when running from IDE

addressbook.xml
addressbook.txt

# Compiled classfiles
bin/
Expand Down
90 changes: 90 additions & 0 deletions src/seedu/addressbook/storage/AddressBookDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package seedu.addressbook.storage;

import static seedu.addressbook.parser.Parser.PERSON_DATA_ARGS_FORMAT;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;

import seedu.addressbook.data.AddressBook;
import seedu.addressbook.data.exception.IllegalValueException;
import seedu.addressbook.data.person.Address;
import seedu.addressbook.data.person.Email;
import seedu.addressbook.data.person.Name;
import seedu.addressbook.data.person.Person;
import seedu.addressbook.data.person.Phone;
import seedu.addressbook.data.person.UniquePersonList;
import seedu.addressbook.data.tag.Tag;
import seedu.addressbook.storage.StorageFile.StorageOperationException;

/**
* Decodes the storage data file into an {@code AddressBook} object.
*/
public class AddressBookDecoder {

/**
* Decodes {@code encodedAddressBook} into an {@code AddressBook} containing the decoded persons.
*
* @throws IllegalValueException if any of the fields in any encoded person string is invalid.
* @throws StorageOperationException if the {@code encodedAddressBook} is in an invalid format.
*/
public static AddressBook decodeAddressBook(List<String> encodedAddressBook)
throws IllegalValueException, StorageOperationException {
final List<Person> decodedPersons = new ArrayList<>();
for (String encodedPerson : encodedAddressBook) {
decodedPersons.add(decodePersonFromString(encodedPerson));
}
return new AddressBook(new UniquePersonList(decodedPersons));
}

/**
* Decodes {@code encodedPerson} into a {@code Person}.
*
* @throws IllegalValueException if any field in the {@code encodedPerson} is invalid.
* @throws StorageOperationException if {@code encodedPerson} is in an invalid format.
*/
private static Person decodePersonFromString(String encodedPerson)
throws IllegalValueException, StorageOperationException {
final Matcher matcher = PERSON_DATA_ARGS_FORMAT.matcher(encodedPerson);
if (!matcher.matches()) {
throw new StorageOperationException("Encoded person in invalid format. Unable to decode.");
}

return new Person(
new Name(matcher.group("name")),
new Phone(matcher.group("phone"), isPrivatePrefixPresent(matcher.group("isPhonePrivate"))),
new Email(matcher.group("email"), isPrivatePrefixPresent(matcher.group("isEmailPrivate"))),
new Address(matcher.group("address"), isPrivatePrefixPresent(matcher.group("isAddressPrivate"))),
getTagsFromEncodedPerson(matcher.group("tagArguments"))
);
}

/**
* Returns true if {@code matchedPrefix} is equal to the private prefix for contact details.
*/
private static boolean isPrivatePrefixPresent(String matchedPrefix) {
return "p".equals(matchedPrefix);
}

/**
* Extracts the {@code Tag}s from the {@code tagArguments} string.
* Merges duplicate tag strings.
*/
private static Set<Tag> getTagsFromEncodedPerson(String tagArguments) throws IllegalValueException {
if (tagArguments.isEmpty()) {
return Collections.emptySet();
}

// replace first delimiter prefix, then split
final String[] tagStrings = tagArguments.replaceFirst(" t/", "").split(" t/");
final Set<Tag> tagSet = new HashSet<>();
for (String tagName : tagStrings) {
tagSet.add(new Tag(tagName));
}

return tagSet;
}
}
45 changes: 45 additions & 0 deletions src/seedu/addressbook/storage/AddressBookEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package seedu.addressbook.storage;

import java.util.ArrayList;
import java.util.List;

import seedu.addressbook.data.AddressBook;
import seedu.addressbook.data.person.Person;

/**
* Encodes the {@code AddressBook} object into a data file for storage.
*/
public class AddressBookEncoder {

/**
* Encodes all the {@code Person} in the {@code toSave} into a list of decodable and readable string presentation
* for storage.
*/
public static List<String> encodeAddressBook(AddressBook toSave) {
final List<String> encodedPersons = new ArrayList<>();
toSave.getAllPersons().forEach(person -> encodedPersons.add(encodePersonToString(person)));
return encodedPersons;
}

/**
* Encodes the {@code person} into a decodable and readable string representation.
*/
private static String encodePersonToString(Person person) {
final StringBuilder encodedPersonBuilder = new StringBuilder();

encodedPersonBuilder.append(person.getName());

encodedPersonBuilder.append(person.getPhone().isPrivate() ? " p" : " ");
encodedPersonBuilder.append("p/").append(person.getPhone().value);

encodedPersonBuilder.append(person.getEmail().isPrivate() ? " p" : " ");
encodedPersonBuilder.append("e/").append(person.getEmail().value);

encodedPersonBuilder.append(person.getAddress().isPrivate() ? " p" : " ");
encodedPersonBuilder.append("a/").append(person.getAddress().value);

person.getTags().forEach(tag -> encodedPersonBuilder.append(" t/").append(tag.tagName));

return encodedPersonBuilder.toString();
}
}
74 changes: 16 additions & 58 deletions src/seedu/addressbook/storage/StorageFile.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
package seedu.addressbook.storage;

import seedu.addressbook.data.AddressBook;
import seedu.addressbook.data.exception.IllegalValueException;
import seedu.addressbook.storage.jaxb.AdaptedAddressBook;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import seedu.addressbook.data.AddressBook;
import seedu.addressbook.data.exception.IllegalValueException;

/**
* Represents the file used to store address book data.
*/
public class StorageFile {

/** Default file path used if the user doesn't provide the file name. */
public static final String DEFAULT_STORAGE_FILEPATH = "addressbook.xml";
public static final String DEFAULT_STORAGE_FILEPATH = "addressbook.txt";

/* Note: Note the use of nested classes below.
* More info https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Expand All @@ -52,8 +41,6 @@ public StorageOperationException(String message) {
}
}

private final JAXBContext jaxbContext;

public final Path path;

/**
Expand All @@ -67,56 +54,38 @@ public StorageFile() throws InvalidStorageFilePathException {
* @throws InvalidStorageFilePathException if the given file path is invalid
*/
public StorageFile(String filePath) throws InvalidStorageFilePathException {
try {
jaxbContext = JAXBContext.newInstance(AdaptedAddressBook.class);
} catch (JAXBException jaxbe) {
throw new RuntimeException("jaxb initialisation error");
}

path = Paths.get(filePath);
if (!isValidPath(path)) {
throw new InvalidStorageFilePathException("Storage file should end with '.xml'");
throw new InvalidStorageFilePathException("Storage file should end with '.txt'");
}
}

/**
* Returns true if the given path is acceptable as a storage file.
* The file path is considered acceptable if it ends with '.xml'
* The file path is considered acceptable if it ends with '.txt'
*/
private static boolean isValidPath(Path filePath) {
return filePath.toString().endsWith(".xml");
return filePath.toString().endsWith(".txt");
}

/**
* Saves all data to this storage file.
* Saves the {@code addressBook} data to the storage file.
*
* @throws StorageOperationException if there were errors converting and/or storing data to file.
*/
public void save(AddressBook addressBook) throws StorageOperationException {

/* Note: Note the 'try with resource' statement below.
* More info: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
*/
try (final Writer fileWriter =
new BufferedWriter(new FileWriter(path.toFile()))) {

final AdaptedAddressBook toSave = new AdaptedAddressBook(addressBook);
final Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(toSave, fileWriter);

try {
List<String> encodedAddressBook = AddressBookEncoder.encodeAddressBook(addressBook);
Files.write(path, encodedAddressBook);
} catch (IOException ioe) {
throw new StorageOperationException("Error writing to file: " + path);
} catch (JAXBException jaxbe) {
throw new StorageOperationException("Error converting address book into storage format");
}
}

/**
* Loads data from this storage file.
* Loads the {@code AddressBook} data from this storage file, and then returns it.
* Returns an empty {@code AddressBook} if the file does not exist, or is not a regular file.
*
* @return an {@link AddressBook} containing the data in the file, or an empty {@link AddressBook} if it
* does not exist.
* @throws StorageOperationException if there were errors reading and/or converting data from file.
*/
public AddressBook load() throws StorageOperationException {
Expand All @@ -125,24 +94,13 @@ public AddressBook load() throws StorageOperationException {
return new AddressBook();
}

try (final Reader fileReader =
new BufferedReader(new FileReader(path.toFile()))) {

final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
final AdaptedAddressBook loaded = (AdaptedAddressBook) unmarshaller.unmarshal(fileReader);
// manual check for missing elements
if (loaded.isAnyRequiredFieldMissing()) {
throw new StorageOperationException("File data missing some elements");
}
return loaded.toModelType();

try {
return AddressBookDecoder.decodeAddressBook(Files.readAllLines(path));
} catch (FileNotFoundException fnfe) {
throw new AssertionError("A non-existent file scenario is already handled earlier.");
// other errors
} catch (IOException ioe) {
throw new StorageOperationException("Error writing to file: " + path);
} catch (JAXBException jaxbe) {
throw new StorageOperationException("Error parsing file data format");
} catch (IllegalValueException ive) {
throw new StorageOperationException("File contains illegal data values; data type constraints not met");
}
Expand Down
70 changes: 0 additions & 70 deletions src/seedu/addressbook/storage/jaxb/AdaptedAddressBook.java

This file was deleted.

Loading

0 comments on commit 7cb648d

Please sign in to comment.