Implements an Undo/Redo in MVC - java

I have a Java application and I want to implement an Undo/Redo option. the value that I want to stock and that I want to be able to recover is an integer.
My Class Model implements the interface StateEditable and I have to redefine the 2 functions restoreState(Hashtable<?, ?> state) and storeState(Hashtable<Object, Object> state) but I don't know what to put on them. It will be really great if somebody can help me to do that.
These are the first lines of my Model class, the value that I want to do an undo/redo on it is value
public class Model extends Observable implements StateEditable
{
private int value = 5;
private UndoManager undoRedo = new UndoManager();
final UndoableEditListener editListener = new UndoableEditListener() {
public void undoableEditHappened(UndoableEditEvent evt) {
undoRedo.addEdit(evt.getEdit());
}
};
#Override
public void restoreState(Hashtable<?, ?> state)
{
}
#Override
public void storeState(Hashtable<Object, Object> state)
{
}
}

From looking through an example of StateEditable, it would appear that in your storeState method, you need to populate the Hashtable that is passed in. Similarly, you assign state in your restoreState from the Hashtable that is passed in. You will need to define a key for the value in the Hashtable. With that in mind, I suggest that you add
private final String KEY = "myKey";
to the top of your file, and then fill out the two methods like so:
#Override
public void restoreState(Hashtable<?, ?> state)
{
Object val = state.get(KEY);
if( val instanceof Integer ) //performs the null test for us.
{
value = val;
}
}
#Override
public void storeState(Hashtable<Object, Object> state)
{
state.put(KEY, value);
}

Related

How do I structure factory classes to allow for a fluent interface?

I want to create a series of Actions that do related things
public interface Action{
public void execute();
}
public class DatabaseAction implements Action{
public void execute(){}
}
public class WebAction implements Action{
public void execute(){}
}
public class EmailAction implements Action{
public void execute(){}
}
Generally speaking, users don't care about the details. They want all the actions to run and not worry about it.
But there's going to be some special cases where they only want to run some of the actions, and configure some of the actions.
And I suppose there could be cases where configuration is non-optional.
I figure a fluent interface is the most readable here.
// Executes all Actions - intended to be used in almost all cases
// I write to a database, call a web API, and send an email.
Actions.withAllDefaults().execute();
// I don't need to send an email and I need to configure the database
Actions.withAction(DATABASE_ACTION)
.withConfiguration(DatabaseAction.PORT, 9000)
.withAction(WEB_ACTION)
.execute();
It feels like I should be implementing some sort of factory but it's hard for me to actually translate that into code.
Consider using the Fluent Builder Pattern instead of trying to make your factory fluent.
C# uses fluent programming extensively in LINQ to build queries using "standard query operators".
This is C# implementation. It looks like this sample code can be converted to Java as special features of C# is not used.
So let's see an example. We start from an interface IFluent which allows to build your actions with settings:
public interface IFluent
{
IFluent WithAction(Action action);
IFluent WithConfiguration(KeyValuePair<string, object> configuration);
}
and this is Fluent class which implements IFluent interface:
public class Fluent : IFluent
{
private List<Action> actions;
public IFluent WithAction(Action action)
{
if (actions == null)
actions = new List<Action>();
actions.Add(action);
return this;
}
public IFluent WithConfiguration(KeyValuePair<string, object> configuration)
{
if (actions == null || actions.Count == 0)
throw new InvalidOperationException("There are no actions");
int currentActionIndex = actions.Count - 1;
actions[currentActionIndex].Set(configuration);
return this;
}
}
Then we create an abstract class for Action that should define behavior for derived classes:
public abstract class Action
{
public abstract Dictionary<string, object> Properties { get; set; }
public abstract void Execute();
public abstract void Set(KeyValuePair<string, object> configuration);
public abstract void Add(string name, object value);
}
And our derived classes would look like this:
public class DatabaseAction : Abstract.Action
{
public override Dictionary<string, object> Properties { get; set; }
= new Dictionary<string, object>()
{
{ "port", 0},
{ "connectionString", "foobarConnectionString"},
{ "timeout", 60}
};
public override void Execute()
{
Console.WriteLine("It is a database action");
}
public override void Set(KeyValuePair<string, object> configuration)
{
if (Properties.ContainsKey(configuration.Key))
Properties[configuration.Key] = configuration.Value;
}
public override void Add(string name, object value)
{
Properties.Add(name, value);
}
}
and EmailAction:
public class EmailAction : Abstract.Action
{
public override Dictionary<string, object> Properties { get; set; }
= new Dictionary<string, object>()
{
{ "from", "Head First - Object Oriented Design"},
{ "to", "who wants to learn object oriented design"},
{ "index", 123456}
};
public override void Execute()
{
Console.WriteLine("It is a email action");
}
public override void Set(KeyValuePair<string, object> configuration)
{
if (Properties.ContainsKey(configuration.Key))
Properties[configuration.Key] = configuration.Value;
}
public override void Add(string name, object value)
{
Properties.Add(name, value);
}
}
and WebAction:
public class WebAction : Abstract.Action
{
public override Dictionary<string, object> Properties { get; set; }
= new Dictionary<string, object>()
{
{ "foo", "1"},
{ "bar", "2"},
{ "hey", "hi"}
};
public override void Execute()
{
Console.WriteLine("It is a email action");
}
public override void Set(KeyValuePair<string, object> configuration)
{
if (Properties.ContainsKey(configuration.Key))
Properties[configuration.Key] = configuration.Value;
}
public override void Add(string name, object value)
{
Properties.Add(name, value);
}
}
The it is possible to call code like this:
Fluent actions = new Fluent();
actions.WithAction(new DatabaseAction())
.WithConfiguration(new KeyValuePair<string, object>("port", 1))
.WithAction(new EmailAction())
.WithConfiguration(new KeyValuePair<string, object>("to", "me"));

JavaFx: Populate ComboBox with different enums depending on another ComboBox

I have two ComboBoxes:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<>();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
Depending on the value chosen in comboBoxMainCategory, the comboBoxSubCategory should be populated with the corresponding enum.
public enum MainCategory { // extra enum class
EUROPE("Europe"),
USA("USA");
}
public enum SubCategoryEurope { // extra enum class
GERMANY("Germany"),
FRANCE("France");
}
public enum SubCategoryUSA {
COLORADO("Colorado"),
CALIFORNIA("California");
}
If "Europe" is chosen for comboBoxMainCategory, comboBoxSubCategory should be populated with SubCategoryEurope. If "USA", with SubCategoryUSA.
How do you achieve this?
Here's my code:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
comboBoxMainCategory.valueProperty().addListener((obs, oldValue,
newValue) ->
{
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else if (newValue.equals(MainCategory.EUROPE)) {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryEurope.values()));
comboBoxSubCategory.setDisable(false);
} else {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryUSA.values()));
comboBoxSubCategory.setDisable(false);}
});
Problem is, because comboBoxSubCategory is "SubCategory", there is a type error if it is populated with 'SubCategoryEurope' or 'SubCategoryUSA'.
What is the best way to solve this? Sorry if it's a silly question, I'm new to JavaFx.
Thanks a lot!
I wouldn't use enums at all, since this doesn't allow for data manipulation without recompiling. If you insist on using enums though, you need to use Object or a interface implemented with both subcategory enum types as parameter type for comboBoxSubCategory:
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else {
comboBoxSubCategory.setDisable(false);
List<? extends Object> list;
switch (newValue) {
case EUROPE:
list = Arrays.asList(SubCategoryEurope.values());
break;
default:
list = Arrays.asList(SubCategoryUSA.values());
break;
}
comboBoxSubCategory.getItems().setAll(list);
}
});
The better approach would be using a Map<String, List<String>> to store the data:
Map<String, List<String>> data = new HashMap<>();
data.put("EUROPE", Arrays.asList("GERMANY", "FRANCE"));
data.put("USA", Arrays.asList("COLORADO", "CALIFORNIA"));
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
List<String> list = data.get(newValue);
if (list != null) {
comboBoxSubCategory.setDisable(false);
comboBoxSubCategory.getItems().setAll(list);
} else {
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
}
});
Just for fun (and to flesh out my comments): a more versatile approach than those in the other answers is to move away the focus of interest from the concrete nature of the backing data to a more general solution of the use-case at hand. The drawback of letting the UI implement special cases is always the same - you have to do it over and over again for each special UI and each special data type. The way out is always the same, too: implement a Model that takes over the general aspect and re-use that in concrete UI/data contexts.
The general aspects here are:
there's list of items with each having a list of dependent objects (same or different type)
this (let's call it root) list of items is shown in a control
from root list, a single item can be chosen (aka: selected)
another control should show the dependents of the root
The general approach is to have a Model that
manages list of items
has the notion of one of those items as selected (or current or active or ..)
manages a list of dependent items that always is the dependent list of the selected item
its state (root items, current item, dependent items) is exposed as properties
The advantages of such a Model are
can be formally and rigorouly tested, so using code can rely on its proper functioning
it's re-usable for any data context
it's re-usable for many controls
usage is pretty simple by binding
In the example below, the Model is named RelationModel which expects root items of type RelationProvider (which allows access to a list of dependents, it's one option, could just as well use f.i. a Function to build the dependents). It is used once with a plain Map of String/list and once with enums of Continents/Countries, each very simple to implement. Note that the resulting UI is blissfully unaware of the nature of the data, implemented solely against the model.
Naturally, not production grade, in particular, not formally tested and the model with just the barest functionality :)
public class CombosWithCategories extends Application {
public interface RelationProvider<T> {
default ObservableList<T> getRelations() {
return emptyObservableList();
};
}
/**
* A model that manages a list of RelationProviders and has the notion
* of a current relationProvider with relations (it's a kind-of selectionModel).
*
* <T> the type of elements in the list of relations
*/
public static class RelationModel<T> {
/**
* all relationProviders managed by this model
*/
private ListProperty<RelationProvider<T>> relationProviders;
/**
* The owner of the relations. Must be contained in the providers managed
* by this model.
*/
private ObjectProperty<RelationProvider<T>> relationProvider;
private ListProperty<T> relations;
public RelationModel() {
initProperties();
}
/**
* The RelationProviders managed by the model.
*/
public ListProperty<RelationProvider<T>> relationProvidersProperty() {
return relationProviders;
}
/**
* The RelationProvider that manages the current relations.
*/
public ObjectProperty<RelationProvider<T>> relationProviderProperty() {
return relationProvider;
}
public RelationProvider<T> getRelationProvider() {
return relationProviderProperty().get();
}
public ListProperty<T> relations() {
return relations;
}
/**
* Callback from invalidation of current relationProvider.
* Implemented to update relations.
*/
protected void relationProviderInvalidated() {
RelationProvider<T> value = getRelationProvider();
relations().set(value != null ? value.getRelations() : emptyObservableList());
}
/**
* Creates and wires all properties.
*/
private void initProperties() {
relationProviders = new SimpleListProperty<>(this, "relationProviders", observableArrayList());
relationProvider = new SimpleObjectProperty<>(this, "relationProvider") {
#Override
protected void invalidated() {
// todo: don't accept providers that are not in the list
relationProviderInvalidated();
}
};
relations = new SimpleListProperty<>(this, "relations");
relationProviderInvalidated();
}
}
/**
* Implement the ui against a RelationModel. Here we create
* the same UI with a model backed by enums or a Map, respectively
*/
private Parent createContent() {
TabPane tabPane = new TabPane(
new Tab("Enums", createRelationUI(createEnumRelationModel())),
new Tab("Manual map", createRelationUI(createMapRelationModel()))
);
return new BorderPane(tabPane);
}
/**
* Common factory for UI: creates and returns a Parent that
* contains two combo's configured to use the model.
*/
protected <T> Parent createRelationUI(RelationModel<T> model) {
ComboBox<RelationProvider<T>> providers = new ComboBox<>();
providers.itemsProperty().bind(model.relationProvidersProperty());
providers.valueProperty().bindBidirectional(model.relationProviderProperty());
ComboBox<T> relations = new ComboBox<>();
relations.itemsProperty().bind(model.relations());
relations.valueProperty().addListener((src, ov, nv) -> {
LOG.info("relation changed: " + nv);
});
return new VBox(10, providers, relations);
}
// ------------- manual with maps
/**
* On-the-fly creation of a RelationModel using a backing map.
*/
protected RelationModel<String> createMapRelationModel() {
RelationModel<String> model = new RelationModel<>();
Map<String, ObservableList<String>> data = new HashMap<>();
data.put("EUROPE", observableArrayList("GERMANY", "FRANCE"));
data.put("AMERICA", observableArrayList("MEXICO", "USA"));
for (String key: data.keySet()) {
model.relationProvidersProperty().add(new RelationProvider<String>() {
#Override
public ObservableList<String> getRelations() {
return data.get(key);
}
#Override
public String toString() {
return key;
}
});
}
return model;
}
//-------------------- enum
/**
* RelationModel using Enums.
*/
protected RelationModel<Object> createEnumRelationModel() {
RelationModel<Object> model = new RelationModel<Object>();
model.relationProvidersProperty().setAll(Continent.values());
return model;
}
public enum EuropeanCountry {
FRANCE, GERMANY;
}
public enum AmericanCountry {
MEXICO, CANADA, USA;
}
public enum Continent implements RelationProvider<Object> {
AMERICA(AmericanCountry.values()),
EUROPE(EuropeanCountry.values())
;
ObservableList<Object> subs;
private Continent(Object[] subs) {
this.subs = FXCollections.observableArrayList(subs);
}
#Override
public ObservableList<Object> getRelations() {
return FXCollections.unmodifiableObservableList(subs);
}
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(CombosWithCategories.class.getName());
}
Create a generic placeholder interface
public interface EnumPlaceHolder<E extends Enum<?>> {
public abstract String getDisplayValue();
public abstract E getEnum();
}
Create an implementation for all your enums. For example
public class EuropePlaceholder implements EnumPlaceHolder<Europe> {
private final Europe value;
public EuropePlaceholder(Europe pValue){
value = pValue;
}
#Override
public String getDisplayValue() {
// here you create a user-friendly version of your enum for display
return value.toString();
}
#Override
public Europe getEnum() {
return value;
}
}
Then change the type of your ComboBox<Enum> to ComboBox<EnumPlaceholder<?>> and you can add any of your implemented EnumPlaceholders to it. When retrieving the selected item you can check which one is contained via instance check
EnumPlaceholder<?> selectedItem = ...;
if(selectedItem instanceof EuropePlaceholder){
Europe selectedEuropeEnum = (Europe) selectedItem.getEnum();
} else if(....){
// check with else if for your other enums
}
And to display your enum in your combobox you call the getDisplayValue() of the EnumPlaceholder and show the returned String in your cell :)
EDIT
Tho in general i have to agree with fabians answer. You shouldn't use enums for a construct like this. Rather use a Map<> or a List<> with appropriate content and structure.

Comparing a String to an Object's toString() method dynamically

I'm reading from a text file, like so:
while ((line = br.readLine()) != null) {
String[] a = line.split("\\: ");
key = a[0];
action = a[1];
gameKeys.add(key, action);
}
where the file would be something like
SPACE: FIRE_ACTION
E: USE_ACTION
This part works, key and action are both what I want.
gameKeys is a Map declared like so:
private static Map<Keyboard.Key, Action> gameKeys = new HashMap<>();
Keyboard.Key has fields such as SPACE, A, RETURN, etc.
Action is an interface, that holds other actions; those actions have a toString() method that returns the action, e.g. new FireAction.toString() returns FIRE_ACTION.
Example of an Action:
public class FireAction implements Action {
#Override
public void execute() {
System.out.println("Fire key pressed!");
}
#Override
public String toString() {
return "FIRE_ACTION";
}
}
So, I'm trying to turn the file's components into objects, like if key was "SPACE" and action was "FIRE_ACTION", then, after the add method is performed, gameKeys would have <Keyboard.Key.SPACE, new FireAction()>
Is there anyway I can do this?
You could try this:
Save your Action classes in a Map<String, Class<? extends Action>>
Read the Key -> Action bindings from the file
Resolve the string action to an actual Action object via the map
Example:
public class Main {
private static final Map<Keyboard.Key, Action> gameKeys = new HashMap<>();
private static final Map<String, Class<? extends Action>> actions = new HashMap<>();
static {
actions.put(FireAction.NAME, FireAction.class);
actions.put(WalkAction.NAME, WalkAction.class);
}
public static void main(String[] args) {
// read from file etc.
try {
// e.g. found SPACE : FIRE_ACTION
gameKeys.put(Keyboard.Key.SPACE, actions.get("FIRE_ACTION").newInstance());
// e.g. found A : WALK_ACTION
gameKeys.put(Keyboard.Key.A, actions.get("WALK_ACTION").newInstance());
} catch (IllegalAccessException | InstantiationException ex) {
ex.printStackTrace();
}
}
}
public class FireAction implements Action {
public static final String NAME = "FIRE_ACTION";
#Override
public void execute() {
System.out.println("Fire key pressed!");
}
#Override
public String toString() {
return NAME;
}
}
Sure
Object keyObj = key, actionObj;
if (key.equals("SPACE")) keyObj = Keyboard.Key.SPACE;
if (action.equals("FIRE_ACTION")) actionObj = new FireAction());
You can use a Map<String, ...> as an alternative to using 'if's if you have a lot of cases
You can't achieve what you have asked directly - because then Java would have to create ALL the classes it can create(some have non-default constructors or even private), and call their toString() method (which may have side-effects in general case).
So anyway you'll have to create registry with all actions(preferrable way), or you can try to use reflection to create Actions in runtime.

GWT Editor Framework - Show ENUM using ValueListBox in own editor

I have an Enum SupplierCode:
public enum SupplierCode
{
BG("British Gas"), CNG("Contract Natural Gas"), COR("Corona Energy");
private String value;
SupplierCode(String value)
{
if(value != "")
{
this.value = value;
}
}
// ... toString() and fromString() omitted for brevity
// for editor framework (?)
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
I display it in my editors using a ValueListBox:
#UiField(provided = true)
ValueListBox<SupplierCode> supplierCode = new ValueListBox<SupplierCode>(new AbstractRenderer<SupplierCode>()
{
#Override
public String render(SupplierCode object)
{
return object == null ? "" : object.toString();
}
});
// in the constructor
public ContractEditor()
{
initWidget(uiBinder.createAndBindUi(this));
supplierCode.setAcceptableValues(Arrays.asList(SupplierCode.values()));
}
I have to edit this type a few times in my app so I wanted to make an editor for just this dropdown, called SupplierCodeEditor:
public class SupplierCodeEditor extends Composite implements Editor<SupplierCode>
{
private static SupplierCodeEditorUiBinder uiBinder = GWT.create(SupplierCodeEditorUiBinder.class);
interface SupplierCodeEditorUiBinder extends UiBinder<Widget, SupplierCodeEditor>
{
}
#UiField(provided = true)
ValueListBox<SupplierCode> value = new ValueListBox<SupplierCode>(new AbstractRenderer<SupplierCode>()
{
#Override
public String render(SupplierCode object)
{
return object == null ? "" : object.toString();
}
});
public SupplierCodeEditor()
{
initWidget(uiBinder.createAndBindUi(this));
value.setAcceptableValues(Arrays.asList(SupplierCode.values()));
}
}
However, when I use it, although it renders the list ok with the options, it doesn't select the actual value from the list. I thought having the getValue() and setValue() methods would work but seemingly not.
Does anyone know of a way to put this in one editor file? Then I won't have to repeat the code for the renderer and call setAcceptableValues() every place I want to use it.
Use LeafValueEditor<SupplierCode>:
public class SupplierEditor extends Composite implements LeafValueEditor<SupplierCode> {
interface SupplierEditorUiBinder extends UiBinder<Widget, SupplierEditor> {
}
private static SupplierEditorUiBinder uiBinder = GWT.create(SupplierEditorUiBinder.class);
#UiField(provided = true)
ValueListBox<SupplierCode> codes;
public SupplierEditor() {
codes = new ValueListBox<>(new AbstractRenderer<SupplierCode>() {
#Override
public String render(SupplierCode object) {
return object == null ? "" : object.toString();
}
});
initWidget(uiBinder.createAndBindUi(this));
codes.setAcceptableValues(Arrays.asList(SupplierCode.values()));
}
#Override
public SupplierCode getValue() {
return codes.getValue();
}
#Override
public void setValue(SupplierCode value) {
codes.setValue(value);
}
}
This way, your widget will be easily pluggable in a Editor hierarchy.
And you don't need the get/set methods in your SupplierCode enum.
You have to either:
use #Editor.Path("") on your child ValueListBox
make your SupplierCodeEditor implement LeafValueEditor<SupplierCode>, with delegating getValue and setValue to the ValueListBox
make your SupplierCodeEditor implement IsEditor<LeafValueEditor<SupplierCode>, returning the ValueListBox's asEditor() from your own asEditor().
BTW, you absolutely don't need the getValue and setValue on your enum values.

Cache in GWT app/widget with HTML5 localStorage

I am trying to incorporate a data cache for one of my GWT widgets.
I have a datasource interface/class which retrieves some data from my backend via RequestBuilder and JSON. Because I display the widget multiple times I only want to retrieve the data once.
So I tried to come with an app cache. The naive approach is to use a HashMap in a singleton object to store the data. However I also want to make use of HTML5's localStorage/sessionStorage if supported.
HTML5 localStorage only supports String values. So I have to convert my object into JSON and store as a string. However somehow I can't come up with a nice clean way of doing this. here is what I have so far.
I define a interface with two functions: fetchStatsList() fetches the list of stats that can be displayed in the widget and fetchStatsData() fetches the actual data.
public interface DataSource {
public void fetchStatsData(Stat stat,FetchStatsDataCallback callback);
public void fetchStatsList(FetchStatsListCallback callback);
}
The Stat class is a simple Javascript Overlay class (JavaScriptObject) with some getters (getName(), etc)
I have a normal non-cachable implementation RequestBuilderDataSource of my DataSource which looks like the following:
public class RequestBuilderDataSource implements DataSource {
#Override
public void fetchStatsList(final FetchStatsListCallback callback) {
// create RequestBuilderRequest, retrieve response and parse JSON
callback.onFetchStatsList(stats);
}
#Override
public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) {
String url = getStatUrl(stats);
//create RequestBuilderRquest, retrieve response and parse JSON
callback.onFetchStats(dataTable); //dataTable is of type DataTable
}
}
I left out most of the code for the RequestBuilder as it is quite straightforward.
This works out of the box however the list of stats and also the data is retrieved everytime even tough the data is shared among each widget instance.
For supporting caching I add a Cache interface and two Cache implementations (one for HTML5 localStorage and one for HashMap):
public interface Cache {
void put(Object key, Object value);
Object get(Object key);
void remove(Object key);
void clear();
}
I add a new class RequestBuilderCacheDataSource which extends the RequestBuilderDataSource and takes a Cache instance in its constructor.
public class RequestBuilderCacheDataSource extends RequestBuilderDataSource {
private final Cache cache;
publlic RequestBuilderCacheDataSource(final Cache cache) {
this.cache = cache;
}
#Override
public void fetchStatsList(final FetchStatsListCallback callback) {
Object value = cache.get("list");
if (value != null) {
callback.fetchStatsList((List<Stat>)value);
}
else {
super.fetchStatsList(stats,new FetchStatsListCallback() {
#Override
public void onFetchStatsList(List<Stat>stats) {
cache.put("list",stats);
callback.onFetchStatsList(stats);
}
});
super.fetchStatsList(callback);
}
}
#Override
public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) {
String url = getStatUrl(stats);
Object value = cache.get(url);
if (value != null) {
callback.onFetchStatsData((DataTable)value);
}
else {
super.fetchStatsData(stats,new FetchStatsDataCallback() {
#Override
public void onFetchStatsData(DataTable dataTable) {
cache.put(url,dataTable);
callback.onFetchStatsData(dataTable);
}
});
}
}
}
Basically the new class will lookup the value in the Cache and if it is not found it will call the fetch function in the parent class and intercept the callback to put it into the cache and then call the actual callback.
So in order to support both HTML5 localstorage and normal JS HashMap storage I created two implementations of my Cache interface:
JS HashMap storage:
public class DefaultcacheImpl implements Cache {
private HashMap<Object, Object> map;
public DefaultCacheImpl() {
this.map = new HashMap<Object, Object>();
}
#Override
public void put(Object key, Object value) {
if (key == null) {
throw new NullPointerException("key is null");
}
if (value == null) {
throw new NullPointerException("value is null");
}
map.put(key, value);
}
#Override
public Object get(Object key) {
// Check for null as Cache should not store null values / keys
if (key == null) {
throw new NullPointerException("key is null");
}
return map.get(key);
}
#Override
public void remove(Object key) {
map.remove(key);
}
#Override
public void clear() {
map.clear();
}
}
HTML5 localStorage:
public class LocalStorageImpl implements Cache{
public static enum TYPE {LOCAL,SESSION}
private TYPE type;
private Storage cacheStorage = null;
public LocalStorageImpl(TYPE type) throws Exception {
this.type = type;
if (type == TYPE.LOCAL) {
cacheStorage = Storage.getLocalStorageIfSupported();
}
else {
cacheStorage = Storage.getSessionStorageIfSupported();
}
if (cacheStorage == null) {
throw new Exception("LocalStorage not supported");
}
}
#Override
public void put(Object key, Object value) {
//Convert Object (could be any arbitrary object) into JSON
String jsonData = null;
if (value instanceof List) { // in case it is a list of Stat objects
JSONArray array = new JSONArray();
int index = 0;
for (Object val:(List)value) {
array.set(index,new JSONObject((JavaScriptObject)val));
index = index +1;
}
jsonData = array.toString();
}
else // in case it is a DataTable
{
jsonData = new JSONObject((JavaScriptObject) value).toString();
}
cacheStorage.setItem(key.toString(), jsonData);
}
#Override
public Object get(Object key) {
if (key == null) {
throw new NullPointerException("key is null");
}
String jsonDataString = cacheStorage.getItem(key.toString());
if (jsonDataString == null) {
return null;
}
Object data = null;
Object jsonData = JsonUtils.safeEval(jsonDataString);
if (!key.equals("list"))
data = DataTable.create((JavaScriptObject)data);
else if (jsonData instanceof JsArray){
JsArray<GenomeStat> jsonStats = (JsArray<GenomeStat>)jsonData;
List<GenomeStat> stats = new ArrayList<GenomeStat>();
for (int i = 0;i<jsonStats.length();i++) {
stats.add(jsonStats.get(i));
}
data = (Object)stats;
}
return data;
}
#Override
public void remove(Object key) {
cacheStorage.removeItem(key.toString());
}
#Override
public void clear() {
cacheStorage.clear();
}
public TYPE getType() {
return type;
}
}
The post got a little bit long but hopefully clarifies what I try to reach. It boils down to two questions:
Feedback on the design/architecture of this approach (for example subclassing RequestBilderDataSource for cache function, etc). Can this be improved (this is probably more related to general design than specifically GWT).
With the DefaultCacheImpl it is really easy to store and retrieve any arbitrary objects. How can I achieve the same thing with localStorage where I have to convert and parse JSON? I am using a DataTable which requires to call the DataTable.create(JavaScriptObject jso) function to work. How can I solve this without to many if/else and instance of checks?
My first thoughts: make it two layers of cache, not two different caches. Start with the in-memory map, so no serialization/deserialization is needed for reading a given object out, and so that changing an object in one place changes it in all. Then rely on the local storage to keep data around for the next page load, avoiding the need for pulling data down from the server.
I'd tend to say skip session storage, since that doesn't last long, but it does have its benefits.
For storing/reading data, I'd encourage checking out AutoBeans instead of using JSOs. This way you could support any type of data (that can be stored as an autobean) and could pass in a Class param into the fetcher to specify what kind of data you will read from the server/cache, and decode the json to a bean in the same way. As an added bonus, autobeans are easier to define - no JSNI required. A method could look something like this (note that In DataSource and its impl, the signature is different).
public <T> void fetch(Class<T> type, List<Stat> stats, Callback<T, Throwable> callback);
That said, what is DataTable.create? If it is already a JSO, you can just cast to DataTable as you (probably) normally do when reading from the RequestBuilder data.
I would also encourage not returning a JSON array directly from the server, but wrapping it in an object, as a best practice to protect your users' data from being read by other sites. (Okay, on re-reading the issues, objects aren't great either). Rather than discussing it here, check out JSON security best practices?
So, all of that said, first define the data (not really sure how this data is intended to work, so just making up as I go)
public interface DataTable {
String getTableName();
void setTableName(String tableName);
}
public interface Stat {// not really clear on what this is supposed to offer
String getKey();
void setKey(String key);
String getValue();
String setValue(String value);
}
public interface TableCollection {
List<DataTable> getTables();
void setTables(List<DataTable> tables);
int getRemaining();//useful for not sending all if you have too much?
}
For autobeans, we define a factory that can create any of our data when given a Class instance and some data. Each of these methods can be used as a sort of constructor to create a new instance on the client, and the factory can be passed to AutoBeanCodex to decode data.
interface DataABF extends AutoBeanFactory {
AutoBean<DataTable> dataTable();
AutoBean<Stat> stat();
AutoBean<TableCollection> tableCollection();
}
Delegate all work of String<=>Object to AutoBeanCodex, but you probably want some simple wrapper around it to make it easy to call from both the html5 cache and from the RequestBuilder results. Quick example here:
public class AutoBeanSerializer {
private final AutoBeanFactory factory;
public AutoBeanSerializer(AutoBeanFactory factory) {
this.factory = factory;
}
public String <T> encodeData(T data) {
//first, get the autobean mapped to the data
//probably throw something if we can't find it
AutoBean<T> autoBean = AutoBeanUtils.getAutoBean(data);
//then, encode it
//no factory or type needed here since the AutoBean has those details
return AutoBeanCodex.encode(autoBean);
}
public <T> T decodeData(Class<T> dataType, String json) {
AutoBean<T> bean = AutoBeanCodex.decode(factory, dataType, json);
//unwrap the bean, and return the actual data
return bean.as();
}
}

Categories

Resources