com.gemstone.gemfire.internal.tools.gfsh.app.pogo.MapLite Maven / Gradle / Ivy
Show all versions of gemfire-core Show documentation
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* 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. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal.tools.gfsh.app.pogo;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import com.gemstone.gemfire.DataSerializable;
import com.gemstone.gemfire.DataSerializer;
import com.gemstone.gemfire.Delta;
import com.gemstone.gemfire.InvalidDeltaException;
import com.gemstone.gemfire.internal.HeapDataOutputStream;
import com.gemstone.gemfire.internal.shared.Version;
/**
* MapLite is a GemFire data class for efficiently storing and retrieving data
* to/from GemFire data fabrics. It is a lightweight map class designed for
* delivering self-describing messages over the network without the cost of
* embedded keys in the wire format. MapLite achieves this by predefining the
* keys in the form of enum (and String) constants and deploying them as part of
* the application binary classes. The enum key classes are automatically
* versioned and generated using the provided IDE plug-in, ensuring full
* compatibility and coexistence with other versions.
*
* In addition to the code generator, MapLite includes the following GemFire
* cache optimization and operational features while maintaining the same level
* of self-describing message accessibility.
*
*
* - MapLite is fully POGO compliant.
* - MapLite implements {@link java.util.Map}.
* - MapLite and POGO are in general significantly lighter than POJO and
* {@link HashMap}. Its wire format is compact and does not require class
* reflection.
* - MapLite and POGO are faster than POJO and HashMap. Its smaller payload
* size means it is serialized and delivered faster.
* - MapLite lookup is faster than HashMap. MapLite keeps values internally
* indexed in an array for faster access.
* - MapLite fully supports GemFire delta propagation.
*
- MapLite fully supports selective key inflation (SKI). With SKI, the
* underlying POGO mechanism inflates only the values that are accessed by the
* application. The rest of the values are kept deflated until they are
* accessed. This reduces the memory footprint and eliminates the unnecessary
* latency overhead introduced by the serialization and deserialization
* operations.
* - MapLite fully supports the GemFire query service.
* - MapLite is fully integrated with the key class versioning mechanism,
* which enables multiple versions of MapLite key sets to coexist in the fabric.
* All versioned key classes are fully forward and backward compatible.
* - MapLite key classes are universally unique across space and time,
* eradicating the class ID requirement.
* - MapLite is natively integrated with the GemFire command line tool, gfsh.
* - MapLite is language neutral.
*
*
*
Lighter and Faster
* MapLite, in general, is significantly lighter than HashMap in terms of both
* size and speed. The size of a typical serialized MapLite object is
* considerably smaller than the counterpart HashMap object. Enum
* {@link #get(KeyType)} calls are faster than
* {@link HashMap#get(Object)} because the values are indexed in an
* array, circumventing the more expensive hash lookup operation.
*
* Map with enum KeyType Keys
* MapLite implements Map and therefore has the same Map methods and behaves
* exactly like Map. Unlike HashMap which also implements Map, a MapLite object
* is restricted to a fixed set of predefined keys in an enum class that
* implements the interface KeyType. This restriction effectively makes MapLite
* lighter, faster, and more acquiescent than HashMap. It removes the keys from
* the wire format and provides a valid key list for strict allowed key and type
* checking.
*
* Code Generator
* Editing keys, although it can be done manually, is done via the provided IDE
* plug-in which automatically generates a new version of the enum class. The
* built-in versioning mechanism allows the new versioned enum class to be
* deployed to the servers and clients during runtime without the requirement of
* restarting them. The servers automatically load the new versioned class
* making it immediately available to the application along with the previous
* versions.
*
* String Keys
* In addition to the enum keys, MapLite also supports String keys. String keys
* are costlier than enum keys but comparable to HashMap in terms of the put and
* get speeds. One of the benefits of using String keys is the flexibility of
* executing ad hoc queries. MapLite is fully compliant with the GemFire query
* service, making it ideal for object-relational mapping.
*
*
*
Using MapLite
*
* -
* Create a
{@link KeyType}
enum class using the code generator or
* the provided example template.
* -
* Register the new
KeyType
enum class using
* {@link KeyTypeManager}
.
* -
* Use
KeyType
to create MapLite
objects. Always use
* {@link #MapLite(KeyType)} to create MapLite
objects.
* -
* Put the MapLite objects into cache regions
* -
* Get the MapLite objects from cache regions
* -
* Get values from the objects using
KeyType
or String
* keys
*
*
* Examples
*
*
* import com.gemstone.gemfire.internal.tools.gfsh.pogo.KeyTypeManager;
* import com.gemstone.gemfire.internal.tools.gfsh.pogo.MapLite;
* import gemfire.examples.model.Dummy;
* import gemfre.examples.model.pogo.Dummy_v1;
* import gemfire.examples.model.pogo.Dummy_v2;
*
* . . .
*
* // Register the Dummy key type. This also registers
* // all of the versions in the sub-package pogo. This registration
* // call is not is not required if the registration plug-in
* // is included in the GemFire cache.xml file.
* KeyTypeManager.registerKeyType(Dummy.getKeyType());
*
* // Create a MapLite object using the latest Dummy version.
* // Dummy is equivalent to Dummy_v2 assuming that Dummy_v2 is the
* // latest version.
* MapLite ml = new MapLite(Dummy.getKeyType());
*
* // put data using the Dummy.Message enum constant
* ml.put(Dummy.Message, "Hello, world.");
*
* // put data using the string key "Dummy" which is equivalent to
* // Dummy.Message
* ml.put("Message", "Hello, world.");
*
* // Get the value using the Dummy.Message enum constant
* String message = (String) ml.get(Dummy.Message);
*
* // Get the value using the versioned enum class Dummy_v2 which is
* // equivalent to Dummy assuming Dummy_v2 is the latest version.
* message = (String) ml.get(Dummy_v2.Message);
*
* // Get the value using the previous versioned class Dummy_v1 which
* // may or may not work depending on the changes made to version 2.
* // If the Message key type has been changed then the type cast will
* // fail.
* message = (String) ml.get(Dummy_v1.Message);
*
* // Get the value using the string key "Message".
* message = (String) ml.get("Message");
*
*
* @author dpark
*
*/
public class MapLite implements DataSerializable, Delta, Map, Cloneable
{
private static final long serialVersionUID = 1L;
private static final int BIT_MASK_SIZE = 32; // int type
private KeyType keyType;
private Object[] values;
private int keyVersion;
private int[] dirtyFlags;
private byte flags;
// serialized values
private byte[] serializedBytes;
/**
* The use of this constructor is strongly
* discouraged. Always use {@link #MapLite(KeyType)} wherever
* possible.
*
* The default constructor creates a new MapLite object with KeyType
* undefined. Undefined KeyType may lead to undesired effects. The following
* restriction applies when using this constructor:
*
*
{@link #put(KeyType, Object)} or {@link #get(KeyType)}
* must be invoked once with a {@link KeyType} enum constant before the
* MapLite object can be used. These methods implicitly initialize the
* MapLite object with the specified key type. MapLite ignores String keys
* until the key type has been assigned.
*
* An exception to the above restriction is {@link #putAll(Map)} with the
* argument type of MapLite. If MapLite is passed in, then putAll()
* transforms this empty MapLite object into the passed-in MapLite
* key type.
*
* It is recommended that the overloaded constructor
* {@link #MapLite(KeyType)} should always be used wherever possible. This
* default constructor is primarily for satisfying Java serialization
* restrictions in addition to handling special operations such as putAll().
*
*/
public MapLite()
{
}
/**
* Creates a new MapLite object with the specified key type. Once created,
* all subsequent operations must use the same KeyType
enum
* class. The key type is obtained from the no-arg static method,
* getKeyType()
, included in the generated key type class. For
* example,
*
*
* MapLite ml = new MapLite(Dummy.getKeyType());
*
*
* @param keyType
* The key type enum constant to assign to MapLite.
*/
public MapLite(KeyType keyType)
{
init(keyType);
}
/**
* Initializes the MapLite object by creating data structures for the
* specified key type.
*
* @param keyType
* The key type enum constant to assign to MapLite.
*/
private void init(KeyType keyType)
{
if (keyType == null) {
return;
}
this.keyType = keyType;
this.keyVersion = keyType.getVersion();
int count = keyType.getKeyCount();
this.values = new Object[count];
int dirtyFlagCount = calculateDirtyFlagCount();
this.dirtyFlags = new int[dirtyFlagCount];
if (KeyTypeManager.isRegistered(keyType) == false) {
KeyTypeManager.registerKeyType(keyType);
}
}
/**
* Calculates the dirty flag count. The dirty flags are kept in an array of
* integers. Each integer value represents 32 dirty flags.
*
* @return Returns the dirty flag count.
*/
private int calculateDirtyFlagCount()
{
int count = keyType.getKeyCount();
int dirtyFlagCount = count / BIT_MASK_SIZE;
int reminder = count % BIT_MASK_SIZE;
if (reminder > 0) {
dirtyFlagCount++;
}
return dirtyFlagCount;
}
/**
* Marks all keys dirty.
*/
private void dirtyAllKeys()
{
if (dirtyFlags != null) {
for (int i = 0; i < dirtyFlags.length; i++) {
dirtyFlags[i] = 0xFFFFFFFF;
}
}
}
/**
* Clears the entire dirty flags.
*/
private void clearDirty()
{
if (dirtyFlags != null) {
for (int i = 0; i < dirtyFlags.length; i++) {
dirtyFlags[i] = 0x0;
}
}
}
/**
* Returns the key type constant used to initialize this object.
*/
public KeyType getKeyType()
{
return keyType;
}
/**
* Returns the value of the specified key type. If the default constructor
* {@link #MapLite()} is used to create this object then this method
* implicitly initializes itself with the specified key type if it has not
* been initialized previously.
*
* @param keyType
* The key type constant to lookup the mapped value.
* @return Returns the mapped value. It returns null if the value does not
* exist or it was explicitly set to null.
*/
public V get(KeyType keyType)
{
if (keyType == null) {
return null;
}
// Initialization is not thread safe.
// It allows the use of the default constructor but at
// the expense of the lack of thread safety.
if (values == null && keyType != null) {
init(keyType);
}
return (V) values[keyType.getIndex()];
}
/**
* Puts the specified value mapped by the specified key type into this
* object. If the default constructor {@link #MapLite()} is used to create
* this object then this method implicitly initializes itself with the
* specified key type if it has not been initialized previously.
*
* Note that for MapLite to be language neutral, the value type must
* be a valid POGO type. It must be strictly enforced by the application.
* For Java only applications, any Serializable objects are valid.
*
* @param keyType
* The key type constant to lookup the mapped value.
* @param value
* The value to put into the MapLite object.
* @return Returns the old value. It returns null if the old value does not
* exist or has been explicitly set to null.
* @throws InvalidKeyException
* A runtime exception thrown if the passed in value type does
* not match the key type.
*/
public V put(KeyType keyType, V value) throws InvalidKeyException
{
if (keyType == null) {
return null;
}
// Initialization is not thread safe.
// It allows the use of the default constructor but at
// the expense of the lack of thread safety.
if (values == null) {
init(keyType);
}
V oldVal = (V) values[keyType.getIndex()];
values[keyType.getIndex()] = value;
setDirty(keyType, dirtyFlags);
return oldVal;
}
/**
* Returns the mapped value for the specified key. It uses the String value
* of the key, i.e., key.toString(), to lookup the mapped value.
*
* @param key
* The key object.
*/
public V get(Object key)
{
if (keyType == null || key == null) {
return null;
}
deserialize();
KeyType keyType = this.keyType.getKeyType(key.toString());
if (keyType == null) {
return null;
}
return get(keyType);
}
/**
* Puts the specified value mapped by the specified key into this object.
* Unlike {@link #put(KeyType, Object)}, this method will not implicitly
* initialize this object if the default constructor is used. If this object
* has not bee initialized, then it throws a runtime exception
* InvalidKeyException.
*
* @param key
* The key object.
* @param value
* The value to put into the MapLite object.
* @return Returns the old value.
*/
public V put(String key, V value) throws InvalidKeyException
{
if (keyType == null) {
if (value == null) {
throw new InvalidKeyException("KeyType undefined due to the use of the MapLite default constructor. "
+ "Use MapLite(KeyType) to register the key type.");
} else {
throw new InvalidKeyException("Invalid " + value.getClass().getName()
+ ". KeyType undefined due to the use of the default constructor.");
}
}
deserialize();
KeyType keyType = this.keyType.getKeyType(key.toString());
if (keyType == null) {
return null;
}
return put(keyType, value);
}
/**
* Returns true if GemFire delta propagation is enabled and there are
* changes in values.
*/
public boolean hasDelta()
{
if (keyType.isDeltaEnabled()) {
return isDirty();
}
return false;
}
/**
* Returns true if there are changes made in values.
*/
public boolean isDirty()
{
for (int i = 0; i < dirtyFlags.length; i++) {
if ((dirtyFlags[i] & 0xFFFFFFFF) != 0) {
return true;
}
}
return false;
}
/**
* Sets the specified key type dirty.
*
* @param keyType
* The key type to set dirty.
* @param flags
* The flags that contain the key type.
*/
private void setDirty(KeyType keyType, int flags[])
{
int index = keyType.getIndex();
setDirty(index, flags);
}
/**
* Sets the specified contiguous bit of the flags. A contiguous bit is the
* bit number of the contiguous array integers. For example, if the flags
* array size is 2 then the contiguous bit of 32 represents the first bit of
* the flags[1] integer, 33 represents the second bit, and etc.
*
* @param contiguousBit
* The contiguous bit position.
* @param flags
* The bit flags.
*/
private void setDirty(int contiguousBit, int flags[])
{
int dirtyFlagsIndex = contiguousBit / BIT_MASK_SIZE;
int bit = contiguousBit % BIT_MASK_SIZE;
flags[dirtyFlagsIndex] |= 1 << bit;
}
/**
* Returns true if the specified key type is dirty.
*
* @param keyType
* The key type to check.
* @param flags
* The flags that contain the key type.
*/
//FindBugs - private method never called
// private boolean isBitDirty(KeyType keyType, int flags[])
// {
// int index = keyType.getIndex();
// int dirtyFlagsIndex = index / BIT_MASK_SIZE;
// int bit = index % BIT_MASK_SIZE;
// return isBitDirty(flags[dirtyFlagsIndex], bit);
// }
/**
* Returns true if the specified contiguous bit of the flags is set. A
* contiguous bit the bit number of the contiguous array integers. For
* example, if the flags array size is 2 then the contiguous bit of 32
* represents the first bit of the flags[1] integer, 33 represents the
* second bit, and etc.
*
* @param contiguousBit
* The contiguous bit position
* @param flags
* The bit flags
*/
//FindBugs - private method never called
// private boolean isDirty(int contiguousBit, int flags[])
// {
// int dirtyFlagsIndex = contiguousBit / BIT_MASK_SIZE;
// int bit = contiguousBit % BIT_MASK_SIZE;
// return isBitDirty(flags[dirtyFlagsIndex], bit);
// }
/**
* Returns true if the specified flag bit id dirty.
*
* @param flag
* The flag to check.
* @param bit
* The bit to compare.
* @return true if the specified flag bit id dirty.
*/
private boolean isBitDirty(int flag, int bit)
{
return ((flag >> bit) & 1) == 1;
}
/**
* Returns true if the any of the flag bits is dirty.
*
* @param flag
* The flag to check.
*/
private boolean isDirty(int flag)
{
return (flag & 0xFFFFFFFF) != 0;
}
/**
* Deserializes (inflates) the serialized bytes if has not been done.
*/
private void deserialize()
{
byte[] byteArray = serializedBytes;
if (byteArray != null) {
KeyType[] keyTypeValues = keyType.getValues(keyVersion);
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
DataInputStream dis = new DataInputStream(bais);
try {
for (int i = 0; i < keyTypeValues.length; i++) {
if (keyTypeValues[i].isKeyKeepSerialized()) {
// deserialized values
values[i] = readValue(keyTypeValues, i, dis);
}
}
dis.close();
serializedBytes = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Reads the value at for the specified key type index.
*
* @param keyTypes
* The entire key types that represent the MapLite values.
* @param index
* The index of the key to read.
* @param input
* The input stream.
* @return Returns the read value.
* @throws IOException
* Thrown if an IO error encountered.
* @throws ClassNotFoundException
*/
private Object readValue(KeyType[] keyTypes, int index, DataInput input) throws IOException, ClassNotFoundException
{
return MapLiteSerializer.read(keyTypes[index].getType(), input);
}
/**
* Reads MapLite contents in the specified input stream.
*
* @param input
* The input stream.
* @throws IOException
* Thrown if an IO error encountered.
* @throws ClassNotFoundException
* Thrown if the input stream contains the wrong class type.
* This should never occur with MapLite.
*/
public void fromData(DataInput input) throws IOException, ClassNotFoundException
{
flags = MapLiteSerializer.readByte(input);
long mostSigBits = input.readLong();
long leastSigBits = input.readLong();
keyVersion = DataSerializer.readUnsignedShort(input);
keyType = KeyTypeManager.getKeyType(mostSigBits, leastSigBits, keyVersion);
init(keyType);
values = new Object[keyType.getKeyCount()];
KeyType[] keyTypeValues = keyType.getValues(keyVersion);
if (keyType.isPayloadKeepSerialized()) {
// need not to lock since fromData is invoked only
// once by GemFire
serializedBytes = DataSerializer.readByteArray(input);
byte[] deserializedBytes = DataSerializer.readByteArray(input);
ByteArrayInputStream bais = new ByteArrayInputStream(deserializedBytes);
DataInputStream dis = new DataInputStream(bais);
for (int i = 0; i < keyTypeValues.length; i++) {
if (keyTypeValues[i].isKeyKeepSerialized() == false) {
// deserialized values
values[i] = readValue(keyTypeValues, i, dis);
}
}
dis.close();
} else {
for (int i = 0; i < keyTypeValues.length; i++) {
values[i] = readValue(keyTypeValues, i, input);
}
}
}
/**
* Writes the value of the specified index to the output stream.
*
* @param keyTypes
* The entire key types that represent the MapLite values.
* @param index
* The index of the key to write.
* @param output
* The output stream.
* @throws IOException
* Thrown if an IO error encountered.
*/
private void writeValue(KeyType[] keyTypes, int index, DataOutput output) throws IOException
{
try {
MapLiteSerializer.write(keyTypes[index].getType(), values[index], output);
} catch (Exception ex) {
throw new InvalidKeyException(ex.getMessage() + keyTypes.getClass() + " index=" + keyTypes[index].getName(), ex);
}
}
/**
* Writes the MapLite contents to the specified output stream.
*
* @param output
* The output stream.
* @throws IOException
* Thrown if an IO error encountered.
*/
public void toData(DataOutput output) throws IOException
{
MapLiteSerializer.writeByte(flags, output);
output.writeLong(((UUID) keyType.getId()).getMostSignificantBits());
output.writeLong(((UUID) keyType.getId()).getLeastSignificantBits());
DataSerializer.writeUnsignedShort(keyType.getVersion(), output);
KeyType[] keyTypeValues = keyType.getValues(keyVersion);
if (keyType.isPayloadKeepSerialized()) {
// assign byteArray to serializedBytes beforehand to
// handle race condition
byte[] byteArray = serializedBytes;
if (byteArray != null) {
DataSerializer.writeByteArray(byteArray, output);
HeapDataOutputStream hdos2 = new HeapDataOutputStream(Version.CURRENT);
for (int i = 0; i < keyTypeValues.length; i++) {
if (keyTypeValues[i].isKeyKeepSerialized() == false) {
// keep it separate in deserialized array.
// this array is always deserialized
writeValue(keyTypeValues, i, hdos2);
}
}
DataSerializer.writeByteArray(hdos2.toByteArray(), output);
hdos2.close();
} else {
HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT);
HeapDataOutputStream hdos2 = new HeapDataOutputStream(Version.CURRENT);
for (int i = 0; i < keyTypeValues.length; i++) {
if (keyTypeValues[i].isKeyKeepSerialized()) {
// serialize in the normal array
// the normal array is deserialized only when the
// one of its keys is accessed.
writeValue(keyTypeValues, i, hdos);
} else {
// keep it separate in deserialized array.
// this array is always deserialized
writeValue(keyTypeValues, i, hdos2);
}
}
DataSerializer.writeByteArray(hdos.toByteArray(), output);
DataSerializer.writeByteArray(hdos2.toByteArray(), output);
hdos.close();
hdos2.close();
}
} else {
for (int i = 0; i < keyTypeValues.length; i++) {
writeValue(keyTypeValues, i, output);
}
}
clearDirty();
}
/**
* Reads deltas from the specified input stream.
*
* @param input
* The input stream.
* @throws IOException
* Thrown if an IO error encountered.
* @throws InvalidDeltaException
* Thrown if the received deltas cannot be properly applied.
*/
public void fromDelta(DataInput input) throws IOException, InvalidDeltaException
{
KeyType[] keyTypeValues = keyType.getValues();
int bitCount = keyTypeValues.length;
int dirtyFlagCount = dirtyFlags.length;
int dirtyFlagsToApply[] = new int[dirtyFlagCount];
for (int i = 0; i < dirtyFlagCount; i++) {
dirtyFlagsToApply[i] = input.readInt();
// CacheFactory.getAnyInstance().getLogger().info("dirty = " +
// dirtyFlagsToApply[i]);
}
try {
int count = BIT_MASK_SIZE; // int
for (int i = 0; i < dirtyFlagsToApply.length; i++) {
int dirty = dirtyFlagsToApply[i]; // received dirty
int userDirty = dirtyFlags[i]; // app dirty
if (i == dirtyFlagsToApply.length - 1) {
count = bitCount % BIT_MASK_SIZE;
if (count == 0 && bitCount != 0) {
count = BIT_MASK_SIZE;
}
}
// Compare both the current bit and the received bit.
// The app might be modifying the object. If so, keep the
// user modified data and discard the change received.
int startIndex = i * BIT_MASK_SIZE;
for (int j = 0; j < count; j++) {
if (isBitDirty(dirty, j)) {
int index = startIndex + j;
Object value = MapLiteSerializer.readObject(input);
// Set the new value only if the app has not set the
// value
if (isBitDirty(userDirty, j) == false) {
values[index] = value;
}
// CacheFactory.getAnyInstance().getLogger().info("bit set = "
// + j + ", index = " + index);
}
}
}
} catch (ClassNotFoundException ex) {
// ignore
}
}
/**
* Writes deltas to the specified output stream.
*
* @param output
* The output stream.
* @throws IOException
* Thrown if an IO error encountered.
*/
public void toDelta(DataOutput output) throws IOException
{
KeyType[] keyTypeValues = keyType.getValues();
int bitCount = keyTypeValues.length;
for (int i = 0; i < dirtyFlags.length; i++) {
output.writeInt(dirtyFlags[i]);
// System.out.println("dirty = " + dirtyFlags[i]);
}
int count = BIT_MASK_SIZE;
for (int i = 0; i < dirtyFlags.length; i++) {
int dirty = dirtyFlags[i];
if (isDirty(dirty) == false) {
continue;
}
if (i == dirtyFlags.length - 1) {
count = bitCount % BIT_MASK_SIZE;
if (count == 0 && bitCount != 0) {
count = BIT_MASK_SIZE;
}
}
int startIndex = i * BIT_MASK_SIZE;
for (int j = 0; j < count; j++) {
if (isBitDirty(dirty, j)) {
int index = startIndex + j;
MapLiteSerializer.writeObject(values[index], output);
}
}
}
clearDirty();
}
/**
* Returns the key type ID that is universally unique. This call is
* equivalent to getKeyType().getId()
.
*/
public Object getId()
{
if (keyType == null) {
return null;
}
return keyType.getId();
}
/**
* Returns the key type version. There are one or more key type versions per
* ID. This method call is equivalent to invoking
* getKeyType().getVersion()
.
*/
public int getKeyTypeVersion()
{
if (keyType == null) {
return 0;
}
return keyType.getVersion();
}
/**
* Returns the simple (short) class name of the key type. It returns null if
* the key type is not defined.
*/
public String getName()
{
if (keyType == null) {
return null;
}
return (String) keyType.getClass().getSimpleName();
}
/**
* Returns the fully qualified class name of the key type. It returns null
* if the key type is not defined.
*/
public String getKeyTypeName()
{
if (keyType == null) {
return null;
}
return (String) keyType.getClass().getSimpleName();
}
/**
* Returns all of the keys that map non-null values. It returns an empty set
* if this object has not been initialized, i.e., KeyType is undefined.
*/
public Set keySet()
{
TreeSet retSet = new TreeSet();
if (keyType != null) {
Set set = (Set) keyType.getNameSet();
for (String key : set) {
if (get(key) != null) {
retSet.add(key);
}
}
}
return retSet;
}
/**
* Returns the entire collection of non-null values.
*/
public Collection values()
{
ArrayList list = new ArrayList(values.length + 1);
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
list.add(values[i]);
}
}
return Collections.unmodifiableCollection(list);
}
/**
* Returns the (string key, value) paired entry set that contains only
* non-null values.
*/
public Set> entrySet()
{
if (keyType == null) {
return null;
}
HashMap map = new HashMap(keyType.getKeyCount() + 1, 1f);
for (KeyType ft : keyType.getValues()) {
Object value = get(ft);
if (value != null) {
map.put((String) ft.getName(), get(ft));
}
}
return Collections.unmodifiableMap(map).entrySet();
}
/**
* Clears the MapLite values. All non-null values are set to null and dirty.
*/
public void clear()
{
if (values != null) {
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
setDirty(i, dirtyFlags);
}
}
values = new Object[values.length];
}
}
/**
* Returns true if the specified key maps a non-null value. It uses
* key.toString() to search the key.
*
* @param key
* The key to check.
*/
public boolean containsKey(Object key)
{
if (keyType == null) {
return false;
}
return get(key) != null;
}
/**
* Returns true if the specified value exists in this object. It returns
* null if the specified value is null and the MapLite object contains one
* or more null values.
*
* @param value
* The value to search.
*/
public boolean containsValue(Object value)
{
if (keyType == null) {
return false;
}
for (int i = 0; i < values.length; i++) {
if (values[i] == value) {
return true;
}
}
return false;
}
/**
* Returns true if there are no values stored in this object. A null value
* is considered no value.
*/
public boolean isEmpty()
{
if (keyType == null || values == null) {
return true;
}
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
return false;
}
}
return false;
}
/**
* Puts all entries found in the specified map into this MapLite object. The
* specified map is handled based on its type as follows:
*
*
* - If the specified map is null then it is ignored.
*
* - If the specified map is MapLite and this object has not assigned
* KeyType then this method shallow-copies the entire map image into this
* object. As a result, MapLite object effectively becomes a clone of the
* specified map with all of the keys marked dirty.
*
* - If the specified map is MapLite and this object has the same KeyType
* as the map then the above bullet also applies.
*
* - If the specified map is Map or MapLite with a KeyType that is
* different from this object then this method shallow-copies only the valid
* keys and values. All invalid keys and values are ignored. The valid keys
* must have the same key names defined in this object's KeyType. Similarly,
* the valid values must have the same types defined in this object's
* KeyType. All valid keys are marked dirty.
*
*
* Note that the last bullet transforms any Map objects into MapLite
* objects.
*
* @param map
* Mappings to be stored in this MapLite object. If it is null
* then it is silently ignored.
*/
public void putAll(Map map)
{
if (map == null) {
return;
}
// if key type is not defined
if (keyType == null) {
if (map instanceof MapLite) {
MapLite ml = (MapLite) map;
if (ml.getKeyType() == null) {
return;
}
init(ml.getKeyType());
System.arraycopy(ml.values, 0, values, 0, values.length);
dirtyAllKeys();
return;
} else {
return;
}
}
// if key type is defined
if (map instanceof MapLite) {
MapLite ml = (MapLite) map;
if (keyType == ml.getKeyType()) {
System.arraycopy(ml.values, 0, values, 0, values.length);
dirtyAllKeys();
return;
}
}
// If Map or MapLite with a different KeyType - key must be string
Set> set = map.entrySet();
for (Map.Entry entry : set) {
KeyType keyType = this.keyType.getKeyType(entry.getKey());
if (keyType == null) {
continue;
}
if (entry.getValue() != null && keyType.getType() != entry.getValue().getClass()) {
continue;
}
put(keyType, entry.getValue());
}
}
/**
* Removes the specified key's value. This method removes only the value
* that the key maps to. The keys are never removed.
*
* @param key
* The key of the value to remove.
*/
public V remove(Object key)
{
if (keyType == null || key == null) {
return null;
}
KeyType keyType = this.keyType.getKeyType(key.toString());
if (keyType == null) {
return null;
}
V oldVal = (V) values[keyType.getIndex()];
if (oldVal != null) {
// if (this.keyType.isDeltaEnabled()) {
setDirty(keyType, dirtyFlags);
// }
}
// TODO: take care of initial values for primitives
values[keyType.getIndex()] = null;
return oldVal;
}
/**
* Returns the count of the non-null values.
*/
public int size()
{
if (keyType == null) {
return 0;
}
int count = 0;
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
count++;
}
}
return count;
}
/**
* Returns the count of all keys defined by the key type. This method call
* is equivalent to invoking
* {@link #getKeyType()}.getKeyCount()
. It returns 0 if MapLite
* is not initialized, i.e., key type is not defined.
*/
public int getKeyCount()
{
if (keyType == null) {
return 0;
}
return keyType.getKeyCount();
}
/**
* Clones this object by shallow-copying values. The returned object is the
* exact image of this object including deltas and serialized values.
*/
public Object clone()
{
if (keyType == null) {
return new MapLite();
}
MapLite ml = new MapLite(keyType);
System.arraycopy(values, 0, ml.values, 0, values.length);
System.arraycopy(dirtyFlags, 0, ml.dirtyFlags, 0, dirtyFlags.length);
if (serializedBytes != null) {
System.arraycopy(serializedBytes, 0, ml.serializedBytes, 0, serializedBytes.length);
}
return ml;
}
}