uk.kludje.Meta Maven / Gradle / Ivy
/*
* Copyright 2014 McDowell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.kludje;
import java.util.*;
import static java.util.Collections.emptyList;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import static java.util.Arrays.asList;
/**
* Provides a basic meta-method builder for common {@code Object} method implementations.
*
* Example that builds equals, hashCode and toString methods using the id, name and dateOfBirth properties:
*
*
* import uk.kludje.Meta;
* import java.time.LocalDate;
* import static uk.kludje.Meta.meta;
*
* public class PersonPojo {
* private static final Meta<PersonPojo> META = meta(PersonPojo.class)
* . longs($ -> $.id).objects($ -> $.name, $ -> $.dateOfBirth);
*
* private final long id;
* private final String name;
* private final LocalDate dateOfBirth;
*
* public PersonPojo(long id, String name, LocalDate dateOfBirth) {
* this.id = id;
* this.name = name;
* this.dateOfBirth = dateOfBirth;
* }
*
* public String getName() {
* return name;
* }
*
* public LocalDate getDateOfBirth() {
* return dateOfBirth;
* }
*
* public boolean equals(Object obj) {
* return META.equals(this, obj);
* }
*
* public int hashCode() {
* return META.hashCode(this);
* }
*
* public String toString() {
* return META.toString(this);
* }
* }
*
*
* Note: arrays are treated as objects; use a decorator to provide alternative equals/hashCode/toString behaviour.
* For example, Google Guava's {@code Bytes.asList(byte...)}.
*
* Instances of this type are immutable and thread safe.
*
* @param the accessed type
*/
public final class Meta {
private static final Meta> INIT = new Meta<>(emptyList(), emptyList(), emptyList());
private final BiConsumer[] toString;
private final BiPredicate[] equals;
private final ToIntFunction[] hashCode;
@SuppressWarnings("unchecked")
private Meta(List> toString,
List> equals,
List> hashCode) {
this.toString = toString.toArray(new BiConsumer[toString.size()]);
this.equals = equals.toArray(new BiPredicate[equals.size()]);
this.hashCode = hashCode.toArray(new ToIntFunction[hashCode.size()]);
}
/**
* @param the type of class
* @return a new instance
*/
public static Meta meta() {
@SuppressWarnings("unchecked")
Meta safe = (Meta) INIT;
return safe;
}
/**
* Use to specify properties of type object that should be considered by this type.
*
* Do not use this method for primitive properties - alternatives have been provided.
*
* This method does not mutate the instance; it returns a new one.
*
* @param getters a vararg array of non-null getters
* @return a new instance
*/
@SafeVarargs
public final Meta objects(Getter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::objects,
eq()::objects,
hash()::objects);
}
@SafeVarargs
public final Meta booleans(BooleanGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::booleans,
eq()::booleans,
hash()::booleans);
}
@SafeVarargs
public final Meta chars(CharGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::chars,
eq()::chars,
hash()::chars);
}
@SafeVarargs
public final Meta bytes(ByteGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::bytes,
eq()::bytes,
hash()::bytes);
}
@SafeVarargs
public final Meta shorts(ShortGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::shorts,
eq()::shorts,
hash()::shorts);
}
@SafeVarargs
public final Meta ints(IntGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::ints,
eq()::ints,
hash()::ints);
}
@SafeVarargs
public final Meta longs(LongGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::longs,
eq()::longs,
hash()::longs);
}
@SafeVarargs
public final Meta floats(FloatGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::floats,
eq()::floats,
hash()::floats);
}
@SafeVarargs
public final Meta doubles(DoubleGetter... getters) {
@SuppressWarnings("varargs")
List> list = asList(getters);
return update(list,
str()::doubles,
eq()::doubles,
hash()::doubles);
}
private Meta update(List getters,
Function> strTransform,
Function> eqTransform,
Function> hashTransform) {
List> newToString = combine(toString, getters, strTransform);
List> newEquals = combine(equals, getters, eqTransform);
List> newHashCode = combine(hashCode, getters, hashTransform);
return new Meta<>(newToString, newEquals, newHashCode);
}
private List combine(R[] existing, List src, Function transform) {
List result = new ArrayList<>();
result.addAll(asList(existing));
src.stream()
.map(transform)
.forEach(result::add);
return result;
}
/**
* {@code any} is equal to {@code t} if it is of type {@code T}
* and all the properties defined by this type are equal.
*
* @param t a non-null instance of type T
* @param any any object, including null
* @return true if the arguments are equal
*/
public boolean equals(T t, Object any) {
Objects.requireNonNull(t);
if (any == null) {
return false;
}
if (any == t) {
return true;
}
if (!t.getClass().isInstance(any)) {
return false;
}
@SuppressWarnings("unchecked")
T other = (T) any;
for(BiPredicate bp : equals) {
if(!bp.test(t, other)) {
return false;
}
}
return true;
}
/**
* Creates a hash of all values defined by the properties provided to this instance.
* The hashing formula is not specified.
*
* @param t the non-null instance to create a hash for
* @return the hash
*/
public int hashCode(T t) {
int result = 0;
for (ToIntFunction fn : hashCode) {
result = (result * 31) + fn.applyAsInt(t);
}
return result;
}
/**
* Creates a string form of the type for debugging purposes.
* The exact form is not specified.
*
* @param t the non-null instance to create a string form of
* @return debug string
*/
public String toString(T t) {
StringBuilder buf = new StringBuilder();
buf.append(t.getClass().getSimpleName());
buf.append(" {");
for (BiConsumer val : toString) {
if (buf.length() > 0) {
buf.append(", ");
}
val.accept(t, buf);
}
return buf.append("}").toString();
}
private ToStringTransformer str() {
@SuppressWarnings("unchecked")
ToStringTransformer typed = (ToStringTransformer) ToStringTransformer.INST;
return typed;
}
private EqualsTransformer eq() {
@SuppressWarnings("unchecked")
EqualsTransformer typed = (EqualsTransformer) EqualsTransformer.INST;
return typed;
}
private HashTransformer hash() {
@SuppressWarnings("unchecked")
HashTransformer typed = (HashTransformer) HashTransformer.INST;
return typed;
}
/**
* A functional interface for reading a property value.
* Alternative types have been provided for primitives.
*
* @param the type to read the property from
* @see Meta#objects(uk.kludje.Meta.Getter[])
*/
@FunctionalInterface
public static interface Getter {
/**
* Reads a property value from the argument.
*
* @param t the source of the property value
* @return the property value instance or null
*/
Object get(T t);
}
@FunctionalInterface
public static interface BooleanGetter {
boolean getBoolean(T t);
}
@FunctionalInterface
public static interface CharGetter {
char getChar(T t);
}
@FunctionalInterface
public static interface ByteGetter {
byte getByte(T t);
}
@FunctionalInterface
public static interface ShortGetter {
short getShort(T t);
}
@FunctionalInterface
public static interface IntGetter {
int getInt(T t);
}
@FunctionalInterface
public static interface LongGetter {
long getLong(T t);
}
@FunctionalInterface
public static interface FloatGetter {
float getFloat(T t);
}
@FunctionalInterface
public static interface DoubleGetter {
double getDouble(T t);
}
}
interface GetterTransformer {
U objects(Meta.Getter g);
U booleans(Meta.BooleanGetter g);
U chars(Meta.CharGetter g);
U bytes(Meta.ByteGetter g);
U shorts(Meta.ShortGetter g);
U ints(Meta.IntGetter g);
U longs(Meta.LongGetter g);
U floats(Meta.FloatGetter g);
U doubles(Meta.DoubleGetter g);
}
class ToStringTransformer implements GetterTransformer> {
public static final ToStringTransformer> INST = new ToStringTransformer<>();
private ToStringTransformer() {
}
@Override
public BiConsumer objects(Meta.Getter g) {
return (t, buf) -> buf.append(g.get(t));
}
@Override
public BiConsumer booleans(Meta.BooleanGetter g) {
return (t, buf) -> buf.append(g.getBoolean(t));
}
@Override
public BiConsumer chars(Meta.CharGetter g) {
return (t, buf) -> buf.append(g.getChar(t));
}
@Override
public BiConsumer bytes(Meta.ByteGetter g) {
return (t, buf) -> buf.append(g.getByte(t));
}
@Override
public BiConsumer shorts(Meta.ShortGetter g) {
return (t, buf) -> buf.append(g.getShort(t));
}
@Override
public BiConsumer ints(Meta.IntGetter g) {
return (t, buf) -> buf.append(g.getInt(t));
}
@Override
public BiConsumer longs(Meta.LongGetter g) {
return (t, buf) -> buf.append(g.getLong(t));
}
@Override
public BiConsumer floats(Meta.FloatGetter g) {
return (t, buf) -> buf.append(g.getFloat(t));
}
@Override
public BiConsumer doubles(Meta.DoubleGetter g) {
return (t, buf) -> buf.append(g.getDouble(t));
}
}
class EqualsTransformer implements GetterTransformer> {
public static final EqualsTransformer> INST = new EqualsTransformer<>();
private EqualsTransformer() {
}
@Override
public BiPredicate objects(Meta.Getter g) {
return (t1, t2) -> {
Object o1 = g.get(t1);
Object o2 = g.get(t2);
return (o1 == null) ? (o2 == null) : o1.equals(o2);
};
}
@Override
public BiPredicate booleans(Meta.BooleanGetter g) {
return (t1, t2) -> g.getBoolean(t1) == g.getBoolean(t2);
}
@Override
public BiPredicate chars(Meta.CharGetter g) {
return (t1, t2) -> g.getChar(t1) == g.getChar(t2);
}
@Override
public BiPredicate bytes(Meta.ByteGetter g) {
return (t1, t2) -> g.getByte(t1) == g.getByte(t2);
}
@Override
public BiPredicate shorts(Meta.ShortGetter g) {
return (t1, t2) -> g.getShort(t1) == g.getShort(t2);
}
@Override
public BiPredicate ints(Meta.IntGetter g) {
return (t1, t2) -> g.getInt(t1) == g.getInt(t2);
}
@Override
public BiPredicate longs(Meta.LongGetter g) {
return (t1, t2) -> g.getLong(t1) == g.getLong(t2);
}
@Override
public BiPredicate floats(Meta.FloatGetter g) {
return (t1, t2) -> Float.compare(g.getFloat(t1), g.getFloat(t2)) == 0;
}
@Override
public BiPredicate doubles(Meta.DoubleGetter g) {
return (t1, t2) -> Double.compare(g.getDouble(t1), g.getDouble(t2)) == 0;
}
}
class HashTransformer implements GetterTransformer> {
public static final HashTransformer> INST = new HashTransformer<>();
private HashTransformer() {
}
@Override
public ToIntFunction objects(Meta.Getter g) {
return t -> {
Object o = g.get(t);
return (o == null) ? 0 : o.hashCode();
};
}
@Override
public ToIntFunction booleans(Meta.BooleanGetter g) {
return t -> g.getBoolean(t) ? 1 : 0;
}
@Override
public ToIntFunction chars(Meta.CharGetter g) {
return t -> g.getChar(t);
}
@Override
public ToIntFunction bytes(Meta.ByteGetter g) {
return t -> g.getByte(t);
}
@Override
public ToIntFunction shorts(Meta.ShortGetter g) {
return t -> g.getShort(t);
}
@Override
public ToIntFunction ints(Meta.IntGetter g) {
return t -> g.getInt(t);
}
@Override
public ToIntFunction longs(Meta.LongGetter g) {
return t -> {
long l = g.getLong(t);
return (int) (l ^ (l >>> 32));
};
}
@Override
public ToIntFunction floats(Meta.FloatGetter g) {
return t -> Float.hashCode(g.getFloat(t));
}
@Override
public ToIntFunction doubles(Meta.DoubleGetter g) {
return t -> Double.hashCode(g.getDouble(t));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy