diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaReflection.java new file mode 100644 index 00000000..5a663ab6 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaReflection.java @@ -0,0 +1,316 @@ +package com.comphenix.protocol.wrappers; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.wrappers.collection.ConvertedSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; + +/** + * Wrap multimap and multiset from another version of Guava by using reflection. + *

+ * This is often the last resort, if we can't remap manually. + * @author Kristian + */ +class GuavaReflection { + /** + * Wrap a Bukkit multimap around Minecraft's internal multimap. + * @param multimap - the multimap to wrap. + * @return The Bukkit multimap. + */ + public static Multimap getBukkitMultimap( + final Object multimap) { + return new Multimap() { + private Class multimapClass = multimap.getClass(); + private MethodAccessor methodAsMap = Accessors.getMethodAccessor(multimapClass, "asMap"); + private MethodAccessor methodClear = Accessors.getMethodAccessor(multimapClass, "clear"); + private MethodAccessor methodContainsEntry = Accessors.getMethodAccessor(multimapClass, "containsEntry",Object.class, Object.class); + private MethodAccessor methodContainsKey = Accessors.getMethodAccessor(multimapClass, "containsKey", Object.class); + private MethodAccessor methodContainsValue = Accessors.getMethodAccessor(multimapClass, "containsValue", Object.class); + private MethodAccessor methodEntries = Accessors.getMethodAccessor(multimapClass, "entries"); + private MethodAccessor methodGet = Accessors.getMethodAccessor(multimapClass, "get", Object.class); + private MethodAccessor methodIsEmpty = Accessors.getMethodAccessor(multimapClass, "isEmpty"); + private MethodAccessor methodKeySet = Accessors.getMethodAccessor(multimapClass, "keySet"); + private MethodAccessor methodKeys = Accessors.getMethodAccessor(multimapClass, "keys"); + private MethodAccessor methodPut = Accessors.getMethodAccessor(multimapClass, "put", Object.class, Object.class); + private MethodAccessor methodPutAll = Accessors.getMethodAccessor(multimapClass, "putAll", Object.class, Iterable.class); + private MethodAccessor methodRemove = Accessors.getMethodAccessor(multimapClass, "remove", Object.class, Object.class); + private MethodAccessor methodRemoveAll = Accessors.getMethodAccessor(multimapClass, "removeAll", Object.class); + private MethodAccessor methodReplaceValues = Accessors.getMethodAccessor(multimapClass, "replaceValues", Object.class, Iterable.class); + private MethodAccessor methodSize = Accessors.getMethodAccessor(multimapClass, "size"); + private MethodAccessor methodValues = Accessors.getMethodAccessor(multimapClass, "values"); + + @SuppressWarnings("unchecked") + public Map> asMap() { + return (Map>) methodAsMap.invoke(multimap); + } + + public void clear() { + methodClear.invoke(multimap); + } + + public boolean containsEntry(Object arg0, Object arg1) { + return (Boolean) methodContainsEntry.invoke(multimap, arg0, arg1); + } + + public boolean containsKey(Object arg0) { + return (Boolean) methodContainsKey.invoke(multimap, arg0); + } + + public boolean containsValue(Object arg0) { + return (Boolean) methodContainsValue.invoke(multimap, arg0); + } + + @SuppressWarnings("unchecked") + public Collection> entries() { + return (Collection>) methodEntries.invoke(multimap); + } + + public boolean equals(Object arg0) { + return multimap.equals(arg0); + } + + public int hashCode() { + return multimap.hashCode(); + } + + @Override + public String toString() { + return multimap.toString(); + } + + @SuppressWarnings("unchecked") + public Collection get(TKey arg0) { + return (Collection) methodGet.invoke(multimap, arg0); + } + + public boolean isEmpty() { + return (Boolean) methodIsEmpty.invoke(multimap); + } + + @SuppressWarnings("unchecked") + public Set keySet() { + return (Set) methodKeySet.invoke(multimap); + } + + public Multiset keys() { + return getBukkitMultiset(methodKeys.invoke(multimap)); + } + + public boolean put(TKey arg0, TValue arg1) { + return (Boolean) methodPut.invoke(multimap, arg0, arg1); + } + + public boolean putAll(com.google.common.collect.Multimap arg0) { + boolean result = false; + + // Add each entry + for (Entry entry : arg0.entries()) { + result |= (Boolean) methodPut.invoke(multimap, entry.getKey(), entry.getValue()); + } + return result; + } + + public boolean putAll(TKey arg0, Iterable arg1) { + return (Boolean) methodPutAll.invoke(arg0, arg1); + } + + public boolean remove(Object arg0, Object arg1) { + return (Boolean) methodRemove.invoke(multimap, arg0, arg1); + } + + @SuppressWarnings("unchecked") + public Collection removeAll(Object arg0) { + return (Collection) methodRemoveAll.invoke(multimap, arg0); + } + + @SuppressWarnings("unchecked") + public Collection replaceValues(TKey arg0, Iterable arg1) { + return (Collection) methodReplaceValues.invoke(multimap, arg0, arg1); + } + + public int size() { + return (Integer) methodSize.invoke(multimap); + } + + @SuppressWarnings("unchecked") + public Collection values() { + return (Collection) methodValues.invoke(multimap); + } + }; + } + + public static Multiset getBukkitMultiset(final Object multiset) { + return new Multiset() { + private Class multisetClass = multiset.getClass(); + private MethodAccessor methodAddMany = Accessors.getMethodAccessor(multisetClass, "add", Object.class, int.class); + private MethodAccessor methodAddOne = Accessors.getMethodAccessor(multisetClass, "add", Object.class); + private MethodAccessor methodAddAll = Accessors.getMethodAccessor(multisetClass, "addAll", Collection.class); + private MethodAccessor methodClear = Accessors.getMethodAccessor(multisetClass, "clear"); + private MethodAccessor methodContains = Accessors.getMethodAccessor(multisetClass, "contains", Object.class); + private MethodAccessor methodContainsAll = Accessors.getMethodAccessor(multisetClass, "containsAll", Collection.class); + private MethodAccessor methodCount = Accessors.getMethodAccessor(multisetClass, "count", Object.class); + private MethodAccessor methodElementSet = Accessors.getMethodAccessor(multisetClass, "elementSet"); + private MethodAccessor methodEntrySet = Accessors.getMethodAccessor(multisetClass, "entrySet"); + private MethodAccessor methodIsEmpty = Accessors.getMethodAccessor(multisetClass, "isEmpty"); + private MethodAccessor methodIterator = Accessors.getMethodAccessor(multisetClass, "iterator"); + private MethodAccessor methodRemoveCount = Accessors.getMethodAccessor(multisetClass, "remove", Object.class, int.class); + private MethodAccessor methodRemoveOne = Accessors.getMethodAccessor(multisetClass, "remove", Object.class); + private MethodAccessor methodRemoveAll = Accessors.getMethodAccessor(multisetClass, "removeAll", Collection.class); + private MethodAccessor methodRetainAll = Accessors.getMethodAccessor(multisetClass, "retainAll", Collection.class); + private MethodAccessor methodSetCountOldNew = Accessors.getMethodAccessor(multisetClass, "setCount", Object.class, int.class, int.class); + private MethodAccessor methodSetCountNew = Accessors.getMethodAccessor(multisetClass, "setCount", Object.class, int.class); + private MethodAccessor methodSize = Accessors.getMethodAccessor(multisetClass, "size"); + private MethodAccessor methodToArray = Accessors.getMethodAccessor(multisetClass, "toArray"); + private MethodAccessor methodToArrayBuffer = Accessors.getMethodAccessor(multisetClass, "toArray", Object[].class); + + public int add(TValue arg0, int arg1) { + return (Integer) methodAddMany.invoke(multiset, arg0, arg1); + } + + public boolean add(TValue arg0) { + return (Boolean) methodAddOne.invoke(multiset, arg0); + } + + public boolean addAll(Collection c) { + return (Boolean) methodAddAll.invoke(multiset, c); + } + + public void clear() { + methodClear.invoke(multiset); + } + + public boolean contains(Object arg0) { + return (Boolean) methodContains.invoke(multiset, arg0); + } + + public boolean containsAll(Collection arg0) { + return (Boolean) methodContainsAll.invoke(multiset, arg0); + } + + public int count(Object arg0) { + return (Integer) methodCount.invoke(multiset, arg0); + } + + @SuppressWarnings("unchecked") + public Set elementSet() { + return (Set) methodElementSet.invoke(multiset); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public Set> entrySet() { + return new ConvertedSet< + Object, + Multiset.Entry> + ((Set) methodEntrySet.invoke(multiset)) { + + @Override + protected com.google.common.collect.Multiset.Entry toOuter( + Object inner) { + return getBukkitEntry(inner); + } + + @Override + protected Object toInner( + com.google.common.collect.Multiset.Entry outer) { + throw new UnsupportedOperationException("Cannot convert " + outer); + } + }; + } + + public boolean equals(Object arg0) { + return multiset.equals(arg0); + } + + public int hashCode() { + return multiset.hashCode(); + } + + public boolean isEmpty() { + return (Boolean) methodIsEmpty.invoke(multiset); + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + return (Iterator) methodIterator.invoke(multiset); + } + + public int remove(Object arg0, int arg1) { + return (Integer) methodRemoveCount.invoke(multiset, arg0, arg1); + } + + public boolean remove(Object arg0) { + return (Boolean) methodRemoveOne.invoke(multiset, arg0); + } + + public boolean removeAll(Collection arg0) { + return (Boolean) methodRemoveAll.invoke(multiset, arg0); + } + + public boolean retainAll(Collection arg0) { + return (Boolean) methodRetainAll.invoke(multiset, arg0); + } + + public boolean setCount(TValue arg0, int arg1, int arg2) { + return (Boolean) methodSetCountOldNew.invoke(multiset, arg0, arg1, arg2); + } + + public int setCount(TValue arg0, int arg1) { + return (Integer) methodSetCountNew.invoke(multiset, arg0, arg1); + } + + public int size() { + return (Integer) methodSize.invoke(multiset); + } + + public Object[] toArray() { + return (Object[]) methodToArray.invoke(multiset); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + return (T[]) methodToArrayBuffer.invoke(multiset, a); + } + + public String toString() { + return multiset.toString(); + } + }; + } + + private static Multiset.Entry getBukkitEntry(final Object entry) { + return new Multiset.Entry() { + private Class entryClass = entry.getClass(); + private MethodAccessor methodEquals = Accessors.getMethodAccessor(entryClass, "equals", Object.class); + private MethodAccessor methodGetCount = Accessors.getMethodAccessor(entryClass, "getCount"); + private MethodAccessor methodGetElement = Accessors.getMethodAccessor(entryClass, "getElement"); + + public boolean equals(Object arg0) { + return (Boolean) methodEquals.invoke(entry, arg0); + } + + public int getCount() { + return (Integer) methodGetCount.invoke(entry); + } + + @SuppressWarnings("unchecked") + public TValue getElement() { + return (TValue) methodGetElement.invoke(entry); + } + + public int hashCode() { + return entry.hashCode(); + } + + public String toString() { + return entry.toString(); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaWrappers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaWrappers.java index 29df7a35..78d45409 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaWrappers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/GuavaWrappers.java @@ -15,6 +15,8 @@ import com.google.common.collect.Multiset; * @author Kristian */ class GuavaWrappers { + private static volatile boolean USE_REFLECTION_FALLBACK = false; + /** * Wrap a Bukkit multimap around Minecraft's internal multimap. * @param multimap - the multimap to wrap. @@ -22,7 +24,12 @@ class GuavaWrappers { */ public static Multimap getBukkitMultimap( final net.minecraft.util.com.google.common.collect.Multimap multimap) { - return new Multimap() { + + if (USE_REFLECTION_FALLBACK) { + return GuavaReflection.getBukkitMultimap(multimap); + } + + Multimap result = new Multimap() { public Map> asMap() { return multimap.asMap(); } @@ -109,12 +116,23 @@ class GuavaWrappers { return multimap.values(); } }; + + try { + result.size(); // Test + return result; + } catch (LinkageError e) { + // Occurs on Cauldron 1.7.10 + USE_REFLECTION_FALLBACK = true; + return GuavaReflection.getBukkitMultimap(multimap); + } } - public static Multiset getBukkitMultiset(net.minecraft.util.com.google.common.collect.Multiset multiset2) { - return new Multiset() { - private net.minecraft.util.com.google.common.collect.Multiset multiset; - + public static Multiset getBukkitMultiset(final net.minecraft.util.com.google.common.collect.Multiset multiset) { + if (USE_REFLECTION_FALLBACK) { + return GuavaReflection.getBukkitMultiset(multiset); + } + + Multiset result = new Multiset() { public int add(TValue arg0, int arg1) { return multiset.add(arg0, arg1); } @@ -223,6 +241,14 @@ class GuavaWrappers { return multiset.toString(); } }; + + try { + result.size(); // Test + return result; + } catch (LinkageError e) { + USE_REFLECTION_FALLBACK = true; + return GuavaReflection.getBukkitMultiset(multiset); + } } private static Multiset.Entry getBukkitEntry(final net.minecraft.util.com.google.common.collect.Multiset.Entry entry) {