258 lines
8.1 KiB
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();
|
|
}
|
|
}
|