178 lines
4.8 KiB
Java
178 lines
4.8 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.reflect.cloning;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.net.Inet4Address;
|
|
import java.net.Inet6Address;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.URI;
|
|
import java.net.URL;
|
|
import java.security.PublicKey;
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.function.Supplier;
|
|
|
|
import javax.crypto.SecretKey;
|
|
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
import com.comphenix.protocol.utility.MinecraftVersion;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.primitives.Primitives;
|
|
|
|
/**
|
|
* Detects classes that are immutable, and thus doesn't require cloning.
|
|
* <p>
|
|
* This ought to have no false positives, but plenty of false negatives.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class ImmutableDetector implements Cloner {
|
|
// Notable immutable classes we might encounter
|
|
private static final Set<Class<?>> immutableClasses = ImmutableSet.of(
|
|
StackTraceElement.class, BigDecimal.class,
|
|
BigInteger.class, Locale.class, UUID.class,
|
|
URL.class, URI.class, Inet4Address.class,
|
|
Inet6Address.class, InetSocketAddress.class,
|
|
SecretKey.class, PublicKey.class
|
|
);
|
|
|
|
private static final Set<Class<?>> immutableNMS = Sets.newConcurrentHashSet();
|
|
|
|
static {
|
|
add(MinecraftReflection::getGameProfileClass);
|
|
add(MinecraftReflection::getDataWatcherSerializerClass);
|
|
add(MinecraftReflection::getBlockClass);
|
|
add(MinecraftReflection::getItemClass);
|
|
add("SoundEffect");
|
|
|
|
if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
|
|
add(MinecraftReflection::getFluidTypeClass);
|
|
add(MinecraftReflection::getParticleTypeClass);
|
|
add("Particle");
|
|
}
|
|
|
|
if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) {
|
|
add("EntityTypes");
|
|
add("VillagerType");
|
|
add("VillagerProfession");
|
|
}
|
|
|
|
// TODO automatically detect the technically-not-an-enum enums that Mojang is so fond of
|
|
// Would also probably go in tandem with having the FieldCloner use this
|
|
|
|
if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) {
|
|
add("IRegistry");
|
|
}
|
|
|
|
if (MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) {
|
|
add("ResourceKey");
|
|
}
|
|
}
|
|
|
|
private static void add(Supplier<Class<?>> getClass) {
|
|
try {
|
|
Class<?> clazz = getClass.get();
|
|
if (clazz != null) {
|
|
immutableNMS.add(clazz);
|
|
}
|
|
} catch (RuntimeException ignored) { }
|
|
}
|
|
|
|
private static void add(String className) {
|
|
try {
|
|
Class<?> clazz = MinecraftReflection.getMinecraftClass(className);
|
|
if (clazz != null) {
|
|
immutableNMS.add(clazz);
|
|
}
|
|
} catch (RuntimeException ignored) { }
|
|
}
|
|
|
|
@Override
|
|
public boolean canClone(Object source) {
|
|
// Don't accept NULL
|
|
if (source == null) {
|
|
return false;
|
|
}
|
|
|
|
return isImmutable(source.getClass());
|
|
}
|
|
|
|
/**
|
|
* Determine if the given type is probably immutable.
|
|
* @param type - the type to check.
|
|
* @return TRUE if the type is immutable, FALSE otherwise.
|
|
*/
|
|
public static boolean isImmutable(Class<?> type) {
|
|
// Cases that are definitely not true
|
|
if (type.isArray()) {
|
|
return false;
|
|
}
|
|
|
|
// All primitive types
|
|
if (Primitives.isWrapperType(type) || String.class.equals(type)) {
|
|
return true;
|
|
}
|
|
|
|
// May not be true, but if so, that kind of code is broken anyways
|
|
if (isEnumWorkaround(type)) {
|
|
return true;
|
|
}
|
|
|
|
// No good way to clone lambdas
|
|
if (type.getName().contains("$$Lambda$")) {
|
|
return true;
|
|
}
|
|
|
|
if (immutableClasses.contains(type)) {
|
|
return true;
|
|
}
|
|
|
|
for (Class<?> clazz : immutableNMS) {
|
|
if (MinecraftReflection.is(clazz, type)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Probably not
|
|
return false;
|
|
}
|
|
|
|
// This is just great. Just great.
|
|
private static boolean isEnumWorkaround(Class<?> enumClass) {
|
|
while (enumClass != null) {
|
|
if (enumClass.isEnum()) {
|
|
return true;
|
|
}
|
|
|
|
enumClass = enumClass.getSuperclass();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Object clone(Object source) {
|
|
// Safe if the class is immutable
|
|
return source;
|
|
}
|
|
}
|