ghidra/Ghidra/Framework/DB/src/main/java/db/Field.java
2024-11-05 19:36:00 +07:00

569 lines
16 KiB
Java

/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <p><code>Field</code> is an abstract data wrapper for use with Records.
* Note that when comparing two Field instances both must be of the same
* class.</p>
*
* <p>Fields may take on a null state. In the case of {@link FixedField}
* and {@link PrimitiveField} this state is distinct from value and only
* applies when used for a sparse column within a {@link SparseRecord}.
* In this sparse column situation the {@link SparseRecord#setField(int, Field)}
* method may be passed a null Field argument. Sparse columns with a
* null value/state will not be indexed within a {@link Table}.
*
* <p>Stored Schema Field Type Encoding:</p>
*
* <p><U>8-bit Legacy Field Type Encoding (I....FFF)</U></p>
* Supported encodings: 0x00..0x06 and 0x80..0x86,
* where:
* <pre>
* FFF - indexed field type (0..6)
* I - index field indicator (only long primary keys were supported)
* </pre>
*
* <p><U>8-bit Field Type Encoding (PPPPFFFF)</U></p>
* (Reserved for future field extensions: 0x88 and 0xf0..0xff)
* <pre>
* 0xff - see {@link Schema#FIELD_EXTENSION_INDICATOR}
* </pre>
* where:
* <pre>
* FFFF - normal/indexed field type
* PPPP - indexed table primary key type (1000b: LegacyIndexField)
* </pre>
*/
public abstract class Field implements Comparable<Field> {
public static final Field[] EMPTY_ARRAY = new Field[0];
/**
* Field type for ByteField
* @see db.ByteField
*/
static final byte BYTE_TYPE = 0;
/**
* Field type for ShortField
* @see db.ShortField
*/
static final byte SHORT_TYPE = 1;
/**
* Field type for IntField
* @see db.IntField
*/
static final byte INT_TYPE = 2;
/**
* Field type for LongField
* @see db.LongField
*/
static final byte LONG_TYPE = 3;
/**
* Field type for StringField
* @see db.StringField
*/
static final byte STRING_TYPE = 4;
/**
* Field type for BinaryField
* @see db.BinaryField
*/
static final byte BINARY_OBJ_TYPE = 5;
/**
* Field type for BooleanField
* @see db.BooleanField
*/
static final byte BOOLEAN_TYPE = 6;
/**
* Field type for 10-byte binary FixedField(10)
* @see db.FixedField
*/
static final byte FIXED_10_TYPE = 7;
/**
* Legacy Index Primary Key Field type for LongField
* which was previously a boolean indicator for an index
* field with assumed long primary key. Applies only
* to upper-nibble. This value in the lower-nibble
* is reserved for use in the special-purpose byte value 0x88.
* (see {@link LegacyIndexField})
*/
static final byte LEGACY_INDEX_LONG_TYPE = 8;
// Available field types (6): 0x9..0xE
/**
* Reserved field encoding. Intended for special purpose
* schema used (e.g.
*/
static final byte FIELD_RESERVED_15_TYPE = 0xf;
/**
* Field base type mask
*/
static final byte FIELD_TYPE_MASK = (byte) 0x0F;
/**
* Field index primary key type mask
*/
static final byte INDEX_PRIMARY_KEY_TYPE_MASK = (byte) ~FIELD_TYPE_MASK;
/**
* Index Primary Key Field Type Shift
*/
static final int INDEX_FIELD_TYPE_SHIFT = 4;
private final boolean immutable;
/**
* Abstract Field Constructor for a mutable instance
*/
Field() {
immutable = false;
}
/**
* Abstract Field Constructor
* @param immutable true if field value is immutable
*/
Field(boolean immutable) {
this.immutable = immutable;
}
void checkImmutable() {
if (immutable) {
throw new IllegalFieldAccessException("immutable field instance");
}
}
/**
* Get field as a long value.
* All fixed-length field objects must implement this method
* @return long value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public long getLongValue() {
throw new IllegalFieldAccessException();
}
/**
* Set field's long value.
* All fixed-length field objects must implement this method
* @param value long value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setLongValue(long value) {
throw new IllegalFieldAccessException();
}
/**
* Get field as an integer value.
* @return integer value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public int getIntValue() {
throw new IllegalFieldAccessException();
}
/**
* Set field's integer value.
* @param value integer value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setIntValue(int value) {
throw new IllegalFieldAccessException();
}
/**
* Get field as a short value.
* @return short value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public short getShortValue() {
throw new IllegalFieldAccessException();
}
/**
* Set field's short value.
* @param value short value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setShortValue(short value) {
throw new IllegalFieldAccessException();
}
/**
* Get field as a byte value.
* @return byte value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public byte getByteValue() {
throw new IllegalFieldAccessException();
}
/**
* Set field's byte value.
* @param value byte value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setByteValue(byte value) {
throw new IllegalFieldAccessException();
}
/**
* Get field as a boolean value.
* @return boolean value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public boolean getBooleanValue() {
throw new IllegalFieldAccessException();
}
/**
* Set field's boolean value.
* @param value boolean value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setBooleanValue(boolean value) {
throw new IllegalFieldAccessException();
}
/**
* Get data as a byte array.
* @return byte[]
*/
abstract public byte[] getBinaryData();
/**
* Set data from binary byte array.
* All variable-length fields must implement this method.
* @param bytes field data
* @throws IllegalFieldAccessException if error occurs while reading bytes
* into field which will generally be caused by the incorrect number of
* bytes provided to a fixed-length field.
*/
abstract public void setBinaryData(byte[] bytes);
/**
* Get field as a String value.
* @return String value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public String getString() {
throw new IllegalFieldAccessException();
}
/**
* Set field's String value.
* @param str String value
* @throws IllegalFieldAccessException thrown if method is not supported by specific
* Field instance.
*/
public void setString(String str) {
throw new IllegalFieldAccessException();
}
/**
* Truncate a variable length field to the specified length.
* If current length is shorterm, this method has no affect.
* @param length truncated length
*/
void truncate(int length) {
throw new UnsupportedOperationException("Field may not be truncated");
}
/**
* @return true if a Field instance is variable length, else false.
*/
public boolean isVariableLength() {
return false;
}
/**
* Determine if specified field is same type as this field
* @param field a Field instance
* @return true if field is same type as this field
*/
public boolean isSameType(Field field) {
return field != null && field.getClass() == getClass();
}
/**
* Create new instance of this field with the same value.
* @return new field instance with same value
*/
public abstract Field copyField();
/**
* Create new instance of this field type.
* @return new field instance with undefined initial value
*/
public abstract Field newField();
/**
* Return Field instance type as an integer value.
* @return encoded field type
*/
abstract byte getFieldType();
/**
* Write the field to buf at the specified offset. When writing variable length
* fields, the length precedes the actual data.
* @param buf data buffer
* @param offset data offset
* @return next available Field offset within buffer, or -1 if end of buffer reached.
* @throws IndexOutOfBoundsException if invalid offset is specified
* @throws IOException thrown if IO error occurs
*/
abstract int write(Buffer buf, int offset) throws IndexOutOfBoundsException, IOException;
/**
* Read the field value from buf at the specified offset. When reading variable length
* fields, the length precedes the actual data.
* @param buf data buffer
* @param offset data offset
* @return next Field offset within buffer, or -1 if end of buffer reached.
* @throws IndexOutOfBoundsException if invalid offset is specified
* @throws IOException thrown if IO error occurs
*/
abstract int read(Buffer buf, int offset) throws IndexOutOfBoundsException, IOException;
/**
* Get the total number of bytes which will be read from the buffer
* for this field. For variable-length fields, only the length
* portion of the data is examined within the buffer. This method is intended
* to be used instead of the read method when only interested in the data
* length.
* @param buf data buffer
* @param offset data offset
* @return total number of bytes for this field stored within buf
* @throws IndexOutOfBoundsException if invalid offset is specified
* @throws IOException thrown if IO error occurs
*/
abstract int readLength(Buffer buf, int offset) throws IndexOutOfBoundsException, IOException;
/**
* Get the number of bytes required to store this field value.
* For a variable length fields, this value also accounts for a 4-byte
* length prefix. Additionally, this method should not be invoked when
* working with stored data until after the read method has been invoked.
* @return total storage length
*/
abstract int length();
/**
* Determine if the specified Object is another Field which has the same
* type and value as this Field. When comparing a {@link PrimitiveField},
* with a null state, a value of zero (0) is used.
* @param obj another object
* @return true if this field equals obj
*/
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
/**
* Get field value as a formatted string
* @return field value string
*/
public abstract String getValueAsString();
/**
* Get minimum field value.
*
* Supported for fixed-length fields only.
* @return minimum value
* @throws UnsupportedOperationException if field is not fixed-length
*/
abstract Field getMinValue();
/**
* Get maximum field value.
*
* Supported for fixed-length fields only.
* @return maximum value
* @throws UnsupportedOperationException if field is not fixed-length
*/
abstract Field getMaxValue();
/**
* Determine if the field has been set to a null-state or value.
* @return true if field has been set to a null state or value, else false
*/
public abstract boolean isNull();
/**
* Set this field to its null-state. For variable-length field this will
* generally correspond to a null value, while primitive and fixed-length
* fields will be set to a zero (0) value. This method may only be invoked
* on a sparse column field.
* @throws IllegalFieldAccessException thrown if this field is immutable or is an index field
*/
abstract void setNull();
/**
* Performs a fast in-place comparison of this field value with another
* field value stored within the specified buffer at the the specified offset.
* NOTE: This method will treat all null primitives as 0 although is not intended
* to support such use.
* @param buffer data buffer
* @param offset field value offset within buffer
* @return comparison value, zero if equal, -1 if this field has a value
* less than the stored field, or +1 if this field has a value greater than
* the stored field located at keyIndex.
* @throws IndexOutOfBoundsException if invalid offset is specified
*/
abstract int compareTo(DataBuffer buffer, int offset) throws IndexOutOfBoundsException;
/**
* Compares this Field with another Field for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified Field.
* <br>
* NOTE: Field objects do not fully comply with the Comparable interface.
* Only the same Field implementations may be compared. In addition, the
* null state is not considered when comparing {@link PrimitiveField}s which have a
* zero (0) value.
* @param otherField another Field which is the same type as this Field
* @return field comparison result (see {@link Comparable#compareTo(Object)}).
* @throws ClassCastException if an attempt to compare dissimilar Fields (e.g.,
* an IntField may not be compared with a ShortField).
*/
@Override
public abstract int compareTo(Field otherField);
/**
* Get the field associated with the specified type value.
* @param fieldType field type index
* @return Field field instance which corresponds to the specified fieldType
* @throws UnsupportedFieldException if unsupported fieldType specified
*/
static Field getField(byte fieldType) throws UnsupportedFieldException {
if (fieldType == 0x88) {
// 0x88 - Reserved value (future expanded Field encoding)
throw new UnsupportedFieldException(fieldType);
}
if ((fieldType & INDEX_PRIMARY_KEY_TYPE_MASK) == 0) {
switch (fieldType & FIELD_TYPE_MASK) {
case LONG_TYPE:
return LongField.INSTANCE;
case INT_TYPE:
return IntField.INSTANCE;
case STRING_TYPE:
return StringField.INSTANCE;
case SHORT_TYPE:
return ShortField.INSTANCE;
case BYTE_TYPE:
return ByteField.INSTANCE;
case BOOLEAN_TYPE:
return BooleanField.INSTANCE;
case BINARY_OBJ_TYPE:
return BinaryField.INSTANCE;
case FIXED_10_TYPE:
return FixedField10.INSTANCE;
}
}
else {
return IndexField.getIndexField(fieldType);
}
throw new UnsupportedFieldException(fieldType);
}
public static class UnsupportedFieldException extends IOException {
UnsupportedFieldException(byte fieldType) {
super("Unsupported DB field type: 0x" + Integer.toHexString(fieldType & 0xff));
}
UnsupportedFieldException(String msg) {
super(msg);
}
}
/**
* Get the type index value of the FixedField type which corresponds
* to the specified fixed-length;
* @param fixedLength fixed length (currently only 10 is supported)
* @return FixedLength field type index
* @throws IllegalArgumentException if unsupported fixedLength is specified
*/
static byte getFixedType(int fixedLength) {
if (fixedLength == 10) {
return FIXED_10_TYPE;
}
throw new IllegalArgumentException(
"Unsupported fixed-length binary type size: " + fixedLength);
}
/**
* Get a fixed-length field of the specified size
* @param size fixed-field length (supported sizes: 1, 4, 8, 10)
* @return fixed field instance
* @throws IllegalArgumentException if unsupported fixed field length
*/
static Field getFixedField(int size) {
switch (size) {
case 1:
return new ByteField();
case 4:
return new IntField();
case 8:
return new LongField();
case 10:
return new FixedField10();
}
throw new IllegalArgumentException("Unsupported fixed-field length: " + size);
}
/**
* Determine if a specified field instance may be indexed
* @param field field to be checked
* @return true if field can be indexed
*/
public static boolean canIndex(Field field) {
if (field == null) {
return false;
}
if (field instanceof IndexField) {
return false;
}
return !field.isSameType(BooleanField.INSTANCE) && !field.isSameType(ByteField.INSTANCE);
}
}