/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.util.lang;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.concurrent.Callable;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;
/**
* Convenient adapter for working with metadata. <h2 class="header">Thread Safety</h2> This class provides necessary
* synchronization for thread-safe access.
*/
@SuppressWarnings({"SynchronizeOnNonFinalField"})
public class GridMetadataAwareAdapter {
/**
* Enum stored predefined keys.
*/
public enum EntryKey {//keys sorted by usage rate, descending.
/** Predefined key. */
CACHE_STORE_MANAGER_KEY(0),
/** Predefined key. */
CACHE_EVICTABLE_ENTRY_KEY(1),
/** Predefined key. */
CACHE_EVICTION_MANAGER_KEY(2);
/** key. */
private int key;
/**
* @param key key
*/
EntryKey(int key) {
this.key = key;
}
/**
* Returns key.
*
* @return key.
*/
public int key() {
return key;
}
}
/** Attributes. */
@GridToStringInclude(sensitive = true)
private Object[] data = null;
/**
* Copies all metadata from another instance.
*
* @param from Metadata aware instance to copy metadata from.
*/
public void copyMeta(GridMetadataAwareAdapter from) {
assert from != null;
copyMeta(from.allMeta());
}
/**
* Copies all metadata from given map.
*
* @param data Map to copy metadata from.
*/
public void copyMeta(Object[] data) {
assert data != null;
synchronized (this) {
if (this.data.length < data.length)
this.data = Arrays.copyOf(this.data, data.length);
for (int k = 0; k < data.length; k++) {
if (data[k] != null)
this.data[k] = data[k];
}
}
}
/**
* Adds a new metadata.
*
* @param key Metadata key.
* @param val Metadata value.
* @param <V> Type of the value.
* @return Metadata previously associated with given name, or {@code null} if there was none.
*/
@SuppressWarnings({"unchecked"})
@Nullable public <V> V addMeta(int key, V val) {
assert val != null;
synchronized (this) {
if (this.data == null)
this.data = new Object[key + 1];
else if (this.data.length <= key)
this.data = Arrays.copyOf(this.data, key + 1);
V old = (V)data[key];
data[key] = val;
return old;
}
}
/**
* Gets metadata by name.
*
* @param key Metadata key.
* @param <V> Type of the value.
* @return Metadata value or {@code null}.
*/
@SuppressWarnings({"unchecked"})
@Nullable public <V> V meta(int key) {
synchronized (this) {
return data != null && data.length > key ? (V)data[key] : null;
}
}
/**
* Removes metadata by key.
*
* @param key Name of the metadata to remove.
* @param <V> Type of the value.
* @return Value of removed metadata or {@code null}.
*/
@SuppressWarnings({"unchecked"})
@Nullable public <V> V removeMeta(int key) {
synchronized (this) {
if (data == null || data.length <= key)
return null;
V old = (V)data[key];
data[key] = null;
return old;
}
}
/**
* Removes metadata only if its current value is equal to {@code val} passed in.
*
* @param key Name of metadata attribute.
* @param val Value to compare.
* @param <V> Value type.
* @return {@code True} if value was removed, {@code false} otherwise.
*/
@SuppressWarnings({"unchecked"})
public <V> boolean removeMeta(int key, V val) {
assert val != null;
synchronized (this) {
if (data == null || data.length <= key)
return false;
V old = (V)data[key];
if (old != null && old.equals(val)) {
data[key] = null;
return true;
}
return false;
}
}
/**
* Gets all metadata in this entry.
*
* @param <V> Type of the value.
* @return All metadata in this entry.
*/
public <V> Object[] allMeta() {
Object[] cp;
synchronized (this) {
cp = Arrays.copyOf(data, data.length);
}
return cp;
}
/**
* Removes all meta.
*/
public void removeAllMeta() {
synchronized (this) {
data = null;
}
}
/**
* Tests whether or not given metadata is set.
*
* @param key key of the metadata to test.
* @return Whether or not given metadata is set.
*/
public boolean hasMeta(int key) {
return meta(key) != null;
}
/**
* Tests whether or not given metadata is set.
*
* @param key Key of the metadata to test.
* @return Whether or not given metadata is set.
*/
public <V> boolean hasMeta(int key, V val) {
Object v = meta(key);
return v != null && v.equals(val);
}
/**
* Adds given metadata value only if it was absent.
*
* @param key Metadata key.
* @param val Value to add if it's not attached already.
* @param <V> Type of the value.
* @return {@code null} if new value was put, or current value if put didn't happen.
*/
@SuppressWarnings({"unchecked"})
@Nullable public <V> V putMetaIfAbsent(int key, V val) {
assert val != null;
synchronized (this) {
V v = (V)meta(key);
if (v == null)
return addMeta(key, val);
return v;
}
}
/**
* Adds given metadata value only if it was absent. This method always returns the latest value and never previous
* one.
*
* @param key Metadata key.
* @param val Value to add if it's not attached already.
* @param <V> Type of the value.
* @return The value of the metadata after execution of this method.
*/
@SuppressWarnings({"unchecked"})
public <V> V addMetaIfAbsent(int key, V val) {
assert val != null;
synchronized (this) {
V v = (V)meta(key);
if (v == null)
addMeta(key, v = val);
return v;
}
}
/**
* Adds given metadata value only if it was absent.
*
* @param key Metadata key.
* @param c Factory closure to produce value to add if it's not attached already. Not that unlike {@link
* #addMeta(int, Object)} method the factory closure will not be called unless the value is required and therefore
* value will only be created when it is actually needed. If {@code null} and metadata value is missing - {@code
* null} will be returned from this method.
* @param <V> Type of the value.
* @return The value of the metadata after execution of this method.
*/
@SuppressWarnings({"unchecked"})
@Nullable public <V> V addMetaIfAbsent(int key, @Nullable Callable<V> c) {
assert c != null;
synchronized (this) {
V v = (V)meta(key);
if (v == null && c != null)
try {
addMeta(key, v = c.call());
}
catch (Exception e) {
throw F.wrap(e);
}
return v;
}
}
/**
* Replaces given metadata with new {@code newVal} value only if its current value is equal to {@code curVal}.
* Otherwise, it is no-op.
*
* @param key Key of the metadata.
* @param curVal Current value to check.
* @param newVal New value.
* @return {@code true} if replacement occurred, {@code false} otherwise.
*/
@SuppressWarnings({"RedundantTypeArguments"})
public <V> boolean replaceMeta(int key, V curVal, V newVal) {
assert newVal != null;
assert curVal != null;
synchronized (this) {
if (hasMeta(key)) {
V val = this.<V>meta(key);
if (val != null && val.equals(curVal)) {
addMeta(key, newVal);
return true;
}
}
return false;
}
}
/**
* Convenience way for super-classes which implement {@link Externalizable} to serialize metadata. Super-classes
* must call this method explicitly from within {@link Externalizable#writeExternal(ObjectOutput)} methods
* implementation.
*
* @param out Output to write to.
* @throws IOException If I/O error occurred.
*/
protected void writeExternalMeta(ObjectOutput out) throws IOException {
Object[] cp;
// Avoid code warning (suppressing is bad here, because we need this warning for other places).
synchronized (this) {
cp = Arrays.copyOf(this.data, this.data.length);
}
out.writeObject(cp);
}
/**
* Convenience way for super-classes which implement {@link Externalizable} to serialize metadata. Super-classes
* must call this method explicitly from within {@link Externalizable#readExternal(ObjectInput)} methods
* implementation.
*
* @param in Input to read from.
* @throws IOException If I/O error occurred.
* @throws ClassNotFoundException If some class could not be found.
*/
@SuppressWarnings({"unchecked"})
protected void readExternalMeta(ObjectInput in) throws IOException, ClassNotFoundException {
Object[] cp = (Object[])in.readObject();
synchronized (this) {
this.data = cp;
}
}
/** {@inheritDoc} */
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "OverriddenMethodCallDuringObjectConstruction"})
@Override public Object clone() {
try {
GridMetadataAwareAdapter clone = (GridMetadataAwareAdapter)super.clone();
clone.copyMeta(this);
return clone;
}
catch (CloneNotSupportedException ignore) {
throw new InternalError();
}
}
}