// db is an existing ItemSpace, such as the InfinityDB itself
cu.clear().append("any prefix that identifies the serialized object");
BinaryLongObjectOutputStream bos = new BinaryLongObjectOutputStream(db, cu);
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(myObject);
oos.close();
This will handle recursive structure enumerations and deal with looping
structures and so on.
keepResourcesOnClose()
on the BinaryLongObjectOutputStream, and then change its ItemSpace and prefix:
bos.setKeepResourcesOnClose(true); ... oos.close(); .. cu.clear().append(objectPrefix2); bos.setSpaceAndPrefix(db, cu); ObjectOutputStream oos = new ObjectOutputStream(bos); // can we reuse this too? oos.writeObject(myObject2); oos.close();The BLOB method is efficient because each serialized Object takes only the space it really needs (see BLOB efficiency). Individual Objects can be stored, retrieved, and deleted easily in any order, with relatively good performance, basically limited by the space and time efficiency of Object serialization.
ItemHolder.
Only small Objects can be persisted this way, because
the 'serialized' form must fit in a single Item, so it
cannot be longer than 1.6KChars long (note that the
representations of the primitives in an Item is
variable-length). The appendTo(Cu)
method and getItem(Cu) methods are for
getting the serialized form out of an ItemHolder, and
setItem(Cu) and setItem(Object)
are for putting a serialized Item into an ItemHolder.
An Example 'ultralight' class:
class Person implements ItemHolder {
long id;
String name;
// get Object state into a Cu: implements CuAppendable
public void appendTo(Cu cu) {
cu.append(id).append(name);
}
// get Object state into a Cu: implements ItemHolder
public void getItem(Cu cu) {
// this simple default always works
cu.clear().append(this);
}
// put Cu contents into an Object: implements ItemHolder
public void setItem(Cu) {
id = cu.longAt(0);
name = cu.stringAt(cu.skipLong(0));
}
// OPTIONAL:
// Here is the worst-case code for speed plus
// generality. This is optional. Iimplements ItemHolder
public void setItem(Object o) {
if (o instanceof Person) {
// This special case is optional, for speed. Probably not needed.
Person person2 = (Person)o;
id = person2.id;
name = person2.name;
} else if (o instanceof Cu) {
// This special case is also optional, for speed. Probably not needed.
// We just save alloc/dispose time for the Cu.
setItem((Cu)o);
} else {
// This case is as general as possible.
// We can append anything that is stored as a
// long followed by a string, whether it
// is a Person or not
Cu cu = Cu.alloc().append(o);
try {
setItem(cu);
} finally {
cu.dispose();
}
}
}
}
Now we can append a Person to a Cu anywhere, as if it were
a java primitive, such as in this code that persists it and
inverts it:
Person person = ... // get some Person who holds a card // Append the person to any prefix, like this EAV prefix cu.append(CREDIT_CARD).append(cardId).append(CARD_HOLDER).append(person); db.insert(cu); // insert into db in one operation // and we can use a Person as an Entity too in an inverse Item: cu.append(PERSON).append(person).append(HOLDS_CARD).append(cardId); db.insert(cu);And to get it back we use the typical cut-and-paste code:
// cuCard contains an Item that identifies a card
// cuPerson is empty
Person person = new Person();
if (CREDIT_CARD.next(db, cuCard, CARD_HOLDER, cuPerson)) {
person.setItem(cuPerson);
// do something with person
}
It is difficult to use this approach to persist networks of Objects that refer to each other with Java references. The standard Object serialization is probably best for this, and many problems have been solved in that system, like handling looping structures and general graphs. There are also other Object persistence systems like JDO to be looked at. Some systems provide "swizzling", which is a means for making Objects come into memory only as needed as references to them are followed - a kind of virtual memory for Objects. These systems have solved many kinds of problems, and we will not try to solve them again.
class Person {
static final EntityClass PERSON = new EntityClass(3);
static final Attribute NAME = new Attribute(32);
static final Attribute CHILD = new Attribute(33);
static final long NULL_CHILD = -1;
ItemSpace db;
long id; // the Entity
String name; // the NAME Attribute
long child = NULL_CHILD; // the CHILD Attribute. Note this is not a collection! It acts like a cursor.
..
Person(ItemSpace db) { this.db = db; }
..
// the usual accessors like long getChild()
..
// for each field, a loadX() and a storeX(),
// and maybe a store() that invokes all of them
// This can all use simplifing helper methods
void loadChild() {
.. allocate, dispose cu etc
cu.append(PERSON).append(id).append(CHILD);
int pl = cu.length();
child = db.next(cu, pl) ? cu.longAt(pl) : NULL_CHILD;
}
void storeChild() {
.. allocate, dispose cu etc
.. ignored here: for a single-valued Attribute, must delete old value
cu.append(PERSON).append(id).append(CHILD).append(child);
db.insert(cu);
}
// Move the current value of child forwards one place in
// the set of children. Start at NULL_CHILD. Helper code may simplify this.
boolean nextChild() {
.. allocate cu etc
cu.append(PERSON).append(id).append(CHILD);
int pl = cu.length();
if (child != NULL_CHILD) cu.append(child);
child = db.next(cu, pl) ? cu.longAt(pl) : NULL_CHILD;
return child != NULL_CHILD;
}
}
The above code is very simple, but may be possible to simplify further,
and might eventually be possible to generate automatically.
First the obvious part: to use it to get a person's name:
// we could re-use a Person instance easily
Person person = new Person(db);
person.setId(personId);
person.loadName();
System.out.println("person " + personId + " name: " + person.getName());
Now to scan the children:
person.setId(personId);
person.setChild(Person.NULL_CHILD); // in case child wasn't already null
while (person.nextChild()) {
System.out.println("personId: " + personId + " childId: " + person.getChild());
}
This code is obviously simple and clear. The Person Object does not represent
an Entity by being a different instance, but by having a different
current id. Each field can potentially become multi-valued merely by
acquiring a new nextX() method.
The above technique is only a concept and example, and various encapsulations have been used. This code uses each multi-valued field as a cursor over a virtual collection associated with it that is actually an EAV multi-value Attribute. There are no Collections, Enumerators, Iterators, EntrySets, Entry's, KeySets, instanceof, casting, etc. etc. There is not even any construction, except when a String Value is returned (although even in that case, it would be possible to avoid construction by keeping the string component in a Cu.)
We have not shown the code to maintain
inversions - this code would simply require a second insert()/delete() on each
storeX() method that would use the Inverse Item of the Item being
stored. See Inversion. We also need code
to delete an entire Entity, and possibly code to scan over
Entities, like a boolean nextPerson(). All of
these can be easily handled by helper methods in the
EntityClass and Inverse classes.