1

I'm searching for a fast and convenient way to add save() and rollback() to a standard Map. Let's say i have an object 'table' of class Table which in turn has a private Map called 'rows'. What I'm trying to achieve is a fast and without memory waste method for Row to do something like:

row = new Row();
table.addRow(row).setValue("col1", "foo").setValue("col2", "bar").save();
row.setValue("col2", "beer");
System.out.println(table.getRows()); // 1. col1=foo, col2=bar

row.save();
System.out.println(table.getRows()); // 1. col1=foo, col2=beer

Actually, my design is quite trivial: when addRow() is called, i put() the row inside the map; no buffer, no temp elements; i simply pass the entire Row instance to rows collection. But I need a fast method and (if possible) avoiding the duplication of rows.

any idea?

Gabriele B
  • 2,665
  • 1
  • 25
  • 40

3 Answers3

3

This sounds too much like "I want to have the new and old values in memory, but I do not want to have the new and old values in memory".

Options:

a) A map of all the added elements, when save do putAll.

b) Your map, instead of <ClassKey, ClassValue>, holds <ClassKey, ClassValue2>. Value2 holds two items of ClassValue, the new and old instance. At save, you pass the new one (if any) to the old one. It will be useful only if you are changing most of the entries in each "transaction".

Not mentioned is the issue of deleting elements, which will bring you yet more joy. With option 2 you can set a boolean at Value2, with option a you will need more workarounds.

SJuan76
  • 24,532
  • 6
  • 47
  • 87
  • _This sounds too much like "I want to have the new and old values in memory, but I do not want to have the new and old values in memory"_ ok I got the point ;) – Gabriele B Dec 17 '12 at 22:03
  • Oh, and the best option: use some framework that already implements transactions. DBs are cheap these day, and probably you can find some FOSS library that implements this. – SJuan76 Dec 17 '12 at 22:03
2

What I'm trying to achieve is a fast and without memory waste method for Row to do something like:

You will waste memory, as you need to keep a "rollback" around somewhere in case it fails along the way. This is how databases handle these types of things.

Now to get the feature you want, you will need to implement your own custom transaction logic, this will allow you to correctly rollback / persist changes to your map. Now within this transaction you will need to keep track of everything that occurred during the transaction. This is because you will be doing temporary writes to your original map, while the transaction processes, subsequently you will need to have the ability to recover from a failed persist/update.

To avoid duplication of rows, the HashMap will already save you from that problem. Assuming you correctly implement a hash function that reports correctly when two objects generate the same code and are therefore "potentially", not definitely, equal in terms of hashing.

Woot4Moo
  • 23,987
  • 16
  • 94
  • 151
0
  • Keep two map fields inside custom Map class, originalMap and temporaryMap.
  • Read operations are delegated to originalMap, and write operations to temporaryMap.
  • rollback() shallow-copies originalMap into temporaryMap and commit() vice versa. Since keys and values are never cloned, only the references are kept and memory is not "wasted"

package com.example;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import lombok.NonNull;
import lombok.ToString;

@ToString
public class TransactionalMap<K, V> implements Map<K, V> {
  
  private Map<K, V> originalMap;
  private Map<K, V> temporaryMap;
  
  public TransactionalMap() {
    this(new HashMap<>());
  }
  
  public TransactionalMap(@NonNull Map<K, V> impl) {
    if (impl instanceof TransactionalMap) {
      throw new IllegalArgumentException("Must provide valid implementation instance");
    }
    this.originalMap = new HashMap<>(impl);
    this.temporaryMap = new HashMap<>(originalMap);
  }

  @Override
  public int size() {
    return originalMap.size();
  }

  @Override
  public boolean isEmpty() {
    return originalMap.isEmpty();
  }

  @Override
  public boolean containsKey(Object key) {
    return originalMap.containsKey(key);
  }

  @Override
  public boolean containsValue(Object value) {
    return originalMap.containsValue(value);
  }

  @Override
  public V get(Object key) {
    return originalMap.get(key);
  }

  @Override
  public V put(K key, V value) {
    return temporaryMap.put(key, value);
  }

  @Override
  public V remove(Object key) {
    return temporaryMap.remove(key);
  }

  @Override
  public void putAll(Map<? extends K, ? extends V> m) {
    temporaryMap.putAll(m);
  }

  @Override
  public void clear() {
    temporaryMap.clear();
  }

  @Override
  public Set<K> keySet() {
    return originalMap.keySet();
  }

  @Override
  public Collection<V> values() {
    return originalMap.values();
  }

  @Override
  public Set<Entry<K, V>> entrySet() {
    return originalMap.entrySet();
  }
  
  private void sync(Map<K, V> src, Map<K, V> tgt) {
    tgt.putAll(src);
    tgt.forEach((k, v) -> {
      if (!src.containsKey(k)) tgt.remove(k);
    });
  }
  
  public void commit() {
    sync(temporaryMap, originalMap);
  }
  
  public void rollback() {
    sync(originalMap, temporaryMap);
  }

}

Uses lombok to generate boilerplate code

Monday Fatigue
  • 223
  • 1
  • 3
  • 19