org.eclipse.persistence.internal.sessions.AbstractRecord Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.persistence.core Show documentation
Show all versions of org.eclipse.persistence.core Show documentation
EclipseLink build based upon Git transaction ecdf3c32c4
/*
* Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.sessions;
import java.io.*;
import java.util.*;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractRecord;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.sessions.Record;
import org.eclipse.persistence.internal.helper.DatabaseField;
/**
*
* Purpose: Define the abstract definition of a record for internal use.
* Public API should reference the Map or Record interface.
* Subclasses are DatabaseRecord and XMLRecord.
*
* Responsibilities:
* - Implement the Record and Map interfaces.
*
* @see DatabaseField
*/
public abstract class AbstractRecord extends CoreAbstractRecord implements Record, Cloneable, Serializable, Map {
/** Use vector to store the fields/values for optimal performance.*/
protected Vector fields;
/** Use vector to store the fields/values for optimal performance.*/
protected Vector values;
/** Optimize field creation for field name lookup. */
protected DatabaseField lookupField;
/** PERF: Cache the row size. */
protected int size;
/** INTERNAL: indicator showing that no entry exists for a given key. */
public static final AbstractRecord.NoEntry noEntry = new AbstractRecord.NoEntry();
/** INTERNAL: flag for any database field containing a null value */
protected boolean nullValueInFields;
/** INTERNAL: SerializedObjectPolicy support */
protected transient Object sopObject;
/**
* INTERNAL:
* NoEntry: This is used to differentiate between the two kinds
* of nulls: no entry exists, and the field is actually mapped
* to null.
*/
public static class NoEntry {
private NoEntry() {
}
}
/**
* INTERNAL:
* converts JDBC results to collections of rows.
*/
public AbstractRecord() {
this.fields = new NonSynchronizedVector();
this.values = new NonSynchronizedVector();
this.size = 0;
this.nullValueInFields = false;
}
/**
* INTERNAL:
* converts JDBC results to collections of rows.
*/
public AbstractRecord(int initialCapacity) {
this.fields = new NonSynchronizedVector(initialCapacity);
this.values = new NonSynchronizedVector(initialCapacity);
this.size = 0;
this.nullValueInFields = false;
}
/**
* INTERNAL:
* converts JDBC results to collections of rows.
*/
public AbstractRecord(Vector fields, Vector values) {
this.fields = fields;
this.values = values;
this.nullValueInFields = false;
resetSize();
}
/**
* INTERNAL:
* converts JDBC results to collections of rows.
*/
public AbstractRecord(Vector fields, Vector values, int size) {
this.fields = fields;
this.values = values;
this.nullValueInFields = false;
this.size = size;
}
/**
* Reset the row size.
* This must be reset after any change to the row.
*/
protected void resetSize() {
if (this.fields == null) {
this.size = 0;
} else {
this.size = this.fields.size();
}
}
/**
* INTERNAL:
* Add the field-value pair to the row. Will not check,
* will simply add to the end of the row
*/
public void add(DatabaseField key, Object value) {
this.fields.add(key);
this.values.add(value);
this.size++;
}
/**
* PUBLIC:
* Clear the contents of the row.
*/
public void clear() {
this.fields = new Vector();
this.values = new Vector();
resetSize();
}
/**
* INTERNAL:
* Clone the row and its values.
*/
public AbstractRecord clone() {
try {
AbstractRecord clone = (AbstractRecord)super.clone();
clone.fields = (Vector)this.fields.clone();
clone.values = (Vector)this.values.clone();
return clone;
} catch (CloneNotSupportedException exception) {
throw new InternalError();
}
}
/**
* PUBLIC:
* Check if the value is contained in the row.
*/
public boolean contains(Object value) {
return containsValue(value);
}
/**
* PUBLIC:
* Check if the field is contained in the row.
* Conform to hashtable interface.
*/
public boolean containsKey(Object key) {
if (key instanceof String) {
return containsKey((String)key);
}
if (key instanceof DatabaseField) {
return containsKey((DatabaseField)key);
}
return false;
}
/**
* PUBLIC:
* Check if the field is contained in the row.
*/
public boolean containsKey(String fieldName) {
return containsKey(getLookupField(fieldName));
}
/**
* INTERNAL:
* Check if the field is contained in the row.
*/
public boolean containsKey(DatabaseField key) {
// Optimize check.
int index = key.index;
if ((index >= 0) && (index < this.size)) {
DatabaseField field = this.fields.get(index);
if ((field == key) || field.equals(key)) {
return true;
}
}
return this.fields.contains(key);
}
/**
* PUBLIC:
* Check if the value is contained in the row.
*/
public boolean containsValue(Object value) {
return getValues().contains(value);
}
/**
* PUBLIC:
* Returns an Enumeration of the values.
*/
public Enumeration elements() {
return getValues().elements();
}
/**
* PUBLIC:
* Returns a set of the keys.
*/
public Set entrySet() {
return new EntrySet();
}
/**
* PUBLIC:
* Retrieve the value for the field name.
* A field is constructed on the name to check the hash table.
* If missing null is returned.
*/
public Object get(Object key) {
if (key instanceof String) {
return get((String)key);
} else if (key instanceof DatabaseField) {
return get((DatabaseField)key);
}
return null;
}
/**
* PUBLIC:
* Retrieve the value for the field name.
* A field is constructed on the name to check the hash table.
* If missing null is returned.
*/
public Object get(String fieldName) {
return get(getLookupField(fieldName));
}
/**
* Internal: factored out of getIndicatingNoEntry(String) to reduce complexity and have
* get(string) use get(DatabaseField) instead of getIndicatingNoEntry and then doing an extra check
* @param fieldName
* @return
*/
protected DatabaseField getLookupField(String fieldName){
if (this.lookupField == null) {
this.lookupField = new DatabaseField(fieldName);
} else {
this.lookupField.resetQualifiedName(fieldName);
}
return this.lookupField;
}
/**
* PUBLIC:
* Retrieve the value for the field name.
* A field is constructed on the name to check the hash table.
* If missing DatabaseRow.noEntry is returned.
*/
public Object getIndicatingNoEntry(String fieldName) {
// Optimized the field creation.
return getIndicatingNoEntry(getLookupField(fieldName));
}
/**
* INTERNAL:
* Retrieve the value for the field. If missing null is returned.
*/
public Object get(DatabaseField key) {
// PERF: Direct variable access.
// ** Code duplicated in getIndicatingNoEntry, replaceAt ensure kept in synch **
// Optimize check.
int index = key.index;
if ((index >= 0) && (index < this.size)) {
DatabaseField field = this.fields.get(index);
if ((field == key) || field.equals(key)) {
return this.values.get(index);
}
}
int fieldsIndex = this.fields.indexOf(key);
if (fieldsIndex >= 0) {
// PERF: If the fields index was not set, then set it.
if (index == -1) {
key.setIndex(fieldsIndex);
}
return this.values.get(fieldsIndex);
} else {
return null;
}
}
//----------------------------------------------------------------------------//
public Object getValues(DatabaseField key) {
return get(key);
}
public Object getValues(String key) {
return get(key);
}
//----------------------------------------------------------------------------//
/**
* INTERNAL:
* Retrieve the value for the field. If missing DatabaseRow.noEntry is returned.
*/
public Object getIndicatingNoEntry(DatabaseField key) {
// PERF: Direct variable access.
// ** Code duplicated in get, ensure kept in synch **
// Optimize check.
int index = key.index;
if ((index >= 0) && (index < this.size)) {
DatabaseField field = this.fields.get(index);
if ((field == key) || field.equals(key)) {
return this.values.get(index);
}
}
int fieldsIndex = this.fields.indexOf(key);
if (fieldsIndex >= 0) {
// PERF: If the fields index was not set, then set it.
if (index == -1) {
key.setIndex(fieldsIndex);
}
return this.values.get(fieldsIndex);
} else {
return AbstractRecord.noEntry;
}
}
/**
* INTERNAL:
* Returns the row's field with the same name.
*/
public DatabaseField getField(DatabaseField key) {
// Optimize check.
int index = key.index;
if ((index >= 0) && (index < getFields().size())) {
DatabaseField field = getFields().elementAt(index);
if ((field == key) || field.equals(key)) {
return field;
}
}
for (index = 0; index < getFields().size(); index++) {
DatabaseField field = getFields().elementAt(index);
if ((field == key) || field.equals(key)) {
return field;
}
}
return null;
}
/**
* INTERNAL:
*/
public Vector getFields() {
return fields;
}
/**
* INTERNAL:
*/
public Vector getValues() {
return values;
}
/**
* PUBLIC:
* Return if the row is empty.
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* INTERNAL:
* Return true if the AbstractRecord has been marked as valid
* to check the update call cache with, false otherwise.
*/
public boolean hasNullValueInFields() {
return this.nullValueInFields;
}
/**
* PUBLIC:
* Returns an Enumeration of the DatabaseField objects.
*/
public Enumeration keys() {
return getFields().elements();
}
/**
* PUBLIC:
* Returns a set of the keys.
*/
public Set keySet() {
return new KeySet();
}
/**
* Defines the virtual keySet.
*/
protected class KeySet extends EntrySet {
public Iterator iterator() {
return new RecordKeyIterator();
}
public boolean contains(Object object) {
return AbstractRecord.this.containsKey(object);
}
public boolean remove(Object object) {
return AbstractRecord.this.remove(object) != null;
}
}
/**
* Defines the virtual valuesSet.
*/
protected class ValuesSet extends EntrySet {
public Iterator iterator() {
return new RecordValuesIterator();
}
public boolean contains(Object object) {
return AbstractRecord.this.contains(object);
}
public boolean remove(Object object) {
int index = getValues().indexOf(object);
if (index == -1) {
return false;
}
AbstractRecord.this.remove(getFields().get(index));
return true;
}
}
/**
* Defines the virtual entrySet.
*/
protected class EntrySet extends AbstractSet {
public Iterator iterator() {
return new RecordEntryIterator();
}
public int size() {
return AbstractRecord.this.size();
}
public boolean contains(Object object) {
if (!(object instanceof Entry)) {
return false;
}
return AbstractRecord.this.containsKey(((Entry)object).getKey());
}
public boolean remove(Object object) {
if (!(object instanceof Entry)) {
return false;
}
AbstractRecord.this.remove(((Entry)object).getKey());
return true;
}
public void clear() {
AbstractRecord.this.clear();
}
}
/**
* Defines the virtual entrySet iterator.
*/
protected class RecordEntryIterator implements Iterator {
int index;
RecordEntryIterator() {
this.index = 0;
}
public boolean hasNext() {
return this.index < AbstractRecord.this.size();
}
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
this.index++;
return new RecordEntry(getFields().get(this.index - 1), getValues().get(this.index - 1));
}
public void remove() {
if (this.index >= AbstractRecord.this.size()) {
throw new IllegalStateException();
}
AbstractRecord.this.remove(getFields().get(this.index));
}
}
/**
* Defines the virtual keySet iterator.
*/
protected class RecordKeyIterator extends RecordEntryIterator {
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
this.index++;
return getFields().get(this.index - 1);
}
}
/**
* Defines the virtual valuesSet iterator.
*/
protected class RecordValuesIterator extends RecordEntryIterator {
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
this.index++;
return getValues().get(this.index - 1);
}
}
/**
* Entry class for implementing Map interface.
*/
protected static class RecordEntry implements Entry {
Object key;
Object value;
public RecordEntry(Object key, Object value) {
this.key = key;
this.value = value;
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object object) {
if (!(object instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry)object;
return compare(key, entry.getKey()) && compare(value, entry.getValue());
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
private boolean compare(Object object1, Object object2) {
return (object1 == null ? object2 == null : object1.equals(object2));
}
}
/**
* INTERNAL:
* Merge the provided row into this row. Existing field values in this row will
* be replaced with values from the provided row. Fields not in this row will be
* added from provided row. Values not in provided row will remain in this row.
*/
public void mergeFrom(AbstractRecord row){
for (int index = 0; index < row.size(); ++index){
this.put(row.getFields().get(index), row.getValues().get(index));
}
}
/**
* PUBLIC:
* Add the field-value pair to the row.
*/
public Object put(Object key, Object value) throws ValidationException {
if (key instanceof String) {
return put((String)key, value);
} else if (key instanceof DatabaseField) {
return put((DatabaseField)key, value);
} else {
throw ValidationException.onlyFieldsAreValidKeysForDatabaseRows();
}
}
/**
* PUBLIC:
* Add the field-value pair to the row.
*/
public Object put(String key, Object value) {
return put(new DatabaseField(key), value);
}
/**
* INTERNAL:
* Add the field-value pair to the row.
*/
public Object put(DatabaseField key, Object value) {
int index = this.fields.indexOf(key);
if (index >= 0) {
return this.values.set(index, value);
} else {
add(key, value);
}
return null;
}
/**
* PUBLIC:
* Add all of the elements.
*/
public void putAll(Map map) {
Iterator entriesIterator = map.entrySet().iterator();
while (entriesIterator.hasNext()) {
Map.Entry entry = (Map.Entry)entriesIterator.next();
put(entry.getKey(), entry.getValue());
}
}
/**
* INTERNAL:
* Remove the field key from the row.
*/
public Object remove(Object key) {
if (key instanceof String) {
return remove((String)key);
} else if (key instanceof DatabaseField) {
return remove((DatabaseField)key);
}
return null;
}
/**
* INTERNAL:
* Remove the field key from the row.
*/
public Object remove(String fieldName) {
return remove(new DatabaseField(fieldName));
}
/**
* INTERNAL:
* Remove the field key from the row.
*/
public Object remove(DatabaseField key) {
int index = getFields().indexOf(key);
if (index >= 0) {
getFields().removeElementAt(index);
Object value = getValues().elementAt(index);
getValues().removeElementAt(index);
resetSize();
return value;
}
return null;
}
/**
* INTERNAL:
* replaces the value at index with value
*/
public void replaceAt(Object value, int index) {
this.values.set(index, value);
}
/**
* INTERNAL:
* replaces the value at field with value
*/
public void replaceAt(Object value, DatabaseField key) {
int index = key.index;
if ((index >= 0) && (index < this.size)) {
DatabaseField field = this.fields.get(index);
if ((field == key) || field.equals(key)) {
this.values.set(index, value);
}
}
int fieldsIndex = this.fields.indexOf(key);
if (fieldsIndex >= 0) {
// PERF: If the fields index was not set, then set it.
if (index == -1) {
key.setIndex(fieldsIndex);
}
this.values.set(fieldsIndex, value);
}
}
protected void setFields(Vector fields) {
this.fields = fields;
resetSize();
}
/**
* INTERNAL:
* Set the validForUpdateCallCacheCheck attribute to true if the row
* does not contain nulls, false otherwise
*/
public void setNullValueInFields(boolean nullValueInFields) {
this.nullValueInFields = nullValueInFields;
}
protected void setValues(Vector values) {
this.values = values;
}
/**
* PUBLIC:
* Return the number of field/value pairs in the row.
*/
public int size() {
return this.fields.size();
}
/**
* INTERNAL:
*/
public String toString() {
StringWriter writer = new StringWriter();
writer.write(Helper.getShortClassName(getClass()));
writer.write("(");
for (int index = 0; index < getFields().size(); index++) {
writer.write(Helper.cr());
writer.write("\t");
writer.write(String.valueOf((getFields().elementAt(index))));
writer.write(" => ");
writer.write(String.valueOf((getValues().elementAt(index))));
}
if (this.sopObject != null) {
writer.write(Helper.cr());
writer.write(" sopObject = ");
writer.write(this.sopObject.toString());
}
writer.write(")");
return writer.toString();
}
/**
* PUBLIC:
* Returns an collection of the values.
*/
public Collection values() {
return new ValuesSet();
}
/**
* INTERNAL:
*/
public boolean hasSopObject() {
return this.sopObject != null;
}
/**
* INTERNAL:
*/
public Object getSopObject() {
return this.sopObject;
}
/**
* INTERNAL:
*/
public void setSopObject(Object sopObject) {
this.sopObject = sopObject;
}
}