Skip to content
Snippets Groups Projects
Commit 17c8f262 authored by Hallvard Trætteberg's avatar Hallvard Trætteberg
Browse files

Cleanups and documentation.

parent 2ddec7bf
No related branches found
No related tags found
No related merge requests found
Showing
with 269 additions and 79 deletions
......@@ -3,4 +3,4 @@
* [Domain model](doc/domain-model.md)
* [DB-based persistence](doc/db-persistence.md)
* [JSON-based serialization](doc/json-persistence.md)
* [Location-based storage](doc/location-based-storage.md)
* [Document storage](doc/document-storage.md)
# Document-based storage of domain data
By *document-based storage* we mean a storage model where a *document* containing domain data is loaded/saved *as a whole*, from/to some explicitly provided *location*. This storage model is typical for document-centric desktop apps with a **File** menu containing actions like **open**, **save**, **save-as** etc. The interfaces and implementation classes described here are designed to support such **File** menu actions. A class diagram of the main classes related to location-based storage is shown below.
* **IDocumentStorage**: An interface with the methods necessary for supporting the standard **File** menu actions. The interface is parameterized with the type used for representing the location to load from or save to. The class representing the document (domain data container) is implicit in the implementation of this interface. The interface includes methods for getting and setting the location and creating, opening and saving the (current) document.
* **IDocumentImporter**: An interface with a method for importing domain data from a location. The interface is parameterized with the location type. The main use is supporting an **import** action in a **File** menu.
* **IDocumentLoader**: An interface with a method for loading and returning a document (domain data container) from an **InputStream**. The interface is parameterized with the document type. This allows various ways of loading or importing domain data, with different sources and formats.
* **IDocumentSaver**: An interface with a method for saving a document (domain data container) to a location. The interface is parameterized with the document and location types. This allows various ways of saving or exporting domain data, to different locations and formats.
* **IDocumentPersistence**: Combination of **IDocumentLoader** and **IDocumentSaver**.
* **IDocumentStorageListener**: Listener interface for the location of the (current) document of an IDocumentStorage, e.g. when a **save-as** action is performed.
* **IDocumentListener**: Listener interface for the contents of the (current) document of an IDocumentStorage, e.g. when a document is created, opened or imported.
* **AbstractDocumentStorageImpl**: Incomplete implementation of **IDocumentStorage**, to simplify implementing ones for specific document and location types. The main missing methods are for getting and setting the current document, creating an empty one and creating an **InputStream** from a location.
<img src="location-based-storage.png" alt="Location-based persistence" style="width: 800px;"/>
# Location-based storage of domain data
A class diagram of the main classes related to location-based storage is shown below.
<img src="location-based-storage.png" alt="Location-based persistence" style="width: 800px;"/>
package tdt4140.gr1800.app.core;
/**
* Interface for geo-located data, i.e. classes that can return a corresponding LatLong object.
* @author hal
*
*/
public interface GeoLocated {
/**
* @return the corresponding LatLong object
*/
public LatLong getLatLong();
/**
* @return the corresponding latitude
*/
default double getLatitude() {
return getLatLong().latitude;
}
/**
* @return the corresponding longitude
*/
default double getLongitude() {
return getLatLong().longitude;
}
default boolean equalsLatLong(GeoLocated geoLoc) {
/**
* Checks that the latitudes and longitudes are the same
* @param geoLoc
* @return true if the latitudes and longitudes are the same, false otherwise
*/
default boolean equalsLatLong(final GeoLocated geoLoc) {
return getLatitude() == geoLoc.getLatitude() && getLongitude() == geoLoc.getLongitude();
}
default double distance(GeoLocated geoLoc) {
/**
* @param geoLoc
* @return the distance between the LatLong of this GeoLocated and the one provided
*/
default double distance(final GeoLocated geoLoc) {
return getLatLong().distance(geoLoc.getLatLong());
}
}
package tdt4140.gr1800.app.core;
/**
* Main implementation of GeoLocated. Combines a LatLong and elevation with generic values
* like name, description, time (implements Timed) and tags (implements Tagged).
* @author hal
*
*/
public class GeoLocation extends TimedTaggedImpl implements GeoLocated, Timed, Tagged {
private LatLong latLong;
@Override
public LatLong getLatLong() {
return latLong;
}
public void setLatLong(LatLong latLong) {
public void setLatLong(final LatLong latLong) {
this.latLong = latLong;
}
......@@ -18,7 +25,7 @@ public class GeoLocation extends TimedTaggedImpl implements GeoLocated, Timed, T
return elevation;
}
public void setElevation(int elevation) {
public void setElevation(final int elevation) {
this.elevation = elevation;
}
......@@ -28,7 +35,7 @@ public class GeoLocation extends TimedTaggedImpl implements GeoLocated, Timed, T
return name;
}
public void setName(String name) {
public void setName(final String name) {
this.name = name;
}
......@@ -36,7 +43,7 @@ public class GeoLocation extends TimedTaggedImpl implements GeoLocated, Timed, T
return description;
}
public void setDescription(String description) {
public void setDescription(final String description) {
this.description = description;
}
}
......@@ -3,24 +3,31 @@ package tdt4140.gr1800.app.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
public class Tags implements Tagged {
public class Tags implements Tagged, Iterable<String> {
private Collection<String> tags = null;
public Tags(String... tags) {
@Override
public Iterator<String> iterator() {
return (tags != null ? tags.iterator() : Collections.<String>emptyList().iterator());
}
public Tags(final String... tags) {
setTags(tags);
}
public Tags(Tagged tags) {
public Tags(final Tagged tags) {
setTags(tags.getTags());
}
public static Tags valueOf(String tags) {
public static Tags valueOf(final String tags) {
return valueOf(tags, ",");
}
public static Tags valueOf(String tags, String separator) {
public static Tags valueOf(final String tags, final String separator) {
return new Tags(tags.split(separator));
}
......@@ -29,7 +36,7 @@ public class Tags implements Tagged {
}
@Override
public boolean hasTags(String... tags) {
public boolean hasTags(final String... tags) {
return (tags.length == 0 || (this.tags != null && this.tags.containsAll(Arrays.asList(tags))));
}
......@@ -41,11 +48,11 @@ public class Tags implements Tagged {
}
@Override
public String getTags(String prefix, String separator, String suffix) {
StringBuilder buffer = new StringBuilder();
public String getTags(final String prefix, final String separator, final String suffix) {
final StringBuilder buffer = new StringBuilder();
append(buffer, prefix);
int tagNum = 0;
for (String tag : tags) {
for (final String tag : tags) {
if (tagNum > 0 && separator != null) {
buffer.append(separator);
}
......@@ -56,7 +63,7 @@ public class Tags implements Tagged {
return buffer.toString();
}
static StringBuilder append(StringBuilder buffer, String s) {
static StringBuilder append(final StringBuilder buffer, final String s) {
if (s != null) {
buffer.append(s);
}
......@@ -64,12 +71,13 @@ public class Tags implements Tagged {
}
@Override
public void setTags(String... tags) {
public void setTags(final String... tags) {
this.tags = new ArrayList<>();
addTags(tags);
}
public void addTags(String... tags) {
@Override
public void addTags(final String... tags) {
if (this.tags == null && tags != null && tags.length > 0) {
this.tags = new ArrayList<>();
}
......@@ -80,7 +88,8 @@ public class Tags implements Tagged {
}
}
public void removeTags(String... tags) {
@Override
public void removeTags(final String... tags) {
if (this.tags != null) {
this.tags.removeAll(Arrays.asList(tags));
}
......
......@@ -7,26 +7,26 @@ public class TimedTaggedImpl extends TimedImpl implements Tagged {
public TimedTaggedImpl() {
}
public TimedTaggedImpl(Tagged tags) {
public TimedTaggedImpl(final Tagged tags) {
setTags(tags.getTags());
}
private static TimedTaggedImpl valueOf(Tags tags) {
TimedTaggedImpl timedTags = new TimedTaggedImpl();
private static TimedTaggedImpl valueOf(final Tags tags) {
final TimedTaggedImpl timedTags = new TimedTaggedImpl();
timedTags.tags = tags;
return timedTags;
}
public static TimedTaggedImpl valueOf(String tags) {
public static TimedTaggedImpl valueOf(final String tags) {
return valueOf(Tags.valueOf(tags));
}
public static TimedTaggedImpl valueOf(String tags, String separator) {
public static TimedTaggedImpl valueOf(final String tags, final String separator) {
return valueOf(Tags.valueOf(tags, separator));
}
@Override
public boolean hasTags(String... tags) {
public boolean hasTags(final String... tags) {
return this.tags != null && this.tags.hasTags(tags);
}
......@@ -36,23 +36,25 @@ public class TimedTaggedImpl extends TimedImpl implements Tagged {
}
@Override
public String getTags(String prefix, String separator, String suffix) {
public String getTags(final String prefix, final String separator, final String suffix) {
return (tags != null ? tags.getTags(prefix, separator, suffix) : Tags.append(Tags.append(new StringBuilder(), prefix), suffix).toString());
}
@Override
public void setTags(String... tags) {
public void setTags(final String... tags) {
this.tags = (tags != null && tags.length > 0 ? new Tags(tags) : null);
}
public void addTags(String... tags) {
@Override
public void addTags(final String... tags) {
if (this.tags == null) {
this.tags = new Tags();
}
this.tags.addTags(tags);
}
public void removeTags(String... tags) {
@Override
public void removeTags(final String... tags) {
if (this.tags != null) {
this.tags.removeTags(tags);
if (this.tags.getTagCount() == 0) {
......
......@@ -5,6 +5,15 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
/**
* Incomplete implementation of **IDocumentStorage**, to simplify implementing ones for specific document and location types.
* The main missing methods are for getting and setting the current document, creating an empty one and
* creating an **InputStream** from a location.
* @author hal
*
* @param <D> the document type
* @param <L> the location type
*/
public abstract class AbstractDocumentStorageImpl<D, L> implements IDocumentStorage<L>, IDocumentPersistence<D, L> {
private L documentLocation;
......@@ -15,46 +24,61 @@ public abstract class AbstractDocumentStorageImpl<D, L> implements IDocumentStor
}
@Override
public void setDocumentLocation(L documentLocation) {
L oldDocumentLocation = this.documentLocation;
public void setDocumentLocation(final L documentLocation) {
final L oldDocumentLocation = this.documentLocation;
this.documentLocation = documentLocation;
fireDocumentLocationChanged(oldDocumentLocation);
}
protected void setDocumentAndLocation(D document, L documentLocation) {
protected void setDocumentAndLocation(final D document, final L documentLocation) {
setDocument(document);
setDocumentLocation(documentLocation);
}
/**
* Returns the current document.
* @return the current document
*/
protected abstract D getDocument();
/**
* Sets the current document
* @param document the new document
*/
protected abstract void setDocument(D document);
//
private Collection<IDocumentStorageListener<L>> documentListeners = new ArrayList<IDocumentStorageListener<L>>();
private final Collection<IDocumentStorageListener<L>> documentListeners = new ArrayList<IDocumentStorageListener<L>>();
public void addDocumentStorageListener(IDocumentStorageListener<L> documentStorageListener) {
@Override
public void addDocumentStorageListener(final IDocumentStorageListener<L> documentStorageListener) {
documentListeners.add(documentStorageListener);
}
public void removeDocumentStorageListener(IDocumentStorageListener<L> documentStorageListener) {
@Override
public void removeDocumentStorageListener(final IDocumentStorageListener<L> documentStorageListener) {
documentListeners.remove(documentStorageListener);
}
protected void fireDocumentLocationChanged(L oldDocumentLocation) {
for (IDocumentStorageListener<L> documentStorageListener : documentListeners) {
protected void fireDocumentLocationChanged(final L oldDocumentLocation) {
for (final IDocumentStorageListener<L> documentStorageListener : documentListeners) {
documentStorageListener.documentLocationChanged(documentLocation, oldDocumentLocation);
}
}
protected void fireDocumentChanged(D oldDocument) {
for (IDocumentStorageListener<L> documentListener : documentListeners) {
protected void fireDocumentChanged(final D oldDocument) {
for (final IDocumentStorageListener<L> documentListener : documentListeners) {
if (documentListener instanceof IDocumentListener) {
((IDocumentListener<D, L>) documentListener).documentChanged(getDocument(), oldDocument);
}
}
}
/**
* Creates a new and empty document.
* @return
*/
protected abstract D createDocument();
@Override
......@@ -62,13 +86,19 @@ public abstract class AbstractDocumentStorageImpl<D, L> implements IDocumentStor
setDocumentAndLocation(createDocument(), null);
}
protected abstract InputStream toInputStream(L storage) throws IOException;
/**
* Creates an ImportStream from a location
* @param location
* @return
* @throws IOException
*/
protected abstract InputStream toInputStream(L location) throws IOException;
@Override
public void openDocument(L storage) throws IOException {
public void openDocument(final L storage) throws IOException {
try (InputStream input = toInputStream(storage)){
setDocumentAndLocation(loadDocument(input), storage);
} catch (Exception e) {
} catch (final Exception e) {
throw new IOException(e);
}
}
......@@ -77,23 +107,23 @@ public abstract class AbstractDocumentStorageImpl<D, L> implements IDocumentStor
public void saveDocument() throws IOException {
try {
saveDocument(getDocument(), getDocumentLocation());
} catch (Exception e) {
} catch (final Exception e) {
throw new IOException(e);
}
}
public void saveDocumentAs(L documentLocation) throws IOException {
L oldDocumentLocation = getDocumentLocation();
public void saveDocumentAs(final L documentLocation) throws IOException {
final L oldDocumentLocation = getDocumentLocation();
setDocumentLocation(documentLocation);
try {
saveDocument();
} catch (IOException e) {
} catch (final IOException e) {
setDocumentLocation(oldDocumentLocation);
throw e;
}
}
public void saveCopyAs(L documentLocation) throws Exception {
public void saveCopyAs(final L documentLocation) throws Exception {
saveDocument(getDocument(), documentLocation);
}
}
......@@ -3,6 +3,17 @@ package tdt4140.gr1800.app.doc;
import java.io.IOException;
import java.io.InputStream;
/**
* An interface with a method for importing domain data from a location.
* The main use is supporting an **import** action in a **File** menu.
* @author hal
*
*/
public interface IDocumentImporter {
/**
* Loads a document from the input stream and sets it as the current document.
* @param inputStream
* @throws IOException
*/
public void importDocument(InputStream inputStream) throws IOException;
}
package tdt4140.gr1800.app.doc;
/**
* Listener interface for the (contents of) the (current) document of an IDocumentStorage, e.g.
* when an **open** action is performed.
* @author hal
*
* @param <D> the document type
* @param <L> the location type
*/
public interface IDocumentListener<D, L> extends IDocumentStorageListener<L> {
/**
* Notifies that the current document has changed.
* @param document the new document
* @param oldDocument the previous document
*/
public void documentChanged(D document, D oldDocument);
}
......@@ -2,6 +2,19 @@ package tdt4140.gr1800.app.doc;
import java.io.InputStream;
/**
* An interface with a method for loading and returning a document (domain data container) from an InputStream.
* This allows various ways of loading or importing domain data, with different sources and formats.
* @author hal
*
* @param <D> the document type
*/
public interface IDocumentLoader<D> {
/**
* Loads and returns a new document from an InputStream
* @param inputStream
* @return
* @throws Exception
*/
public D loadDocument(InputStream inputStream) throws Exception;
}
package tdt4140.gr1800.app.doc;
/**
* An interface with a method for saving a document (domain data container) to a location.
* This allows various ways of saving or exporting domain data, to different locations and formats.
* @author hal
*
* @param <D> the document type
* @param <L> the location type
*/
public interface IDocumentSaver<D, L> {
/**
* Saves the provided document to the provided location
* @param document
* @param documentLocation
* @throws Exception
*/
public void saveDocument(D document, L documentLocation) throws Exception;
}
......@@ -3,16 +3,57 @@ package tdt4140.gr1800.app.doc;
import java.io.IOException;
import java.util.Collection;
/**
* An interface with the methods necessary for supporting the standard File menu actions.
* The class representing the document (domain data container) is implicit in the implementation of this interface.
* The interface includes methods for getting and setting the location and creating, opening and saving the (current) document.
* @author hal
*
* @param <L> The type of the location, typically java.io.File.
*/
public interface IDocumentStorage<L> {
/**
* Returns the current location (of the current document).
* @return the current location
*/
public L getDocumentLocation();
/**
* Sets the current location (of the current document), can be used by a save-as action.
* @param documentLocation
*/
public void setDocumentLocation(L documentLocation);
/**
* Adds an IDocumentStorageListener that will be notified when the current location changes.
* @param documentStorageListener
*/
public void addDocumentStorageListener(IDocumentStorageListener<L> documentStorageListener);
/**
* Removes an IDocumentStorageListener.
* @param documentStorageListener
*/
public void removeDocumentStorageListener(IDocumentStorageListener<L> documentStorageListener);
/**
* Creates a new documents and sets it as the current one, can be used by a new action.
*/
public void newDocument();
/**
* Loads a documents from the provided location and sets it as the current one, can be used by an open action.
*/
public void openDocument(L documentLocation) throws IOException;
/**
* Saves the current document (to the current location), can be used by a save action.
*/
public void saveDocument() throws IOException;
/**
* Returns the set of IDocumentImporters, can be used by an import action.
* @return
*/
public Collection<IDocumentImporter> getDocumentImporters();
}
package tdt4140.gr1800.app.doc;
/**
* Listener interface for the (current) location of the (current) document of an IDocumentStorage, e.g.
* when a **save-as** action is performed.
* @author hal
*
* @param <L>
*/
public interface IDocumentStorageListener<L> {
/**
* Notifies that the current document location has changed.
* @param documentLocation the new document location
* @param oldDocumentLocation the previous document location
*/
public void documentLocationChanged(L documentLocation, L oldDocumentLocation);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment