/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* 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; | |
/** | |
* <p> | |
* <b>Purpose</b>: Define the abstract definition of a record for internal use. | |
* Public API should reference the Map or Record interface. | |
* Subclasses are DatabaseRecord and XMLRecord. | |
* <p> | |
* <b>Responsibilities</b>: <ul> | |
* <li> Implement the Record and Map interfaces. | |
* </ul> | |
* @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<DatabaseField> 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<DatabaseField> 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; | |
} | |
} |