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

io.permazen.PermazenClass Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;

import io.permazen.annotation.PermazenCompositeIndexes;
import io.permazen.annotation.PermazenType;
import io.permazen.core.DeleteAction;
import io.permazen.core.EnumValueEncoding;
import io.permazen.core.ListField;
import io.permazen.core.MapField;
import io.permazen.core.ObjType;
import io.permazen.core.Schema;
import io.permazen.core.SetField;
import io.permazen.core.UnknownFieldException;
import io.permazen.encoding.Encoding;
import io.permazen.encoding.EncodingId;
import io.permazen.encoding.EncodingRegistry;
import io.permazen.encoding.SimpleEncodingRegistry;
import io.permazen.kv.KeyRanges;
import io.permazen.schema.SchemaObjectType;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents a specific Permazen object type in a {@link Permazen} database.
 *
 * @param  the Java type that represents instances of this database object type
 */
public class PermazenClass extends PermazenSchemaItem {

    final Logger log = LoggerFactory.getLogger(this.getClass());

    final Permazen pdb;
    final Class type;
    final boolean singleton;
    final PermazenType permazenType;
    final ClassGenerator classGenerator;
    final TreeMap fieldsByName = new TreeMap<>();                      // does not include sub-fields
    final TreeMap fieldsByStorageId = new TreeMap<>();                // does not include sub-fields
    final TreeMap simpleFieldsByName = new TreeMap<>();          // does include sub-fields
    final TreeMap simpleFieldsByStorageId = new TreeMap<>();    // does include sub-fields
    final TreeMap jcompositeIndexesByName = new TreeMap<>();
    final ArrayList uniqueConstraintFields = new ArrayList<>();
    final ArrayList uniqueConstraintCompositeIndexes = new ArrayList<>();
    final ArrayList upgradeConversionFields = new ArrayList<>();                // only simple and counter fields
    final HashMap> forwardCascadeMap = new HashMap<>();
    final HashMap> inverseCascadeMap = new HashMap<>();

    Set.ReferencePathMethodInfo> referencePathMethods;
    Set.MethodInfo> onCreateMethods;
    Set.MethodInfo> onDeleteMethods;
    Set.MethodInfo> onChangeMethods;
    Set.MethodInfo> earlyOnValidateMethods;
    Set.MethodInfo> lateOnValidateMethods;
    Set.MethodInfo> onSchemaChangeMethods;

    boolean requiresDefaultValidation;
    AnnotatedElement elementRequiringJSR303Validation;

    /**
     * Constructor.
     *
     * @param name the name of the object type
     * @param storageId object type storage ID, or zero to automatically assign
     * @param type object type Java model class
     * @throws IllegalArgumentException if any parameter is null
     * @throws IllegalArgumentException if {@code storageId} is non-positive
     */
    PermazenClass(Permazen pdb, String name, int storageId, Class type) {
        super(name, storageId, String.format("object type \"%s\" (%s)", name, type));
        Preconditions.checkArgument(name != null, "null name");
        if (UntypedPermazenObject.class.isAssignableFrom(type)) {
            throw new IllegalArgumentException(String.format(
              "invalid model type %s: model types may not subclass %s", type.getName(), UntypedPermazenObject.class.getName()));
        }
        this.pdb = pdb;
        this.type = type;
        this.classGenerator = new ClassGenerator<>(pdb, this);
        this.permazenType = Util.getAnnotation(this.type, PermazenType.class);
        this.singleton = this.permazenType.singleton();
    }

    // Get class generator
    ClassGenerator getClassGenerator() {
        return this.classGenerator;
    }

// Public API

    /**
     * Get the Java model object type associated with this instance.
     *
     * @return associated Java type
     */
    public Class getType() {
        return this.type;
    }

    /**
     * Get all {@link PermazenField}'s associated with this instance, indexed by name.
     *
     * 

* The returned map does not include sub-fields of complex fields. * * @return read-only mapping from name to {@link PermazenField} */ public NavigableMap getFieldsByName() { return Collections.unmodifiableNavigableMap(this.fieldsByName); } /** * Get all {@link PermazenField}'s associated with this instance, indexed by storage ID. * *

* The returned map does not include sub-fields of complex fields. * * @return read-only mapping from storage ID to {@link PermazenField} */ public NavigableMap getFieldsByStorageId() { return Collections.unmodifiableNavigableMap(this.fieldsByStorageId); } /** * Get all {@link PermazenCompositeIndex}'s defined on this {@link PermazenClass}, indexed by name. * * @return read-only mapping from name to {@link PermazenCompositeIndex} */ public NavigableMap getCompositeIndexesByName() { return Collections.unmodifiableNavigableMap(this.jcompositeIndexesByName); } /** * Get the specified {@link PermazenField} in this database object type, cast to the given Java type. * * @param fieldName field name * @param type required type * @param expected encoding * @return corresponding {@link PermazenField} in this instance * @throws UnknownFieldException if {@code fieldName} is not found * @throws UnknownFieldException if the field is not an instance of of {@code type} * @throws IllegalArgumentException if either parameter is null */ public T getField(String fieldName, Class type) { Preconditions.checkArgument(fieldName != null, "null fieldName"); Preconditions.checkArgument(type != null, "null type"); final PermazenField pfield = this.fieldsByName.get(fieldName); if (pfield == null) { throw new UnknownFieldException(fieldName, String.format("object type \"%s\" has no field named \"%s\"", this.name, fieldName)); } try { return type.cast(pfield); } catch (ClassCastException e) { throw new UnknownFieldException(fieldName, String.format("object type \"%s\" has no field named \"%s\" of type %s (found %s instead)", this.name, fieldName, type.getName(), pfield)); } } @Override public ObjType getSchemaItem() { return (ObjType)super.getSchemaItem(); } // Package Methods void replaceSchemaItems(Schema schema) { final ObjType objType = schema.getObjType(this.name); this.schemaItem = objType; this.fieldsByName.values().forEach(field -> field.replaceSchemaItems(objType)); this.jcompositeIndexesByName.values().forEach(index -> index.replaceSchemaItems(objType)); } void createFields(EncodingRegistry encodingRegistry, Collection> pclasses) { // Scan for Simple and Counter fields final PermazenFieldScanner simpleFieldScanner = new PermazenFieldScanner<>(this, this.permazenType); for (PermazenFieldScanner.MethodInfo info : simpleFieldScanner.findAnnotatedMethods()) { // Get info final io.permazen.annotation.PermazenField annotation = info.getAnnotation(); final Method getter = Util.findPermazenFieldGetterMethod(this.type, info.getMethod()); final String description = String.format( "%s annotation on method %s", simpleFieldScanner.getAnnotationDescription(), getter); final String fieldName = this.getFieldName(annotation.name(), info, description); final TypeToken encodingToken = TypeToken.of(this.type).resolveType(getter.getGenericReturnType()); if (this.log.isTraceEnabled()) this.log.trace("found {}", description); // Handle Counter fields if (encodingToken.equals(TypeToken.of(Counter.class))) { // Sanity check annotation if (!annotation.encoding().isEmpty()) { throw new IllegalArgumentException(String.format( "invalid %s: counter fields must not specify an encoding", description)); } if (annotation.indexed()) { throw new IllegalArgumentException(String.format( "invalid %s: counter fields cannot be indexed", description)); } // Create counter field final PermazenCounterField pfield = new PermazenCounterField(fieldName, annotation.storageId(), annotation, String.format("counter field \"%s\" of object type \"%s\"", fieldName, this.name), getter); // Remember upgrade conversion fields if (annotation.upgradeConversion().isConvertsValues()) this.upgradeConversionFields.add(pfield); // Add field this.addField(pfield); continue; } // Find corresponding setter method final Method setter; try { setter = Util.findPermazenFieldSetterMethod(this.type, getter); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("invalid %s: %s", description, e.getMessage())); } // Create simple field final PermazenSimpleField pfield = this.createSimpleField(encodingRegistry, pclasses, description, encodingToken, fieldName, annotation.storageId(), annotation, getter, setter, String.format("field \"%s\" of object type \"%s\"", fieldName, this.name)); // Add field this.addField(pfield); // Remember unique constraint fields if (pfield.unique) this.uniqueConstraintFields.add(pfield); // Remember upgrade conversion fields if (annotation.upgradeConversion().isConvertsValues()) this.upgradeConversionFields.add(pfield); } // Scan for Set fields final PermazenSetFieldScanner setFieldScanner = new PermazenSetFieldScanner<>(this, this.permazenType); for (PermazenSetFieldScanner.MethodInfo info : setFieldScanner.findAnnotatedMethods()) { // Get info final io.permazen.annotation.PermazenSetField annotation = info.getAnnotation(); final io.permazen.annotation.PermazenField elementAnnotation = annotation.element(); final Method getter = Util.findPermazenFieldGetterMethod(this.type, info.getMethod()); final String description = String.format( "%s annotation on method %s", setFieldScanner.getAnnotationDescription(), getter); final String fieldName = this.getFieldName(annotation.name(), info, description); if (this.log.isTraceEnabled()) this.log.trace("found {}", description); // Get element type (the raw return type has already been validated by the annotation scanner) final TypeToken elementType = TypeToken.of(this.type).resolveType(this.getParameterType(description, getter, 0)); // Create element sub-field final PermazenSimpleField elementField = this.createSimpleField(encodingRegistry, pclasses, "element() property of " + description, elementType, SetField.ELEMENT_FIELD_NAME, elementAnnotation.storageId(), elementAnnotation, null, null, String.format("%s sub-field \"%s.%s\" in object type \"%s\"", "set", fieldName, SetField.ELEMENT_FIELD_NAME, this.name)); // Create set field final PermazenSetField pfield = new PermazenSetField(fieldName, annotation.storageId(), annotation, elementField, String.format("%s field \"%s\" in object type \"%s\"", "set", fieldName, this.name), getter); elementField.parent = pfield; // Add field this.addField(pfield); } // Scan for List fields final PermazenListFieldScanner listFieldScanner = new PermazenListFieldScanner<>(this, this.permazenType); for (PermazenListFieldScanner.MethodInfo info : listFieldScanner.findAnnotatedMethods()) { // Get info final io.permazen.annotation.PermazenListField annotation = info.getAnnotation(); final io.permazen.annotation.PermazenField elementAnnotation = annotation.element(); final Method getter = Util.findPermazenFieldGetterMethod(this.type, info.getMethod()); final String description = String.format( "%s annotation on method %s", listFieldScanner.getAnnotationDescription(), getter); final String fieldName = this.getFieldName(annotation.name(), info, description); if (this.log.isTraceEnabled()) this.log.trace("found {}", description); // Get element type (the raw return type has already been validated by the annotation scanner) final TypeToken elementType = TypeToken.of(this.type).resolveType(this.getParameterType(description, getter, 0)); // Create element sub-field final PermazenSimpleField elementField = this.createSimpleField(encodingRegistry, pclasses, "element() property of " + description, elementType, ListField.ELEMENT_FIELD_NAME, elementAnnotation.storageId(), elementAnnotation, null, null, String.format("%s sub-field \"%s.%s\" in object type \"%s\"", "list", fieldName, ListField.ELEMENT_FIELD_NAME, this.name)); // Create list field final PermazenListField pfield = new PermazenListField(fieldName, annotation.storageId(), annotation, elementField, String.format("%s field \"%s\" in object type \"%s\"", "list", fieldName, this.name), getter); elementField.parent = pfield; // Add field this.addField(pfield); } // Scan for Map fields final PermazenMapFieldScanner mapFieldScanner = new PermazenMapFieldScanner<>(this, this.permazenType); for (PermazenMapFieldScanner.MethodInfo info : mapFieldScanner.findAnnotatedMethods()) { // Get info final io.permazen.annotation.PermazenMapField annotation = info.getAnnotation(); final io.permazen.annotation.PermazenField keyAnnotation = annotation.key(); final io.permazen.annotation.PermazenField valueAnnotation = annotation.value(); final Method getter = Util.findPermazenFieldGetterMethod(this.type, info.getMethod()); final String description = String.format( "%s annotation on method %s", mapFieldScanner.getAnnotationDescription(), getter); final String fieldName = this.getFieldName(annotation.name(), info, description); if (this.log.isTraceEnabled()) this.log.trace("found {}", description); // Get key and value types (the raw return type has already been validated by the annotation scanner) final TypeToken keyType = TypeToken.of(this.type).resolveType(this.getParameterType(description, getter, 0)); final TypeToken valueType = TypeToken.of(this.type).resolveType(this.getParameterType(description, getter, 1)); // Create key and value sub-fields final PermazenSimpleField keyField = this.createSimpleField(encodingRegistry, pclasses, "key() property of " + description, keyType, MapField.KEY_FIELD_NAME, keyAnnotation.storageId(), keyAnnotation, null, null, String.format("%s sub-field \"%s.%s\" in object type \"%s\"", "map", fieldName, MapField.KEY_FIELD_NAME, this.name)); final PermazenSimpleField valueField = this.createSimpleField(encodingRegistry, pclasses, "value() property of " + description, valueType, MapField.VALUE_FIELD_NAME, valueAnnotation.storageId(), valueAnnotation, null, null, String.format("%s sub-field \"%s.%s\" in object type \"%s\"", "map", fieldName, MapField.VALUE_FIELD_NAME, this.name)); // Create map field final PermazenMapField pfield = new PermazenMapField(fieldName, annotation.storageId(), annotation, keyField, valueField, String.format("%s field \"%s\" in object type \"%s\"", "map", fieldName, this.name), getter); keyField.parent = pfield; valueField.parent = pfield; // Add field this.addField(pfield); } // Verify that the generated class will not have any remaining abstract methods final Map abstractMethods = Util.findAbstractMethods(this.type); for (PermazenField pfield : this.fieldsByName.values()) { abstractMethods.remove(new MethodKey(pfield.getter)); if (pfield instanceof PermazenSimpleField) abstractMethods.remove(new MethodKey(((PermazenSimpleField)pfield).setter)); } for (Method method : PermazenObject.class.getDeclaredMethods()) abstractMethods.remove(new MethodKey(method)); for (Iterator i = abstractMethods.values().iterator(); i.hasNext(); ) { if (Util.getAnnotation(i.next(), io.permazen.annotation.ReferencePath.class) != null) i.remove(); } if (!abstractMethods.isEmpty()) { throw new IllegalArgumentException(String.format( "the @%s-annotated type %s is invalid because %d abstract method(s) remain unimplemented:%n %s", PermazenType.class.getSimpleName(), this.type.getName(), abstractMethods.size(), abstractMethods.values().stream() .sorted(Comparator.comparing(Method::getName)) .map(Method::toString) .collect(Collectors.joining("\n ")))); } // Calculate which fields require default validation this.fieldsByName.values().forEach(PermazenField::calculateRequiresDefaultValidation); } // Like fields, composite indexes are inherited (duplicated) from supertypes void createCompositeIndexes() { for (Class supertype : TypeToken.of(this.type).getTypes().rawTypes()) { // Decode possibly repeated annotation(s) final io.permazen.annotation.PermazenCompositeIndex[] annotations; final PermazenCompositeIndexes container = Util.getAnnotation(supertype, PermazenCompositeIndexes.class); if (container != null) annotations = container.value(); else { io.permazen.annotation.PermazenCompositeIndex annotation = Util.getAnnotation(supertype, io.permazen.annotation.PermazenCompositeIndex.class); if (annotation == null) continue; annotations = new io.permazen.annotation.PermazenCompositeIndex[] { annotation }; } // Create corresponding indexes for (io.permazen.annotation.PermazenCompositeIndex annotation : annotations) { if (annotation.uniqueExcludes().length > 0 && !annotation.unique()) { throw new IllegalArgumentException(String.format( "invalid @%s annotation on %s: use of uniqueExcludes() requires unique() = true", io.permazen.annotation.PermazenCompositeIndex.class.getSimpleName(), supertype)); } this.addCompositeIndex(supertype, annotation); } } } private void addCompositeIndex(Class declaringType, io.permazen.annotation.PermazenCompositeIndex annotation) { // Get info final String indexName = annotation.name(); // Resolve field names final String[] fieldNames = annotation.fields(); final PermazenSimpleField[] indexFields = new PermazenSimpleField[fieldNames.length]; final HashSet seenFieldNames = new HashSet<>(); for (int i = 0; i < fieldNames.length; i++) { final String fieldName = fieldNames[i]; if (!seenFieldNames.add(fieldName)) throw this.invalidIndex(annotation, "field \"%s\" appears more than once", fieldName); final PermazenField pfield = this.fieldsByName.get(fieldName); if (pfield == null) throw this.invalidIndex(annotation, "field \"%s\" not found", fieldName); else if (!(pfield instanceof PermazenSimpleField)) throw this.invalidIndex(annotation, "field \"%s\" is not a simple field", fieldName); indexFields[i] = (PermazenSimpleField)pfield; } // Create and add index final PermazenCompositeIndex index = new PermazenCompositeIndex(indexName, annotation.storageId(), declaringType, annotation, indexFields); if (this.jcompositeIndexesByName.put(indexName, index) != null) throw this.invalidIndex(annotation, "duplicate composite index name \"%s\"", indexName); // Remember unique constraint composite indexes and trigger validation when any indexed field changes if (index.unique) { this.uniqueConstraintCompositeIndexes.add(index); for (PermazenSimpleField pfield : index.pfields) pfield.requiresDefaultValidation = true; } } private IllegalArgumentException invalidIndex( io.permazen.annotation.PermazenCompositeIndex annotation, String format, Object... args) { return new IllegalArgumentException(String.format( "invalid @%s annotation for index \"%s\" on %s: %s", io.permazen.annotation.PermazenCompositeIndex.class.getSimpleName(), annotation.name(), this.type, String.format(format, args))); } void scanAnnotations() { this.referencePathMethods = new ReferencePathScanner<>(this).findReferencePathMethods(); this.onCreateMethods = new OnCreateScanner<>(this).findAnnotatedMethods(); this.onDeleteMethods = new OnDeleteScanner<>(this).findAnnotatedMethods(); this.onChangeMethods = new OnChangeScanner<>(this).findAnnotatedMethods(); this.onSchemaChangeMethods = new OnSchemaChangeScanner<>(this).findAnnotatedMethods(); // Find @OnValidate methods and split into early vs. late final Set.MethodInfo> onValidateMethods = new OnValidateScanner<>(this).findAnnotatedMethods(); this.earlyOnValidateMethods = onValidateMethods.stream() .filter(info -> info.getAnnotation().early()) .collect(Collectors.toSet()); this.lateOnValidateMethods = onValidateMethods.stream() .filter(info -> !info.getAnnotation().early()) .collect(Collectors.toSet()); } void calculateValidationRequirement() { // Check for use of JSR 303 annotations this.elementRequiringJSR303Validation = Util.hasValidation(this.type); // Check for JSR 303 or @OnValidate annotations in default group if (Util.requiresDefaultValidation(this.type)) { this.requiresDefaultValidation = true; return; } // Check for any simple index uniqueness constraints if (!this.uniqueConstraintFields.isEmpty()) { this.requiresDefaultValidation = true; return; } // Check for any composite index uniqueness constraints if (!this.uniqueConstraintCompositeIndexes.isEmpty()) { this.requiresDefaultValidation = true; return; } // Check for singleton constraint if (this.singleton) { this.requiresDefaultValidation = true; return; } } @Override SchemaObjectType toSchemaItem() { final SchemaObjectType objectType = (SchemaObjectType)super.toSchemaItem(); objectType.setSchemaEpoch(this.permazenType.schemaEpoch()); this.fieldsByName.forEach((name, field) -> objectType.getSchemaFields().put(name, field.toSchemaItem())); this.jcompositeIndexesByName.forEach( (name, index) -> objectType.getSchemaCompositeIndexes().put(name, index.toSchemaItem())); return objectType; } @Override SchemaObjectType createSchemaItem() { return new SchemaObjectType(); } @Override void visitSchemaItems(Consumer visitor) { super.visitSchemaItems(visitor); this.fieldsByName.values().forEach(item -> item.visitSchemaItems(visitor)); this.jcompositeIndexesByName.values().forEach(item -> item.visitSchemaItems(visitor)); } // Add new PermazenField (and sub-fields, if any), checking for name conflicts private void addField(PermazenField pfield) { // Check for field name conflict; note we can get this legitimately when a field is declared // only in supertypes when two of the supertypes are mutually unassignable from each other. // In that case, if the generated field is exactlly the same then allow it. PermazenField other = this.fieldsByName.put(pfield.name, pfield); if (other != null) { // If the descriptions differ, no need to give any more details final String desc1 = other.toString(); final String desc2 = pfield.toString(); if (!desc2.equals(desc1)) { throw new IllegalArgumentException(String.format( "illegal duplicate use of name \"%s\" for both %s and %s in %s", pfield.name, desc1, desc2, this)); } // Check whether the fields are exactly the same; if not, there is a conflict if (!other.isSameAs(pfield)) { throw new IllegalArgumentException(String.format( "two or more methods defining %s conflict: %s and %s", pfield, other.getter, pfield.getter)); } } pfield.parent = this; // Update various maps if (pfield instanceof PermazenSimpleField) { final PermazenSimpleField psimpleField = (PermazenSimpleField)pfield; this.simpleFieldsByName.put(psimpleField.getFullName(), psimpleField); } else if (pfield instanceof PermazenComplexField) { for (PermazenSimpleField psimpleField : ((PermazenComplexField)pfield).getSubFields()) this.simpleFieldsByName.put(psimpleField.getFullName(), psimpleField); } // Logging if (this.log.isTraceEnabled()) this.log.trace("added {} to object type \"{}\"", pfield, this.name); } // Get field name, deriving it from the getter property name if necessary private String getFieldName(String fieldName, AnnotationScanner.MethodInfo info, String description) { if (fieldName.length() > 0) return fieldName; try { return info.getMethodPropertyName(); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format( "invalid %s: can't infer field name: %s", description, e.getMessage()), e); } } // Get the n'th generic type parameter private Type getParameterType(String description, Method method, int index) { try { return Util.getTypeParameter(method.getGenericReturnType(), index); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format( "invalid %s: invalid method return type: %s", description, e.getMessage()), e); } } // Create a simple field, either regular object field or sub-field of complex field @SuppressWarnings("unchecked") private PermazenSimpleField createSimpleField(EncodingRegistry encodingRegistry, Collection> pclasses, String description, TypeToken encodingToken, String fieldName, int storageId, io.permazen.annotation.PermazenField annotation, Method getter, Method setter, String fieldDescription) { // Get explicit encoding, if any EncodingId encodingId = null; final String encodingName = annotation.encoding().length() > 0 ? annotation.encoding() : null; if (encodingName != null) { try { encodingId = encodingRegistry.idForAlias(encodingName); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("invalid %s: invalid encoding \"%s\"", description, encodingName)); } } // Include containing type for annotation description (if needed); with autogenProperties it can be more than one final String typeName = this.type.getName(); if (!description.contains(typeName)) description += " in " + this.type; // Complex sub-field? final boolean isSubField = setter == null; // Sanity check annotation if (isSubField && annotation.unique()) { throw new IllegalArgumentException(String.format( "invalid %s: unique() constraint not allowed on complex sub-field", description)); } if (!ValueMatch.isEmpty(annotation.uniqueExcludes()) && !annotation.unique()) { throw new IllegalArgumentException(String.format( "invalid %s: use of %s() requires %s() = true", description, "uniqueExcludes", "unique")); } // See if encoding encompasses one or more PermazenClass types and is therefore a reference type final Class fieldRawType = encodingToken.getRawType(); boolean isReferenceType = false; for (PermazenClass pclass : pclasses) { if (fieldRawType.isAssignableFrom(pclass.type)) { isReferenceType = true; break; } } // Check for reference to UntypedPermazenObject - not currently allowed if (UntypedPermazenObject.class.isAssignableFrom(fieldRawType)) { throw new IllegalArgumentException(String.format( "invalid %s: references to %s%s are not allowed; use %s instead", description, !UntypedPermazenObject.class.equals(fieldRawType) ? "sub-types of " : "", UntypedPermazenObject.class.getName(), PermazenObject.class.getName())); } // See if encoding is a simple type, known either by explicitly-given encoding or type Encoding nonReferenceType = null; if (encodingId != null) { // Field encoding is explicitly specified if ((nonReferenceType = encodingRegistry.getEncoding(encodingId)) == null) throw new IllegalArgumentException(String.format("invalid %s: unknown encoding \"%s\"", description, encodingName)); // Verify encoding matches what we expect final TypeToken expectedType = isSubField ? nonReferenceType.getTypeToken().wrap() : nonReferenceType.getTypeToken(); if (!expectedType.equals(encodingToken)) { throw new IllegalArgumentException(String.format("invalid %s: encoding \"%s\" supports values" + " of type %s but %s is required (according to the getter method's return type)", description, encodingName, nonReferenceType.getTypeToken(), encodingToken)); } } else { // Try to find an encoding supporting getter method return type final List> encodings = encodingRegistry.getEncodings(encodingToken); switch (encodings.size()) { case 0: nonReferenceType = null; break; case 1: nonReferenceType = encodings.get(0); break; default: if (!isReferenceType) { throw new IllegalArgumentException(String.format("invalid %s: an explicit encoding() must be" + " specified because type %s is supported by multiple registered simple encodings: %s", description, encodingToken, encodings)); } break; } } // Handle enum and enum array types Class> enumType = null; Class enumArrayEncoding = null; int enumArrayDimensions = -1; if (nonReferenceType == null) { // Handle non-array Enum type enumType = Enum.class.isAssignableFrom(fieldRawType) ? (Class>)fieldRawType.asSubclass(Enum.class) : null; if (enumType != null) { nonReferenceType = new EnumValueEncoding(enumType); enumArrayDimensions = 0; } // Handle array Enum type if (fieldRawType.isArray()) { // Get base type and count dimensions assert nonReferenceType == null; enumArrayDimensions = 0; Class baseType = fieldRawType; while (baseType != null && baseType.isArray()) { enumArrayDimensions++; baseType = baseType.getComponentType(); } // Is array base type an Enum type? if (Enum.class.isAssignableFrom(baseType)) { // Get base Enum type enumType = (Class>)baseType.asSubclass(Enum.class); // Get the corresponding EnumValue[][]... Java type and Encoding (based on the Enum's identifier list) nonReferenceType = new EnumValueEncoding(enumType); for (int i = 0; i < enumArrayDimensions; i++) nonReferenceType = SimpleEncodingRegistry.buildArrayEncoding(nonReferenceType); // Save type info enumType = (Class>)baseType.asSubclass(Enum.class); enumArrayEncoding = fieldRawType; } } } // If field's type neither refers to a PermazenClass type, nor has a registered encoding, nor is an enum type, fail if (!isReferenceType && nonReferenceType == null && enumType == null && enumArrayEncoding == null) { throw new IllegalArgumentException(String.format("invalid %s: an explicit encoding() must be specified" + " because no registered encoding encodes values of type %s", description, encodingToken)); } // Handle ambiguity between reference vs. non-reference if (isReferenceType && nonReferenceType != null) { // If an explicit encoding was provided, assume they want the specified non-reference type if (encodingId != null) isReferenceType = false; else { throw new IllegalArgumentException(String.format("invalid %s: an explicit encoding() must be specified because" + " type %s is ambiguous, being both a @%s reference type and a simple Java type supported by type \"%s\"", description, encodingToken, PermazenType.class.getSimpleName(), nonReferenceType)); } } // Sanity check annotation some more if (!isReferenceType && annotation.inverseDelete() != DeleteAction.EXCEPTION) { throw new IllegalArgumentException(String.format( "invalid %s: %s() only allowed on reference fields", description, "inverseDelete")); } if (!isReferenceType && annotation.forwardDelete()) { throw new IllegalArgumentException(String.format( "invalid %s: %s() only allowed on reference fields", description, "forwardDelete")); } if (!isReferenceType && annotation.unique() && !annotation.indexed()) { throw new IllegalArgumentException(String.format( "invalid %s: use of %s() requires %s() = true", description, "unique", "indexed")); } if (!isReferenceType && annotation.forwardCascades().length != 0) { throw new IllegalArgumentException(String.format( "invalid %s: %s() only allowed on reference fields", description, "forwardCascades")); } if (!isReferenceType && annotation.inverseCascades().length != 0) { throw new IllegalArgumentException(String.format( "invalid %s: %s() only allowed on reference fields", description, "inverseCascades")); } if (isReferenceType && Stream.of(annotation.forwardCascades()).anyMatch(String::isEmpty)) { throw new IllegalArgumentException(String.format( "invalid %s: %s() may not contain an empty string", description, "forwardCascades")); } if (isReferenceType && Stream.of(annotation.inverseCascades()).anyMatch(String::isEmpty)) { throw new IllegalArgumentException(String.format( "invalid %s: %s() may not contain an empty string", description, "inverseCascades")); } // Create simple, enum, enum array, or reference field try { return isReferenceType ? new PermazenReferenceField(fieldName, storageId, fieldDescription, encodingToken, annotation, pclasses, getter, setter) : enumArrayEncoding != null ? new PermazenEnumArrayField(fieldName, storageId, enumType, enumArrayEncoding, enumArrayDimensions, nonReferenceType, annotation, fieldDescription, getter, setter) : enumType != null ? new PermazenEnumField(fieldName, storageId, enumType, annotation, fieldDescription, getter, setter) : new PermazenSimpleField(fieldName, storageId, encodingToken, nonReferenceType, annotation.indexed(), annotation, fieldDescription, getter, setter); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("invalid %s: %s", description, e.getMessage()), e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy