ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java

258 lines
8.1 KiB
Java

/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.wrappers.nbt;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
import com.google.common.base.Objects;
import java.io.DataOutput;
import java.lang.reflect.Method;
/**
* Represents a wrapped NBT tag element, composite or not.
*
* @author Kristian
* @param <TType> - type of the value field.
*/
class WrappedElement<TType> implements NbtWrapper<TType> {
// For retrieving the current type ID
private static volatile Method methodGetTypeID;
// For handling cloning
private static volatile Method methodClone;
// Which name property to use
private static volatile Boolean hasNbtName;
// Structure modifiers for the different NBT elements
private static final StructureModifier<?>[] MODIFIERS = new StructureModifier<?>[NbtType.values().length];
// The underlying NBT object
private Object handle;
// Saved type
private NbtType type;
// Saved name
private NameProperty nameProperty;
/**
* Initialize a NBT wrapper for a generic element.
* @param handle - the NBT element to wrap.
*/
public WrappedElement(Object handle) {
this.handle = handle;
initializeProperty();
}
/**
* Initialize a NBT wrapper for a generic element.
* @param handle - the NBT element to wrap.
*/
public WrappedElement(Object handle, String name) {
this.handle = handle;
initializeProperty();
setName(name);
}
private void initializeProperty() {
if (nameProperty == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Determine if we have a NBT string field
if (hasNbtName == null) {
hasNbtName = NameProperty.hasStringIndex(base, 0);
}
// Now initialize the name property
if (hasNbtName)
this.nameProperty = NameProperty.fromStringIndex(base, handle, 0);
else
this.nameProperty = NameProperty.fromBean();
}
}
/**
* Retrieve a modifier (with no target) that is used to read and write the NBT value.
* @return The value modifier.
*/
protected StructureModifier<TType> getCurrentModifier() {
NbtType type = getType();
return getCurrentBaseModifier().withType(type.getValueType());
}
/**
* Get the object modifier (with no target) for the current underlying NBT object.
* @return The generic modifier.
*/
@SuppressWarnings("unchecked")
protected StructureModifier<Object> getCurrentBaseModifier() {
int index = getType().ordinal();
StructureModifier<Object> modifier = (StructureModifier<Object>) MODIFIERS[index];
// Double checked locking
if (modifier == null) {
synchronized (this) {
if (MODIFIERS[index] == null) {
MODIFIERS[index] = new StructureModifier<>(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false);
}
modifier = (StructureModifier<Object>) MODIFIERS[index];
}
}
return modifier;
}
@Override
public boolean accept(NbtVisitor visitor) {
return visitor.visit(this);
}
/**
* Retrieve the underlying NBT tag object.
* @return The underlying Minecraft tag object.
*/
@Override
public Object getHandle() {
return handle;
}
@Override
public NbtType getType() {
if (methodGetTypeID == null) {
// Use the base class
methodGetTypeID = FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass()).
getMethodByReturnTypeAndParameters("getTypeID", byte.class, new Class<?>[0]);
}
if (type == null) {
try {
type = NbtType.getTypeFromID((Byte) methodGetTypeID.invoke(handle));
} catch (Exception e) {
throw new FieldAccessException("Cannot get NBT type of " + handle, e);
}
}
return type;
}
/**
* Retrieve the sub element type of the underlying NMS NBT list.
* @return The NBT sub type.
*/
public NbtType getSubType() {
int subID = getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).read(0);
return NbtType.getTypeFromID(subID);
}
/**
* Set the sub element type of the underlying NMS NBT list.
* @param type - the new sub element type.
*/
public void setSubType(NbtType type) {
byte subID = (byte) type.getRawID();
getCurrentBaseModifier().<Byte>withType(byte.class).withTarget(handle).write(0, subID);
}
@Override
public String getName() {
return nameProperty.getName();
}
@Override
public void setName(String name) {
nameProperty.setName(name);
}
@Override
public TType getValue() {
return getCurrentModifier().withTarget(handle).read(0);
}
@Override
public void setValue(TType newValue) {
getCurrentModifier().withTarget(handle).write(0, newValue);
}
@Override
public void write(DataOutput destination) {
// No need to cache this object
NbtBinarySerializer.DEFAULT.serialize(this, destination);
}
@Override
public NbtBase<TType> deepClone() {
if (methodClone == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodClone = FuzzyReflection.fromClass(base).
getMethodByReturnTypeAndParameters("clone", base, new Class<?>[0]);
}
try {
return NbtFactory.fromNMS(methodClone.invoke(handle), getName());
} catch (Exception e) {
throw new FieldAccessException("Unable to clone " + handle, e);
}
}
@Override
public int hashCode() {
return Objects.hashCode(getName(), getType(), getValue());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NbtBase) {
NbtBase<?> other = (NbtBase<?>) obj;
// Make sure we're dealing with the same type
if (other.getType().equals(getType())) {
return Objects.equal(getValue(), other.getValue());
}
}
return false;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
String name = getName();
result.append("{");
if (name != null && name.length() > 0)
result.append("name: '").append(name).append("', ");
result.append("value: ");
// Wrap quotation marks
if (getType() == NbtType.TAG_STRING)
result.append("'").append(getValue()).append("'");
else
result.append(getValue());
result.append("}");
return result.toString();
}
}