de.unkrig.commons.lang.protocol.Mappings Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of de-unkrig-commons Show documentation
Show all versions of de-unkrig-commons Show documentation
A versatile Java(TM) library that implements many useful container and utility classes.
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2014, Arno Unkrig
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package de.unkrig.commons.lang.protocol;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import de.unkrig.commons.lang.AssertionUtil;
import de.unkrig.commons.lang.ObjectUtil;
import de.unkrig.commons.nullanalysis.Nullable;
/**
* Utility methods related to {@link Mapping}.
*/
public final
class Mappings {
static { AssertionUtil.enableAssertionsForThisClass(); }
private Mappings() {}
/**
* Returns a proxy {@link Mapping} for a {@link Map}. This is very straightforward because the {@link Map} declares
* all methods that the {@link Mapping} requires. (Actually the {@link Map} should extend {@link Mapping},
* but doesn't.)
*/
public static Mapping
fromMap(final Map map) {
return new Mapping() {
@Override public boolean containsKey(@Nullable Object key) { return map.containsKey(key); }
@Override public V get(@Nullable Object key) { return map.get(key); }
@Override public String toString() { return map.toString(); }
};
}
/**
*
* @param keysAndValues An alternating sequence of keys and values; even elements must have type {@code K}, odd
* elements must have type {@code V} or {@link Producer Producer<V>}
* @return A mapping of the keys and values
*/
public static Mapping
mapping(final Object... keysAndValues) {
return new Mapping() {
@Override public boolean
containsKey(@Nullable Object key) {
for (int i = 0; i < keysAndValues.length; i += 2) {
if (ObjectUtil.equals(keysAndValues[i], key)) return true;
}
return false;
}
@Override @Nullable public V
get(@Nullable Object key) {
for (int i = 0; i < keysAndValues.length; i += 2) {
if (ObjectUtil.equals(keysAndValues[i], key)) {
Object value = keysAndValues[i + 1];
if (value instanceof Producer) {
@SuppressWarnings("unchecked") Producer producer = (Producer) value;
return producer.produce();
} else {
@SuppressWarnings("unchecked") V value2 = (V) value;
return value2;
}
}
}
return null;
}
@Override public String
toString() { return Arrays.toString(keysAndValues); }
};
}
/**
* @return A {@link Mapping} with no mappings
*/
public static Mapping
none() {
return new Mapping() {
@Override public boolean containsKey(@Nullable Object key) { return false; }
@Override @Nullable public V get(@Nullable Object key) { return null; }
@Override public String toString() { return "(none)"; }
};
}
/**
* Returns a mapping of property names to property values for the given {@code subject} object.
*
* The existence of a property "propertyName
" is determined by the existence of one of the
* following, in this order:
*
*
* - A public method "
getPropertyName()
"
* - A public method "
isPropertyName()
"
* - A public method "
hasPropertyName()
"
* - A public method "
propertyName()
"
* - A public field named "
propertyName
"
*
*
* @return A mapping of property name to property value
*/
public static Mapping
propertiesOf(final Object subject) {
final Class clasS = subject.getClass();
if (clasS.isArray()) {
return Mappings.mapping("length", ((Object[]) subject).length);
}
return new Mapping() {
@Override @Nullable public Object
get(@Nullable Object key) {
assert key instanceof String;
Object result = this.get((String) key);
return result == this ? null : result;
}
@Override public boolean
containsKey(@Nullable Object key) {
assert key instanceof String;
return this.get((String) key) != this;
}
@Nullable private Object
get(String propertyName) {
// Try to invoke 'get' plus the property name.
for (String getterMethodPrefix : new String[] { "get", "is", "has" }) {
try {
String getterMethodName = (
getterMethodPrefix
+ Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1)
);
Method m = clasS.getMethod(getterMethodName);
m.setAccessible(true); // To be able to access non-public classes.
return m.invoke(subject);
} catch (InvocationTargetException ite) {
throw new RuntimeException(ite.getTargetException()); // SUPPRESS CHECKSTYLE AvoidHidingCause
} catch (Exception e) {
;
}
}
// For properties like 'is...', 'has...', 'can...', try to invoke the method with the property name.
try {
Method m = clasS.getMethod(propertyName);
m.setAccessible(true); // To be able to access non-public classes.
return m.invoke(subject);
} catch (InvocationTargetException ite) {
throw new RuntimeException(ite.getTargetException()); // SUPPRESS CHECKSTYLE AvoidHidingCause
} catch (Exception e) {
;
}
try {
return clasS.getField(propertyName).get(subject);
} catch (Exception e) {
;
}
return this;
}
@Override public String
toString() { return "Properties of \"" + subject.toString() + "\" (" + subject.getClass() + ")"; }
};
}
/**
* Invokes {@link Mapping#get(Object)} on the {@code mapping} and, if the result is not {@code null}, converts it
* to the given {@code targetType}.
*
* @throws IllegalArgumentException The type of the value of the property is not assignable to {@code T}
*/
@Nullable public static T
get(Mapping mapping, Object key, Class targetType) {
Object value = mapping.get(key);
if (value == null) return null;
Class actualType = value.getClass();
try {
if (
targetType.isAssignableFrom(actualType)
|| (targetType.isPrimitive() && actualType.getDeclaredField("TYPE").get(null) == targetType)
) {
@SuppressWarnings("unchecked") T tmp = (T) value;
return tmp;
}
} catch (Exception e) {
;
}
throw new IllegalArgumentException(
"Value '"
+ value
+ "' of key '"
+ key
+ "' of mapping '"
+ mapping
+ "' has unexpected type '"
+ actualType
+ "' - expected '"
+ targetType
+ "'"
);
}
/**
* @return The value to which the {@code mapping} maps the {@code key}
* @throws IllegalArgumentException The {@code mapping} does not contain the given {@code key}
* @throws IllegalArgumentException The {@code mapping} contains the {@code key}, but the mapped value is {@code
* null}
*/
public static Object
getNonNull(Mapping mapping, String key) {
Object value = mapping.get(key);
if (value == null) {
if (!mapping.containsKey(key)) {
throw new IllegalArgumentException("Mapping '" + mapping + "' does not contain the key '" + key + "'");
} else {
throw new IllegalArgumentException("Value of key '" + key + "' of mapping '" + mapping + "' is ");
}
}
return value;
}
/**
* @return The value to which the {@code mapping} maps the {@code key}
* @throws IllegalArgumentException The {@code mapping} does not contain the given {@code key}
* @throws IllegalArgumentException The {@code mapping} contains the {@code key}, but the mapped value is {@code
* null}
* @throws IllegalArgumentException The {@code mapping} contains the {@code key}, but the mapped value is not
* assignable to {@code T}
*/
public static T
getNonNull(Mapping mapping, String key, Class targetType) {
T value = Mappings.get(mapping, key, targetType);
if (value == null) {
if (!mapping.containsKey(key)) {
throw new IllegalArgumentException("Mapping '" + mapping + "' does not contain the key '" + key + "'");
} else {
throw new IllegalArgumentException("Value of key '" + key + "' of mapping '" + mapping + "' is ");
}
}
return value;
}
/**
* Returns a mapping that implements the "union" of two mappings.
*
* For the returned {@link Mapping}, the following conditions apply:
*
*
* - A key is contained in the result iff it is contained in (at least) one of the operands.
*
- The value mapped to a key is the value mapped to the key in the first of the operands which
* contains the key, or {@code null} iff none of the {@code operands} contain the key.
*
*/
public static Mapping
union(final Mapping op1, final Mapping op2) {
return new Mapping() {
@Override public boolean
containsKey(@Nullable Object key) { return op1.containsKey(key) || op2.containsKey(key); }
@Override @Nullable public V
get(@Nullable Object key) {
V value = op1.get(key);
return value != null || op1.containsKey(key) ? value : op2.get(key);
}
@Override public String
toString() { return "(union of " + op1 + " and " + op2 + ")"; }
};
}
/**
* Equivalent with {@link #union(Mapping, Mapping)}, where the first argument is {@code in} and the second is
* constructed from the given {@code keyValuePairs}.
*
* @param keysAndValues An alternating sequence of keys and values; even elements must have type {@code K}, odd
* elements must have type {@code V} or {@link Producer Producer<V>}
*/
public static Mapping
augment(Mapping in, Object... keysAndValues) {
return Mappings.union(in, Mappings.mapping(keysAndValues));
}
/**
* Equivalent with {@link #union(Mapping, Mapping)}, where the first argument is constructed from the given {@code
* keyValuePairs} and the second argument is {@code in}.
*
* @param keysAndValues An alternating sequence of keys and values; even elements must have type {@code K}, odd
* elements must have type {@code V} or {@link Producer Producer<V>}
*/
public static Mapping
override(Mapping in, Object... keysAndValues) {
return Mappings.union(Mappings.mapping(keysAndValues), in);
}
/**
* @return A mapping which always returns value, independent from the keyvalue
*/
public static Mapping
constant(final V constantValue) {
return new Mapping() {
@Override public boolean containsKey(@Nullable Object key) { return true; }
@Override @Nullable public V get(@Nullable Object key) { return constantValue; }
};
}
}