package org.andork.q2;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Defines a set of properties that any {@link QArrayObject} constructed with
* this spec will have.<br>
* <br>
* To use this, create a class that extends {@code QSpec}, give it
* {@code public static final Property<?>} fields, and pass those fields into
* {@link #QSpec(Property...)}. You'll also probably want to create a
* {@code public static final} instance of your class and make its constructor
* {@code private}.<br>
* <br>
* The Q doesn't stand for anything.
*
* @author andy.edwards
* @param <S>
* the type of {@link QSpec} for this object.
*/
public class QSpec {
public static class NonNullProperty<T> extends Property<T> {
public NonNullProperty(String name, Class<? super T> type, Supplier<? extends T> initValue) {
super(name, type, Objects.requireNonNull(initValue));
}
@Override
public T set(QObject<?> object, T newValue) {
return super.set(object, Objects.requireNonNull(newValue));
}
}
public static class Property<T> {
int index;
QSpec spec;
String name;
Class<? super T> type;
Supplier<? extends T> initValue;
public Property(String name, Class<? super T> type) {
this.name = name;
this.type = type;
}
public Property(String name, Class<? super T> type, Supplier<? extends T> initValue) {
this(name, type);
this.initValue = initValue;
}
public boolean equals(T a, T b) {
return Objects.equals(a, b);
}
public T get(QObject<?> object) {
requirePropertyOf(object);
return object.doGet(this);
}
public int hashCode(T t) {
return t == null ? 0 : t.hashCode();
}
public final int index() {
return index;
}
public Supplier<? extends T> initValue() {
return initValue;
}
public final boolean isPropertyOf(QObject<?> object) {
return object.spec.properties[index] == this;
}
public final boolean isPropertyOf(QSpec spec) {
return spec.properties[index] == this;
}
public final String name() {
return name;
}
public final Property<T> requirePropertyOf(QObject<?> object) {
if (!isPropertyOf(object)) {
throw new IllegalArgumentException(this + " is not a property of the given object");
}
return this;
}
public final Property<T> requirePropertyOf(QSpec spec) {
if (!isPropertyOf(spec)) {
throw new IllegalArgumentException(this + " is not a property of the given spec");
}
return this;
}
public T set(QObject<?> object, T newValue) {
requirePropertyOf(object);
return object.doSet(this, newValue);
}
@Override
public String toString() {
return name;
}
public final Class<? super T> type() {
return type;
}
}
private static final int[] primes = {
13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139,
149, 151, 157, 163, 167,
173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241,
251, 257, 263, 269, 271,
277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
373, 379, 383, 389, 397,
401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487,
491, 499, 503, 509, 521,
523, 541 };
public static <T> NonNullProperty<T> nonNullProperty(String name, Class<? super T> type,
Supplier<? extends T> initValue) {
return new NonNullProperty<T>(name, type, initValue);
}
public static <T> NonNullProperty<T> nonNullProperty(String name, Class<? super T> type, T initValue) {
return new NonNullProperty<T>(name, type, () -> initValue);
}
public static <T> Property<T> property(String name, Class<? super T> type) {
return new Property<T>(name, type);
}
public static <T> Property<T> property(String name, Class<? super T> type, Supplier<? extends T> initValue) {
return new Property<T>(name, type, initValue);
}
public static <T> Property<T> property(String name, Class<? super T> type, T initValue) {
return new Property<T>(name, type, () -> initValue);
}
QSpec superspec;
Property<?>[] properties;
List<Property<?>> propertyList;
public QSpec(Property<?>... properties) {
superspec = null;
this.properties = new Property[properties.length];
for (int i = 0; i < properties.length; i++) {
if (properties[i].spec != null) {
throw new IllegalStateException("properties[" + i + "] (" + properties[i].name +
") already belongs to spec " + properties[i].spec);
}
this.properties[i] = properties[i];
this.properties[i].spec = this;
this.properties[i].index = i;
}
propertyList = Collections.unmodifiableList(Arrays.asList(properties));
}
/**
* Creates a "derived" {@code QSpec} with all the properties of a
* "superspec" and more. Implementing classes that use this constructor
* should extend the superspec class.
*
* @param superspec
* @param properties
*/
public QSpec(QSpec superspec, Property<?>... properties) {
this.superspec = superspec;
this.properties = new Property[superspec.properties.length + properties.length];
System.arraycopy(superspec.properties, 0, this.properties, 0, superspec.properties.length);
for (int i = 0; i < properties.length; i++) {
if (properties[i].spec != null) {
throw new IllegalStateException("properties[" + i + "] (" + properties[i].name +
") already belongs to spec " + properties[i].spec);
}
int ii = i + superspec.properties.length;
this.properties[ii] = properties[i];
this.properties[ii].spec = this;
this.properties[ii].index = ii;
}
propertyList = Collections.unmodifiableList(Arrays.asList(properties));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean equals(QObject a, Object b) {
if (b instanceof QObject && a.spec == ((QObject) b).spec) {
QObject bq = (QObject) b;
for (int i = 0; i < properties.length; i++) {
if (!((Property) properties[i]).equals(a.doGet(properties[i]), bq.doGet(properties[i]))) {
return false;
}
}
return true;
}
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public int hashCode(QObject o) {
int hashCode = 0;
for (int i = 0; i < properties.length; i++) {
hashCode = hashCode * primes[i % primes.length]
^ ((Property) properties[i]).hashCode(o.doGet(properties[i]));
}
return hashCode;
}
public final List<Property<?>> properties() {
return propertyList;
}
public final Property<?> propertyAt(int index) {
return properties[index];
}
public final int propertyCount() {
return properties.length;
}
public final Property<?> propertyNamed(String name) {
for (int i = 0; i < properties.length; i++) {
if (name.equals(properties[i].name)) {
return properties[i];
}
}
return null;
}
}