Archiviert
13
0

Added support for retrieving the signed property map of a profile.

In addition, we also allow WrappedGameProfile to be retrieved from a
player.
Dieser Commit ist enthalten in:
Kristian S. Stangeland 2014-04-21 15:51:38 +02:00
Ursprung ce8d115b5a
Commit 5185442f35
8 geänderte Dateien mit 793 neuen und 58 gelöschten Zeilen

Datei anzeigen

@ -573,6 +573,15 @@ public class MinecraftReflection {
} }
} }
/**
* Retrieve the EntityHuman class.
* @return The entity human class.
*/
public static Class<?> getEntityHumanClass() {
// Assume its the direct superclass
return getEntityPlayerClass().getSuperclass();
}
/** /**
* Retrieve the GameProfile class in 1.7.2 and later. * Retrieve the GameProfile class in 1.7.2 and later.
* @return The game profile class. * @return The game profile class.

Datei anzeigen

@ -0,0 +1,251 @@
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.wrappers.collection.ConvertedSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
/**
* Represents wrappers for Minecraft's own version of Guava.
* @author Kristian
*/
class GuavaWrappers {
/**
* Wrap a Bukkit multimap around Minecraft's internal multimap.
* @param multimap - the multimap to wrap.
* @return The Bukkit multimap.
*/
public static <TKey, TValue> Multimap<TKey, TValue> getBukkitMultimap(
final net.minecraft.util.com.google.common.collect.Multimap<TKey, TValue> multimap) {
return new Multimap<TKey, TValue>() {
public Map<TKey, Collection<TValue>> asMap() {
return multimap.asMap();
}
public void clear() {
multimap.clear();
}
public boolean containsEntry(Object arg0, Object arg1) {
return multimap.containsEntry(arg0, arg1);
}
public boolean containsKey(Object arg0) {
return multimap.containsKey(arg0);
}
public boolean containsValue(Object arg0) {
return multimap.containsValue(arg0);
}
public Collection<Entry<TKey, TValue>> entries() {
return multimap.entries();
}
public boolean equals(Object arg0) {
return multimap.equals(arg0);
}
public Collection<TValue> get(TKey arg0) {
return multimap.get(arg0);
}
public int hashCode() {
return multimap.hashCode();
}
public boolean isEmpty() {
return multimap.isEmpty();
}
public Set<TKey> keySet() {
return multimap.keySet();
}
public Multiset<TKey> keys() {
return getBukkitMultiset(multimap.keys());
}
public boolean put(TKey arg0, TValue arg1) {
return multimap.put(arg0, arg1);
}
public boolean putAll(com.google.common.collect.Multimap<? extends TKey, ? extends TValue> arg0) {
boolean result = false;
// Add each entry
for (Entry<? extends TKey, ? extends TValue> entry : arg0.entries()) {
result |= multimap.put(entry.getKey(), entry.getValue());
}
return result;
}
public boolean putAll(TKey arg0, Iterable<? extends TValue> arg1) {
return multimap.putAll(arg0, arg1);
}
public boolean remove(Object arg0, Object arg1) {
return multimap.remove(arg0, arg1);
}
public Collection<TValue> removeAll(Object arg0) {
return multimap.removeAll(arg0);
}
public Collection<TValue> replaceValues(TKey arg0, Iterable<? extends TValue> arg1) {
return multimap.replaceValues(arg0, arg1);
}
public int size() {
return multimap.size();
}
public Collection<TValue> values() {
return multimap.values();
}
};
}
public static <TValue> Multiset<TValue> getBukkitMultiset(net.minecraft.util.com.google.common.collect.Multiset<TValue> multiset2) {
return new Multiset<TValue>() {
private net.minecraft.util.com.google.common.collect.Multiset<TValue> multiset;
public int add(TValue arg0, int arg1) {
return multiset.add(arg0, arg1);
}
public boolean add(TValue arg0) {
return multiset.add(arg0);
}
public boolean addAll(Collection<? extends TValue> c) {
return multiset.addAll(c);
}
public void clear() {
multiset.clear();
}
public boolean contains(Object arg0) {
return multiset.contains(arg0);
}
public boolean containsAll(Collection<?> arg0) {
return multiset.containsAll(arg0);
}
public int count(Object arg0) {
return multiset.count(arg0);
}
public Set<TValue> elementSet() {
return multiset.elementSet();
}
public Set<Multiset.Entry<TValue>> entrySet() {
return new ConvertedSet<
net.minecraft.util.com.google.common.collect.Multiset.Entry<TValue>,
Multiset.Entry<TValue>>
(multiset.entrySet()) {
@Override
protected com.google.common.collect.Multiset.Entry<TValue> toOuter(
net.minecraft.util.com.google.common.collect.Multiset.Entry<TValue> inner) {
return getBukkitEntry(inner);
}
@Override
protected net.minecraft.util.com.google.common.collect.Multiset.Entry<TValue> toInner(
com.google.common.collect.Multiset.Entry<TValue> 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 multiset.isEmpty();
}
public Iterator<TValue> iterator() {
return multiset.iterator();
}
public int remove(Object arg0, int arg1) {
return multiset.remove(arg0, arg1);
}
public boolean remove(Object arg0) {
return multiset.remove(arg0);
}
public boolean removeAll(Collection<?> arg0) {
return multiset.removeAll(arg0);
}
public boolean retainAll(Collection<?> arg0) {
return multiset.retainAll(arg0);
}
public boolean setCount(TValue arg0, int arg1, int arg2) {
return multiset.setCount(arg0, arg1, arg2);
}
public int setCount(TValue arg0, int arg1) {
return multiset.setCount(arg0, arg1);
}
public int size() {
return multiset.size();
}
public Object[] toArray() {
return multiset.toArray();
}
public <T> T[] toArray(T[] a) {
return multiset.toArray(a);
}
public String toString() {
return multiset.toString();
}
};
}
private static <TValue> Multiset.Entry<TValue> getBukkitEntry(final net.minecraft.util.com.google.common.collect.Multiset.Entry<TValue> entry) {
return new Multiset.Entry<TValue>() {
public boolean equals(Object arg0) {
return entry.equals(arg0);
}
public int getCount() {
return entry.getCount();
}
public TValue getElement() {
return entry.getElement();
}
public int hashCode() {
return entry.hashCode();
}
public String toString() {
return entry.toString();
}
};
}
}

Datei anzeigen

@ -3,13 +3,19 @@ package com.comphenix.protocol.wrappers;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMultimap;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.Multimap;
import net.minecraft.util.com.mojang.authlib.GameProfile; import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.com.mojang.authlib.properties.Property;
/** /**
* Represents a wrapper for a game profile. * Represents a wrapper for a game profile.
@ -20,12 +26,34 @@ public class WrappedGameProfile extends AbstractWrapper {
private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(GameProfile.class, String.class, String.class); private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(GameProfile.class, String.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(GameProfile.class, "id", String.class); private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(GameProfile.class, "id", String.class);
// Fetching game profile
private static FieldAccessor GET_PROFILE;
// Property map
private Multimap<String, WrappedSignedProperty> propertyMap;
// Profile from a handle // Profile from a handle
private WrappedGameProfile(Object profile) { private WrappedGameProfile(Object profile) {
super(GameProfile.class); super(GameProfile.class);
setHandle(profile); setHandle(profile);
} }
/**
* Retrieve the associated game profile of a player.
* @param player - the player.
* @return The game profile.
*/
public static WrappedGameProfile fromPlayer(Player player) {
FieldAccessor accessor = GET_PROFILE;
Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player);
if (accessor == null) {
accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GameProfile.class, true);
GET_PROFILE = accessor;
}
return WrappedGameProfile.fromHandle(GET_PROFILE.get(nmsPlayer));
}
/** /**
* Construct a new game profile with the given properties. * Construct a new game profile with the given properties.
* <p> * <p>
@ -110,6 +138,39 @@ public class WrappedGameProfile extends AbstractWrapper {
return getProfile().getName(); return getProfile().getName();
} }
/**
* Retrieve the property map of signed values.
* @return Property map.
*/
public Multimap<String, WrappedSignedProperty> getProperties() {
Multimap<String, WrappedSignedProperty> result = propertyMap;
if (result == null) {
result = new ConvertedMultimap<String, Property, WrappedSignedProperty>(
GuavaWrappers.getBukkitMultimap(getProfile().getProperties())) {
@Override
protected Property toInner(WrappedSignedProperty outer) {
return (Property) outer.handle;
}
@Override
protected Object toInnerObject(Object outer) {
if (outer instanceof WrappedSignedProperty) {
return toInner((WrappedSignedProperty) outer);
}
return outer;
}
@Override
protected WrappedSignedProperty toOuter(Property inner) {
return WrappedSignedProperty.fromHandle(inner);
}
};
propertyMap = result;
}
return result;
}
/** /**
* Retrieve the underlying GameProfile. * Retrieve the underlying GameProfile.
* @return The GameProfile. * @return The GameProfile.

Datei anzeigen

@ -0,0 +1,106 @@
package com.comphenix.protocol.wrappers;
import java.security.PublicKey;
import net.minecraft.util.com.mojang.authlib.properties.Property;
import com.google.common.base.Objects;
/**
* Represents a wrapper over a signed property.
* @author Kristian
*/
public class WrappedSignedProperty extends AbstractWrapper {
/**
* Construct a new wrapped signed property from a given handle.
* @param handle - the handle.
*/
private WrappedSignedProperty(Object handle) {
super(Property.class);
setHandle(handle);
}
/**
* Construct a new signed property from a given NMS property.
* @param handle - the property.
* @return The wrapped signed property.
*/
public static WrappedSignedProperty fromHandle(Object handle) {
return new WrappedSignedProperty(handle);
}
/**
* Retrieve the underlying signed property.
* @return The GameProfile.
*/
private Property getProfile() {
return (Property) handle;
}
/**
* Retrieve the name of the underlying property, such as "textures".
* @return Name of the property.
*/
public String getName() {
return getProfile().getName();
}
/**
* Retrieve the signature of the property (base64) as returned by the session server's /hasJoined.
* @return The signature of the property.
*/
public String getSignature() {
return getProfile().getSignature();
}
/**
* Retrieve the value of the property (base64) as return by the session server's /hasJoined
* @return The value of the property.
*/
public String getValue() {
return getProfile().getValue();
}
/**
* Determine if this property has a signature.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasSignature() {
return getProfile().hasSignature();
}
/**
* Determine if the signature of this property is valid and signed by the corresponding private key.
* @param key - the public key.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isSignatureValid(PublicKey key) {
return getProfile().isSignatureValid(key);
}
@Override
public int hashCode() {
return Objects.hashCode(getName(), getValue(), getSignature());
}
@Override
public boolean equals(Object object){
if (object instanceof WrappedSignedProperty) {
if (!super.equals(object))
return false;
WrappedSignedProperty that = (WrappedSignedProperty) object;
return Objects.equal(this.getName(), that.getName())
&& Objects.equal(this.getValue(), that.getValue())
&& Objects.equal(this.getSignature(), that.getSignature());
}
return false;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("name", getName())
.add("value", getValue())
.add("signature", getSignature())
.toString();
}
}

Datei anzeigen

@ -30,6 +30,22 @@ import com.google.common.base.Function;
* @param <VOuter> - the second type. * @param <VOuter> - the second type.
*/ */
public abstract class AbstractConverted<VInner, VOuter> { public abstract class AbstractConverted<VInner, VOuter> {
// Inner conversion
private Function<VOuter, VInner> innerConverter = new Function<VOuter, VInner>() {
@Override
public VInner apply(@Nullable VOuter param) {
return toInner(param);
}
};
// Outer conversion
private Function<VInner, VOuter> outerConverter = new Function<VInner, VOuter>() {
@Override
public VOuter apply(@Nullable VInner param) {
return toOuter(param);
}
};
/** /**
* Convert a value from the inner map to the outer visible map. * Convert a value from the inner map to the outer visible map.
* @param inner - the inner value. * @param inner - the inner value.
@ -44,16 +60,19 @@ public abstract class AbstractConverted<VInner, VOuter> {
*/ */
protected abstract VInner toInner(VOuter outer); protected abstract VInner toInner(VOuter outer);
/**
* Retrieve a function delegate that converts outer objects to inner objects.
* @return A function delegate.
*/
protected Function<VOuter, VInner> getInnerConverter() {
return innerConverter;
}
/** /**
* Retrieve a function delegate that converts inner objects to outer objects. * Retrieve a function delegate that converts inner objects to outer objects.
* @return A function delegate. * @return A function delegate.
*/ */
protected Function<VInner, VOuter> getOuterConverter() { protected Function<VInner, VOuter> getOuterConverter() {
return new Function<VInner, VOuter>() { return outerConverter;
@Override
public VOuter apply(@Nullable VInner param) {
return toOuter(param);
}
};
} }
} }

Datei anzeigen

@ -0,0 +1,12 @@
package com.comphenix.protocol.wrappers.collection;
/**
* Represents a function that accepts two parameters.
* @author Kristian
* @param <T1> - type of the first parameter.
* @param <T2> - type of the second parameter.
* @param <TResult> - type of the return value.
*/
public interface BiFunction<T1, T2, TResult> {
public TResult apply(T1 arg1, T2 arg2);
}

Datei anzeigen

@ -39,6 +39,22 @@ public abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverte
// Inner map // Inner map
private Map<Key, VInner> inner; private Map<Key, VInner> inner;
// Inner conversion
private BiFunction<Key, VOuter, VInner> innerConverter = new BiFunction<Key, VOuter, VInner>() {
@Override
public VInner apply(Key key, VOuter outer) {
return toInner(key, outer);
}
};
// Outer conversion
private BiFunction<Key, VInner, VOuter> outerConverter = new BiFunction<Key, VInner, VOuter>() {
@Override
public VOuter apply(Key key, VInner inner) {
return toOuter(key, inner);
}
};
public ConvertedMap(Map<Key, VInner> inner) { public ConvertedMap(Map<Key, VInner> inner) {
if (inner == null) if (inner == null)
throw new IllegalArgumentException("Inner map cannot be NULL."); throw new IllegalArgumentException("Inner map cannot be NULL.");
@ -63,58 +79,7 @@ public abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverte
@Override @Override
public Set<Entry<Key, VOuter>> entrySet() { public Set<Entry<Key, VOuter>> entrySet() {
return new ConvertedSet<Entry<Key,VInner>, Entry<Key,VOuter>>(inner.entrySet()) { return convertedEntrySet(inner.entrySet(), innerConverter, outerConverter);
@Override
protected Entry<Key, VInner> toInner(final Entry<Key, VOuter> outer) {
return new Entry<Key, VInner>() {
@Override
public Key getKey() {
return outer.getKey();
}
@Override
public VInner getValue() {
return ConvertedMap.this.toInner(outer.getValue());
}
@Override
public VInner setValue(VInner value) {
return ConvertedMap.this.toInner(outer.setValue(ConvertedMap.this.toOuter(value)));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
@Override
protected Entry<Key, VOuter> toOuter(final Entry<Key, VInner> inner) {
return new Entry<Key, VOuter>() {
@Override
public Key getKey() {
return inner.getKey();
}
@Override
public VOuter getValue() {
return ConvertedMap.this.toOuter(inner.getKey(), inner.getValue());
}
@Override
public VOuter setValue(VOuter value) {
final VInner converted = ConvertedMap.this.toInner(getKey(), value);
return ConvertedMap.this.toOuter(getKey(), inner.setValue(converted));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
};
} }
/** /**
@ -215,4 +180,70 @@ public abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverte
sb.append(", "); sb.append(", ");
} }
} }
/**
* Convert a collection of entries.
* @param entries - the collection of entries.
* @param innerFunction - the inner entry converter.
* @param outerFunction - the outer entry converter.
* @return The converted set of entries.
*/
static <Key, VInner, VOuter> Set<Entry<Key, VOuter>> convertedEntrySet(
final Collection<Entry<Key, VInner>> entries,
final BiFunction<Key, VOuter, VInner> innerFunction,
final BiFunction<Key, VInner, VOuter> outerFunction) {
return new ConvertedSet<Entry<Key,VInner>, Entry<Key,VOuter>>(entries) {
@Override
protected Entry<Key, VInner> toInner(final Entry<Key, VOuter> outer) {
return new Entry<Key, VInner>() {
@Override
public Key getKey() {
return outer.getKey();
}
@Override
public VInner getValue() {
return innerFunction.apply(getKey(), outer.getValue());
}
@Override
public VInner setValue(VInner value) {
return innerFunction.apply(getKey(), outer.setValue(outerFunction.apply(getKey(), value)));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
@Override
protected Entry<Key, VOuter> toOuter(final Entry<Key, VInner> inner) {
return new Entry<Key, VOuter>() {
@Override
public Key getKey() {
return inner.getKey();
}
@Override
public VOuter getValue() {
return outerFunction.apply(getKey(), inner.getValue());
}
@Override
public VOuter setValue(VOuter value) {
final VInner converted = innerFunction.apply(getKey(), value);
return outerFunction.apply(getKey(), inner.setValue(converted));
}
@Override
public String toString() {
return String.format("\"%s\": %s", getKey(), getValue());
}
};
}
};
}
} }

Datei anzeigen

@ -0,0 +1,246 @@
package com.comphenix.protocol.wrappers.collection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
/**
* Represents a multimap that wraps another multimap by transforming the entries that are going in and out.
* @author Kristian
*
* @param <Key> - the key.
* @param <VInner> - the inner value type.
* @param <VOuter> - the outer value type.
*/
public abstract class ConvertedMultimap<Key, VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Multimap<Key, VOuter> {
// Inner multimap
private Multimap<Key, VInner> inner;
public ConvertedMultimap(Multimap<Key, VInner> inner) {
this.inner = Preconditions.checkNotNull(inner, "inner map cannot be NULL.");
}
/**
* Wrap a given collection.
* @param inner - the inner collection.
* @return The outer collection.
*/
protected Collection<VOuter> toOuterCollection(Collection<VInner> inner) {
return new ConvertedCollection<VInner, VOuter>(inner) {
@Override
protected VInner toInner(VOuter outer) {
return ConvertedMultimap.this.toInner(outer);
}
@Override
protected VOuter toOuter(VInner inner) {
return ConvertedMultimap.this.toOuter(inner);
}
@Override
public String toString() {
return "[" + Joiner.on(", ").join(this) + "]";
}
};
}
/**
* Wrap a given collection.
* @param outer - the outer collection.
* @return The inner collection.
*/
protected Collection<VInner> toInnerCollection(Collection<VOuter> outer) {
return new ConvertedCollection<VOuter, VInner>(outer) {
@Override
protected VOuter toInner(VInner outer) {
return ConvertedMultimap.this.toOuter(outer);
}
@Override
protected VInner toOuter(VOuter inner) {
return ConvertedMultimap.this.toInner(inner);
}
@Override
public String toString() {
return "[" + Joiner.on(", ").join(this) + "]";
}
};
}
/**
* Convert to an inner object if its of the correct type, otherwise leave it.
* @param outer - the outer object.
* @return The inner object, or the same object.
*/
@SuppressWarnings("unchecked")
protected Object toInnerObject(Object outer) {
return toInner((VOuter) outer);
}
@Override
public int size() {
return inner.size();
}
@Override
public boolean isEmpty() {
return inner.isEmpty();
}
@Override
public boolean containsKey(@Nullable Object key) {
return inner.containsKey(key);
}
@Override
public boolean containsValue(@Nullable Object value) {
return inner.containsValue(toInnerObject(value));
}
@Override
public boolean containsEntry(@Nullable Object key, @Nullable Object value) {
return inner.containsEntry(key, toInnerObject(value));
}
@Override
public boolean put(@Nullable Key key, @Nullable VOuter value) {
return inner.put(key, toInner(value));
}
@Override
public boolean remove(@Nullable Object key, @Nullable Object value) {
return inner.remove(key, toInnerObject(value));
}
@Override
public boolean putAll(@Nullable Key key, Iterable<? extends VOuter> values) {
return inner.putAll(key, Iterables.transform(values, getInnerConverter()));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public boolean putAll(Multimap<? extends Key, ? extends VOuter> multimap) {
return inner.putAll(new ConvertedMultimap<Key, VOuter, VInner>((Multimap) multimap) {
@Override
protected VOuter toInner(VInner outer) {
return ConvertedMultimap.this.toOuter(outer);
}
@Override
protected VInner toOuter(VOuter inner) {
return ConvertedMultimap.this.toInner(inner);
}
});
}
@Override
public Collection<VOuter> replaceValues(@Nullable Key key, Iterable<? extends VOuter> values) {
return toOuterCollection(
inner.replaceValues(key, Iterables.transform(values, getInnerConverter()))
);
}
@Override
public Collection<VOuter> removeAll(@Nullable Object key) {
return toOuterCollection(inner.removeAll(key));
}
@Override
public void clear() {
inner.clear();
}
@Override
public Collection<VOuter> get(@Nullable Key key) {
return toOuterCollection(inner.get(key));
}
@Override
public Set<Key> keySet() {
return inner.keySet();
}
@Override
public Multiset<Key> keys() {
return inner.keys();
}
@Override
public Collection<VOuter> values() {
return toOuterCollection(inner.values());
}
@Override
public Collection<Entry<Key, VOuter>> entries() {
return ConvertedMap.convertedEntrySet(inner.entries(),
new BiFunction<Key, VOuter, VInner>() {
public VInner apply(Key key, VOuter outer) {
return toInner(outer);
}
},
new BiFunction<Key, VInner, VOuter>() {
public VOuter apply(Key key, VInner inner) {
return toOuter(inner);
}
}
);
}
@Override
public Map<Key, Collection<VOuter>> asMap() {
return new ConvertedMap<Key, Collection<VInner>, Collection<VOuter>>(inner.asMap()) {
@Override
protected Collection<VInner> toInner(Collection<VOuter> outer) {
return toInnerCollection(outer);
}
@Override
protected Collection<VOuter> toOuter(Collection<VInner> inner) {
return toOuterCollection(inner);
}
};
}
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map's <tt>entrySet</tt> view's iterator, enclosed in braces
* (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
* <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<tt>"="</tt>) followed by the
* associated value. Keys and values are converted to strings as by
* {@link String#valueOf(Object)}.
*
* @return a string representation of this map
*/
public String toString() {
Iterator<Entry<Key, VOuter>> i = entries().iterator();
if (!i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<Key, VOuter> e = i.next();
Key key = e.getKey();
VOuter value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(", ");
}
}
}