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

org.jsimpledb.JSimpleField Maven / Gradle / Ivy


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

package org.jsimpledb;

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

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.dellroad.stuff.java.Primitive;
import org.jsimpledb.core.FieldType;
import org.jsimpledb.core.ObjId;
import org.jsimpledb.schema.SimpleSchemaField;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * Represents a simple field in a {@link JClass} or a simple sub-field of a complex field in a {@link JClass}.
 */
public class JSimpleField extends JField {

    final TypeToken typeToken;
    final FieldType fieldType;
    final boolean indexed;
    final boolean unique;
    final ArrayList uniqueExcludes;         // note: these are core API values, sorted by this.fieldType
    final Method setter;

    JSimpleField(JSimpleDB jdb, String name, int storageId, TypeToken typeToken, String typeName, boolean indexed,
      org.jsimpledb.annotation.JField annotation, String description, Method getter, Method setter) {
        this(jdb, name, storageId, typeToken,
          jdb.db.getFieldTypeRegistry().getFieldType(typeName), indexed, annotation, description, getter, setter);
    }

    @SuppressWarnings("unchecked")
    JSimpleField(JSimpleDB jdb, String name, int storageId, TypeToken typeToken, FieldType fieldType, boolean indexed,
      org.jsimpledb.annotation.JField annotation, String description, Method getter, Method setter) {
        super(jdb, name, storageId, description, getter);
        Preconditions.checkArgument(typeToken != null, "null typeToken");
        Preconditions.checkArgument(fieldType != null, "null fieldType");
        Preconditions.checkArgument(annotation != null, "null annotation");
        this.typeToken = typeToken;
        this.fieldType = fieldType;
        this.indexed = indexed;
        this.unique = annotation.unique();
        this.setter = setter;

        // Parse uniqueExcludes
        final int numExcludes = annotation.uniqueExclude().length + (annotation.uniqueExcludeNull() ? 1 : 0);
        if (numExcludes > 0) {
            this.uniqueExcludes = new ArrayList<>(numExcludes);
            if (annotation.uniqueExcludeNull())
                this.uniqueExcludes.add(null);
            for (String string : annotation.uniqueExclude()) {
                try {
                    this.uniqueExcludes.add(this.fieldType.fromString(string));
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException("invalid uniqueExclude() value `" + string + "': " + e.getMessage(), e);
                }
            }
            Collections.sort(this.uniqueExcludes, (Comparator)this.fieldType);
        } else
            this.uniqueExcludes = null;
    }

    /**
     * Get the {@link JComplexField} of which this instance is a sub-field, if any.
     *
     * @return parent {@link JComplexField}, or null if this instance is not a sub-field
     */
    public JComplexField getParentField() {
        return this.parent instanceof JComplexField ? (JComplexField)this.parent : null;
    }

    /**
     * Get the type of this field.
     *
     * @return this field's Java type
     */
    public TypeToken getTypeToken() {
        return this.typeToken;
    }

    /**
     * Get the {@link org.jsimpledb.core.FieldType} used by the core API to encode this field's values.
     *
     * 

* Note that for {@link Enum} and reference fields, the core API uses a different type than the Java model * classes ({@link org.jsimpledb.core.EnumValue} and {@link org.jsimpledb.core.ObjId}, respectively). * Values can always be properly converted using the {@link Converter} returned by {@link #getConverter getConverter()}. * * @return this field's core-layer type definition */ public FieldType getFieldType() { return this.fieldType; } /** * Get whether this field is indexed. * * @return whether this field is indexed */ public boolean isIndexed() { return this.indexed; } /** * Get the setter method associated with this field. * * @return field property setter method, or null if this field is a sub-field of a complex field */ public Method getSetter() { return this.setter; } @Override public Object getValue(JObject jobj) { Preconditions.checkArgument(jobj != null, "null jobj"); return jobj.getTransaction().readSimpleField(jobj.getObjId(), this.storageId, false); } @Override public R visit(JFieldSwitch target) { return target.caseJSimpleField(this); } /** * Get a {@link Converter} that converts this field values between core API type and Java model type. * Only {@link Enum} and reference types require conversion; for all other types, this returns an identity converter. * * @param jtx transaction * @return {@link Converter} from core API field type to Java model field type */ public Converter getConverter(JTransaction jtx) { return Converter.identity(); } /** * Set the Java value of this field in the given object. * Does not alter the schema version of the object. * * @param jobj object containing this field * @param value new value * @throws org.jsimpledb.core.DeletedObjectException if {@code jobj} does not exist in its associated {@link JTransaction} * @throws org.jsimpledb.core.StaleTransactionException if the {@link JTransaction} associated with {@code jobj} * is no longer usable * @throws IllegalArgumentException if {@code value} is not an appropriate value for this field * @throws IllegalArgumentException if {@code jobj} is null */ public void setValue(JObject jobj, Object value) { Preconditions.checkArgument(jobj != null, "null jobj"); jobj.getTransaction().writeSimpleField(jobj, this.storageId, value, false); } @Override boolean isSameAs(JField that0) { if (!super.isSameAs(that0)) return false; final JSimpleField that = (JSimpleField)that0; if (!this.typeToken.equals(that.typeToken)) return false; if (!this.fieldType.equals(that.fieldType)) return false; if (this.indexed != that.indexed) return false; if (this.unique != that.unique) return false; if (!(this.uniqueExcludes != null ? this.uniqueExcludes.equals(that.uniqueExcludes) : that.uniqueExcludes == null)) return false; return true; } @Override SimpleSchemaField toSchemaItem(JSimpleDB jdb) { final SimpleSchemaField schemaField = new SimpleSchemaField(); this.initialize(jdb, schemaField); return schemaField; } @Override void calculateRequiresDefaultValidation() { super.calculateRequiresDefaultValidation(); this.requiresDefaultValidation |= this.unique; } void initialize(JSimpleDB jdb, SimpleSchemaField schemaField) { super.initialize(jdb, schemaField); schemaField.setType(this.fieldType.getName()); schemaField.setIndexed(this.indexed); } // Bytecode generation @Override void outputFields(ClassGenerator generator, ClassWriter cw) { // Output cached value field this.outputCachedValueField(generator, cw); } @Override void outputMethods(final ClassGenerator generator, ClassWriter cw) { // Get field type final String className = generator.getClassName(); final Class propertyType = this.typeToken.getRawType(); final boolean wide = propertyType.isPrimitive() && (Primitive.get(propertyType).getSize() == 8); // Get 'cached' flags field and bit offset final String cachedFlagFieldName = generator.getCachedFlagFieldName(this); final int cachedFlagBit = generator.getCachedFlagBit(this); // Start getter MethodVisitor mv = cw.visitMethod( this.getter.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED), this.getter.getName(), Type.getMethodDescriptor(this.getter), null, generator.getExceptionNames(this.getter)); // Return the cached value, if any mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, className, cachedFlagFieldName, Type.getDescriptor(generator.getCachedFlagFieldType(this))); switch (cachedFlagBit) { case 1: mv.visitInsn(Opcodes.ICONST_1); break; case 2: mv.visitInsn(Opcodes.ICONST_2); break; case 4: mv.visitInsn(Opcodes.ICONST_4); break; default: mv.visitLdcInsn(cachedFlagBit); break; } mv.visitInsn(Opcodes.IAND); final Label notCached = new Label(); mv.visitJumpInsn(Opcodes.IFEQ, notCached); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, className, this.getCachedValueFieldName(), Type.getDescriptor(propertyType)); mv.visitInsn(Type.getType(propertyType).getOpcode(Opcodes.IRETURN)); mv.visitLabel(notCached); mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]); // Retrieve (and unwrap if necessary) the value mv.visitVarInsn(Opcodes.ALOAD, 0); this.outputReadCoreValueBytecode(generator, mv); mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(this.typeToken.wrap().getRawType())); if (propertyType.isPrimitive()) generator.unwrap(mv, Primitive.get(propertyType)); // Cache the retrieved value mv.visitInsn(wide ? Opcodes.DUP2_X1 : Opcodes.DUP_X1); mv.visitFieldInsn(Opcodes.PUTFIELD, className, this.getCachedValueFieldName(), Type.getDescriptor(propertyType)); // Set the flag indicating cached value is valid mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitInsn(Opcodes.DUP); mv.visitFieldInsn(Opcodes.GETFIELD, className, cachedFlagFieldName, Type.getDescriptor(generator.getCachedFlagFieldType(this))); switch (cachedFlagBit) { case 1: mv.visitInsn(Opcodes.ICONST_1); break; case 2: mv.visitInsn(Opcodes.ICONST_2); break; case 4: mv.visitInsn(Opcodes.ICONST_4); break; default: mv.visitLdcInsn(cachedFlagBit); break; } mv.visitInsn(Opcodes.IOR); mv.visitFieldInsn(Opcodes.PUTFIELD, className, cachedFlagFieldName, Type.getDescriptor(generator.getCachedFlagFieldType(this))); // Done with getter mv.visitInsn(Type.getType(propertyType).getOpcode(Opcodes.IRETURN)); mv.visitMaxs(0, 0); mv.visitEnd(); // Start setter mv = cw.visitMethod( this.setter.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED), this.setter.getName(), Type.getMethodDescriptor(this.setter), null, generator.getExceptionNames(this.setter)); // Get new value and prepare stack for PUTFIELD mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Type.getType(propertyType).getOpcode(Opcodes.ILOAD), 1); // Wrap (if necessary) and write the value to the field mv.visitInsn(wide ? Opcodes.DUP2 : Opcodes.DUP); if (this.typeToken.isPrimitive()) generator.wrap(mv, Primitive.get(propertyType)); this.outputWriteCoreValueBytecode(generator, mv); // Now cache the value - only after a successful write mv.visitFieldInsn(Opcodes.PUTFIELD, className, this.getCachedValueFieldName(), Type.getDescriptor(propertyType)); // Set the flag indicating cached value is valid mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitInsn(Opcodes.DUP); mv.visitFieldInsn(Opcodes.GETFIELD, className, cachedFlagFieldName, Type.getDescriptor(generator.getCachedFlagFieldType(this))); switch (cachedFlagBit) { case 1: mv.visitInsn(Opcodes.ICONST_1); break; case 2: mv.visitInsn(Opcodes.ICONST_2); break; case 4: mv.visitInsn(Opcodes.ICONST_4); break; default: mv.visitLdcInsn(cachedFlagBit); break; } mv.visitInsn(Opcodes.IOR); mv.visitFieldInsn(Opcodes.PUTFIELD, className, cachedFlagFieldName, Type.getDescriptor(generator.getCachedFlagFieldType(this))); // Done with setter mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } void outputReadCoreValueBytecode(ClassGenerator generator, MethodVisitor mv) { // this.$tx.getTransaction().readSimpleField(this.id, STORAGEID, true) mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, generator.getClassName(), ClassGenerator.TX_FIELD_NAME, Type.getDescriptor(JTransaction.class)); generator.emitInvoke(mv, ClassGenerator.GET_TRANSACTION_METHOD); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, generator.getClassName(), ClassGenerator.ID_FIELD_NAME, Type.getDescriptor(ObjId.class)); mv.visitLdcInsn(this.storageId); mv.visitInsn(Opcodes.ICONST_1); generator.emitInvoke(mv, ClassGenerator.TRANSACTION_READ_SIMPLE_FIELD_METHOD); } void outputWriteCoreValueBytecode(ClassGenerator generator, MethodVisitor mv) { // JTransaction.registerJObject(this); mv.visitVarInsn(Opcodes.ALOAD, 0); generator.emitInvoke(mv, ClassGenerator.REGISTER_JOBJECT_METHOD); // this.$tx.getTransaction().writeSimpleField(this.id, STORAGEID, STACK[0], true) mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, generator.getClassName(), ClassGenerator.TX_FIELD_NAME, Type.getDescriptor(JTransaction.class)); generator.emitInvoke(mv, ClassGenerator.GET_TRANSACTION_METHOD); mv.visitInsn(Opcodes.SWAP); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, generator.getClassName(), ClassGenerator.ID_FIELD_NAME, Type.getDescriptor(ObjId.class)); mv.visitInsn(Opcodes.SWAP); mv.visitLdcInsn(this.storageId); mv.visitInsn(Opcodes.SWAP); mv.visitInsn(Opcodes.ICONST_1); generator.emitInvoke(mv, ClassGenerator.TRANSACTION_WRITE_SIMPLE_FIELD_METHOD); } @Override final JSimpleFieldInfo toJFieldInfo() { return this.toJFieldInfo(0); } JSimpleFieldInfo toJFieldInfo(int parentStorageId) { return new JSimpleFieldInfo(this, parentStorageId); } }