1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: RecordInput.java,v 1.1 2008/02/07 17:12:27 mark Exp $
7 */
8
9package com.sleepycat.persist.impl;
10
11import com.sleepycat.bind.tuple.TupleInput;
12import com.sleepycat.db.DatabaseEntry;
13
14/**
15 * Implements EntityInput to read record key-data pairs.  Extends TupleInput to
16 * implement the subset of TupleInput methods that are defined in the
17 * EntityInput interface.
18 *
19 * @author Mark Hayes
20 */
21class RecordInput extends TupleInput implements EntityInput {
22
23    private Catalog catalog;
24    private boolean rawAccess;
25    private VisitedObjects visited;
26    private DatabaseEntry priKeyEntry;
27    private int priKeyFormatId;
28
29    /**
30     * Creates a new input with a empty/null VisitedObjects set.
31     */
32    RecordInput(Catalog catalog,
33                boolean rawAccess,
34                DatabaseEntry priKeyEntry,
35                int priKeyFormatId,
36                byte[] buffer,
37                int offset,
38                int length) {
39        super(buffer, offset, length);
40        this.catalog = catalog;
41        this.rawAccess = rawAccess;
42        this.priKeyEntry = priKeyEntry;
43        this.priKeyFormatId = priKeyFormatId;
44    }
45
46    /**
47     * Copy contructor where a new offset can be specified.
48     */
49    private RecordInput(RecordInput other, int offset) {
50        this(other.catalog, other.rawAccess, other.priKeyEntry,
51             other.priKeyFormatId, other.buf, offset, other.len);
52        visited = other.visited;
53    }
54
55    /**
56     * Copy contructor where a DatabaseEntry can be specified.
57     */
58    private RecordInput(RecordInput other, DatabaseEntry entry) {
59        this(other.catalog, other.rawAccess, other.priKeyEntry,
60             other.priKeyFormatId, entry.getData(), entry.getOffset(),
61             entry.getSize());
62        visited = other.visited;
63    }
64
65    /**
66     * @see EntityInput#getCatalog
67     */
68    public Catalog getCatalog() {
69        return catalog;
70    }
71
72    /**
73     * @see EntityInput#isRawAccess
74     */
75    public boolean isRawAccess() {
76        return rawAccess;
77    }
78
79    /**
80     * @see EntityInput#setRawAccess
81     */
82    public boolean setRawAccess(boolean rawAccessParam) {
83        boolean original = rawAccess;
84        rawAccess = rawAccessParam;
85        return original;
86    }
87
88    /**
89     * @see EntityInput#readObject
90     */
91    public Object readObject() {
92
93        /* Save the current offset before reading the format ID. */
94        int visitedOffset = off;
95        RecordInput useInput = this;
96        int formatId = readPackedInt();
97        Object o = null;
98
99        /* For a zero format ID, return a null instance. */
100        if (formatId == Format.ID_NULL) {
101            return null;
102        }
103
104        /* For a negative format ID, lookup an already visited instance. */
105        if (formatId < 0) {
106            int offset = (-(formatId + 1));
107            if (visited != null) {
108                o = visited.getObject(offset);
109            }
110            if (o == VisitedObjects.PROHIBIT_REF_OBJECT) {
111                throw new IllegalArgumentException
112                    (VisitedObjects.PROHIBIT_NESTED_REF_MSG);
113            }
114            if (o != null) {
115                /* Return a previously visited object. */
116                return o;
117            } else {
118
119                /*
120                 * When reading starts from a non-zero offset, we may have to
121                 * go back in the stream and read the referenced object.  This
122                 * happens when reading secondary key fields.
123                 */
124                visitedOffset = offset;
125                if (offset == VisitedObjects.PRI_KEY_VISITED_OFFSET) {
126                    assert priKeyEntry != null && priKeyFormatId > 0;
127                    useInput = new RecordInput(this, priKeyEntry);
128                    formatId = priKeyFormatId;
129                } else {
130                    useInput = new RecordInput(this, offset);
131                    formatId = useInput.readPackedInt();
132                }
133            }
134        }
135
136        /*
137         * Add a visted object slot that prohibits nested references to this
138         * object during the call to Reader.newInstance below.  The newInstance
139         * method is allowed to read nested fields (in which case
140         * Reader.readObject further below does nothing) under certain
141         * conditions, but under these conditions we do not support nested
142         * references to the parent object. [#15815]
143         */
144        if (visited == null) {
145            visited = new VisitedObjects();
146        }
147        int visitedIndex =
148            visited.add(VisitedObjects.PROHIBIT_REF_OBJECT, visitedOffset);
149
150        /* Create the object using the format indicated. */
151        Format format = catalog.getFormat(formatId);
152        Reader reader = format.getReader();
153        o = reader.newInstance(useInput, rawAccess);
154
155        /*
156         * Set the newly created object in the set of visited objects.  This
157         * must be done before calling Reader.readObject, which allows the
158         * object to contain a reference to itself.
159         */
160        visited.setObject(visitedIndex, o);
161
162        /*
163         * Finish reading the object.  Then replace it in the visited list in
164         * case a converted object is returned by readObject.
165         */
166        Object o2 = reader.readObject(o, useInput, rawAccess);
167        if (o != o2) {
168            visited.replaceObject(o, o2);
169        }
170        return o2;
171    }
172
173    /**
174     * @see EntityInput#readKeyObject
175     */
176    public Object readKeyObject(Format format) {
177
178        /* Create and read the object using the given key format. */
179        Reader reader = format.getReader();
180        Object o = reader.newInstance(this, rawAccess);
181        return reader.readObject(o, this, rawAccess);
182    }
183
184    /**
185     * Called when copying secondary keys, for an input that is positioned on
186     * the secondary key field.  Handles references to previously occurring
187     * objects, returning a different RecordInput than this one if appropriate.
188     */
189    KeyLocation getKeyLocation(Format fieldFormat) {
190        RecordInput input = this;
191        if (!fieldFormat.isPrimitive()) {
192            int formatId = input.readPackedInt();
193            if (formatId == Format.ID_NULL) {
194                /* Key field is null. */
195                return null;
196            }
197            if (formatId < 0) {
198                int offset = (-(formatId + 1));
199                if (offset == VisitedObjects.PRI_KEY_VISITED_OFFSET) {
200                    assert priKeyEntry != null && priKeyFormatId > 0;
201                    input = new RecordInput(this, priKeyEntry);
202                    formatId = priKeyFormatId;
203                } else {
204                    input = new RecordInput(this, offset);
205                    formatId = input.readPackedInt();
206                }
207            }
208            fieldFormat = catalog.getFormat(formatId);
209        }
210        /* Key field is non-null. */
211        return new KeyLocation(input, fieldFormat);
212    }
213
214    /**
215     * @see EntityInput#registerPriKeyObject
216     */
217    public void registerPriKeyObject(Object o) {
218
219        /*
220         * PRI_KEY_VISITED_OFFSET is used as the visited offset to indicate
221         * that the visited object is stored in the primary key byte array.
222         */
223        if (visited == null) {
224            visited = new VisitedObjects();
225        }
226        visited.add(o, VisitedObjects.PRI_KEY_VISITED_OFFSET);
227    }
228
229    /**
230     * @see EntityInput#skipField
231     */
232    public void skipField(Format declaredFormat) {
233        if (declaredFormat != null && declaredFormat.isPrimitive()) {
234            declaredFormat.skipContents(this);
235        } else {
236            int formatId = readPackedInt();
237            if (formatId > 0) {
238                Format format = catalog.getFormat(formatId);
239                format.skipContents(this);
240            }
241        }
242    }
243
244    public int readArrayLength() {
245        return readPackedInt();
246    }
247
248    public int readEnumConstant(String[] names) {
249        return readPackedInt();
250    }
251}
252