All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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