/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2015 Oracle and/or its affiliates. All rights reserved.
*
*/
package com.sleepycat.persist.impl;
import java.util.IdentityHashMap;
import java.util.Map;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.persist.raw.RawObject;
/**
* Implements EntityOutput to write record key-data pairs. Extends TupleOutput
* to implement the subset of TupleOutput methods that are defined in the
* EntityOutput interface.
*
* @author Mark Hayes
*/
class RecordOutput extends TupleOutput implements EntityOutput {
private Catalog catalog;
private boolean rawAccess;
private Map<Object, Integer> visited;
/**
* Creates a new output with an empty/null visited map.
*/
RecordOutput(Catalog catalog, boolean rawAccess) {
super();
this.catalog = catalog;
this.rawAccess = rawAccess;
this.visited = new IdentityHashMap<Object, Integer>();
}
/**
* @see EntityOutput#writeObject
*/
public void writeObject(Object o, Format fieldFormat)
throws RefreshException {
/* For a null instance, write a zero format ID. */
if (o == null) {
writePackedInt(Format.ID_NULL);
return;
}
/*
* For an already visited instance, output a reference to it. The
* reference is the negation of the visited offset minus one.
*/
Integer offset = visited.get(o);
if (offset != null) {
if (offset == RecordInput.PROHIBIT_REF_OFFSET) {
throw new IllegalArgumentException
(RecordInput.PROHIBIT_NESTED_REF_MSG);
} else {
writePackedInt(-(offset + 1));
return;
}
}
/*
* Get and validate the format. Catalog.getFormat(Class) throws
* IllegalArgumentException if the class is not persistent. We don't
* need to check the fieldFormat (and it will be null) for non-raw
* access because field type checking is enforced by Java.
*/
Format format;
if (rawAccess) {
format = RawAbstractInput.checkRawType(catalog, o, fieldFormat);
} else {
/*
* Do not attempt to open subclass indexes in case this is an
* embedded entity. We will detect that error below, but we must
* not fail first when attempting to open the secondaries.
*/
format = catalog.getFormat
(o.getClass(), false /*checkEntitySubclassIndexes*/);
}
if (format.getProxiedFormat() != null) {
throw new IllegalArgumentException
("May not store proxy classes directly: " +
format.getClassName());
}
/* Check for embedded entity classes and subclasses. */
if (format.getEntityFormat() != null) {
throw new IllegalArgumentException
("References to entities are not allowed: " +
o.getClass().getName());
}
/*
* Remember that we visited this instance. Certain formats
* (ProxiedFormat for example) prohibit nested fields that reference
* the parent object. [#15815]
*/
boolean prohibitNestedRefs = format.areNestedRefsProhibited();
Integer visitedOffset = size();
visited.put(o, prohibitNestedRefs ? RecordInput.PROHIBIT_REF_OFFSET :
visitedOffset);
/* Finally, write the formatId and object value. */
writePackedInt(format.getId());
format.writeObject(o, this, rawAccess);
/* Always allow references from siblings that follow. */
if (prohibitNestedRefs) {
visited.put(o, visitedOffset);
}
}
/**
* @see EntityOutput#writeKeyObject
*/
public void writeKeyObject(Object o, Format fieldFormat)
throws RefreshException {
/* Key objects must not be null and must be of the declared class. */
if (o == null) {
throw new IllegalArgumentException
("Key field object may not be null");
}
Format format;
if (rawAccess) {
if (o instanceof RawObject) {
format = (Format) ((RawObject) o).getType();
} else {
format = catalog.getFormat
(o.getClass(), false /*checkEntitySubclassIndexes*/);
/* Expect primitive wrapper class in raw mode. */
if (fieldFormat.isPrimitive()) {
fieldFormat = fieldFormat.getWrapperFormat();
}
}
} else {
format = catalog.getFormat(o.getClass(),
false /*checkEntitySubclassIndexes*/);
}
if (fieldFormat != format) {
throw new IllegalArgumentException
("The key field object class (" + o.getClass().getName() +
") must be the field's declared class: " +
fieldFormat.getClassName());
}
/* Write the object value (no formatId is written for keys). */
fieldFormat.writeObject(o, this, rawAccess);
}
/**
* @see EntityOutput#registerPriKeyObject
*/
public void registerPriKeyObject(Object o) {
/*
* PRI_KEY_VISITED_OFFSET is used as the visited offset to indicate
* that the visited object is stored in the primary key byte array.
*/
visited.put(o, RecordInput.PRI_KEY_VISITED_OFFSET);
}
/**
* Registers the top level entity before writing it, to allow nested fields
* to reference their parent entity. [#17525]
*/
public void registerEntity(Object entity) {
assert size() == 0;
visited.put(entity, size());
}
/**
* @see EntityOutput#writeArrayLength
*/
public void writeArrayLength(int length) {
writePackedInt(length);
}
/**
* @see EntityOutput#writeEnumConstant
*/
public void writeEnumConstant(String[] names, int index) {
writePackedInt(index);
}
}