Skip to content

Commit

Permalink
Add missing support for object properties having Enum array type.
Browse files Browse the repository at this point in the history
  • Loading branch information
archiecobbs committed Feb 17, 2019
1 parent 575c2bb commit 6b73f18
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 113 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
@@ -1,5 +1,6 @@
Version Next Version Next


- Added missing support for object properties having Enum array type
- Added utility method AbstractXMLStreaming.newInvalidAttributeException() - Added utility method AbstractXMLStreaming.newInvalidAttributeException()
- Fixed bug where ArrayType.getDimensions() was sometimes incorrect - Fixed bug where ArrayType.getDimensions() was sometimes incorrect
- Added NullSafeType.getInnerType() - Added NullSafeType.getInnerType()
Expand Down
@@ -0,0 +1,51 @@

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

package io.permazen;

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

import io.permazen.core.FieldType;
import io.permazen.schema.AbstractEnumSchemaField;
import io.permazen.schema.SimpleSchemaField;

import java.lang.reflect.Method;

import org.dellroad.stuff.java.EnumUtil;

/**
* Support superclass for {@link JSimpleField}'s that involve an {@link Enum} type.
*/
abstract class AbstractEnumJSimpleField<A, B> extends ConvertedJSimpleField<A, B> {

final Class<? extends Enum<?>> enumType;

AbstractEnumJSimpleField(Permazen jdb, String name, int storageId, TypeToken<A> typeToken, FieldType<B> fieldType,
Class<? extends Enum<?>> enumType, boolean indexed, io.permazen.annotation.JField annotation, String description,
Method getter, Method setter, Converter<A, B> converter) {
super(jdb, name, storageId, typeToken, fieldType, indexed, annotation, description, getter, setter, converter);
Preconditions.checkArgument(enumType != null, "null enumType");
this.enumType = enumType;
}

@Override
@SuppressWarnings("unchecked")
void initialize(Permazen jdb, SimpleSchemaField schemaField0) {
super.initialize(jdb, schemaField0);
final AbstractEnumSchemaField schemaField = (AbstractEnumSchemaField)schemaField0;
schemaField.setType(null); // core API ignores "type" of Enum array fields
schemaField.getIdentifiers().clear();
for (Enum<?> value : EnumUtil.getValues(this.enumType))
schemaField.getIdentifiers().add(value.name());
}

@Override
@SuppressWarnings("unchecked")
Class<? extends Enum<?>> getEnumType() {
return this.enumType;
}
}
76 changes: 76 additions & 0 deletions permazen-main/src/main/java/io/permazen/ArrayConverter.java
@@ -0,0 +1,76 @@

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

package io.permazen;

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

import java.lang.reflect.Array;

class ArrayConverter<A, B> extends Converter<A[], B[]> {

private final Converter<A, B> converter;
private final Class<A> aType;
private final Class<B> bType;

@SuppressWarnings("unchecked")
ArrayConverter(Class<A> aType, Class<B> bType, Converter<A, B> converter) {
Preconditions.checkArgument(aType != null, "null aType");
Preconditions.checkArgument(bType != null, "null bType");
Preconditions.checkArgument(converter != null, "null converter");
this.aType = aType;
this.bType = bType;
this.converter = converter;
}

@Override
protected B[] doForward(A[] value) {
return ArrayConverter.convert(this.aType, this.bType, this.converter, value);
}

@Override
protected A[] doBackward(B[] value) {
return ArrayConverter.convert(this.bType, this.aType, this.converter.reverse(), value);
}

@SuppressWarnings("unchecked")
private static <X, Y> Y[] convert(Class<X> xType, Class<Y> yType, Converter<X, Y> converter, X[] value) {
if (value == null)
return null;
final int length = Array.getLength(value);
final Y[] result = (Y[])Array.newInstance(yType, length);
for (int i = 0; i < length; i++)
result[i] = converter.convert(value[i]);
return result;
}

// Object

@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || obj.getClass() != this.getClass())
return false;
final ArrayConverter<?, ?> that = (ArrayConverter<?, ?>)obj;
return this.aType == that.aType
&& this.bType == that.bType
&& this.converter.equals(that.converter);
}

@Override
public int hashCode() {
return this.aType.hashCode()
^ this.bType.hashCode()
^ this.converter.hashCode();
}

@Override
public String toString() {
return this.getClass().getSimpleName() + "[" + this.aType.getName() + "[]->" + this.bType.getName() + "[]]";
}
}

4 changes: 3 additions & 1 deletion permazen-main/src/main/java/io/permazen/ClassGenerator.java
Expand Up @@ -53,7 +53,7 @@ class ClassGenerator<T> {
static final String ID_FIELD_NAME = "$id"; static final String ID_FIELD_NAME = "$id";
static final String CACHED_VALUE_FIELD_PREFIX = "$cached_"; static final String CACHED_VALUE_FIELD_PREFIX = "$cached_";
static final String CACHED_FLAG_FIELD_PREFIX = "$cacheflags"; static final String CACHED_FLAG_FIELD_PREFIX = "$cacheflags";
static final String ENUM_CONVERTER_FIELD_PREFIX = "$ec"; static final String CONVERTER_FIELD_PREFIX = "$converter";
static final String FOLLOW_PATH_FIELD_PREFIX = "$followPath"; static final String FOLLOW_PATH_FIELD_PREFIX = "$followPath";


// JObject method handles // JObject method handles
Expand Down Expand Up @@ -84,6 +84,7 @@ class ClassGenerator<T> {


// EnumConverter method handles // EnumConverter method handles
static final Method ENUM_CONVERTER_CREATE_METHOD; static final Method ENUM_CONVERTER_CREATE_METHOD;
static final Method ENUM_CONVERTER_CREATE_ARRAY_METHOD;


// Transaction method handles // Transaction method handles
static final Method TRANSACTION_READ_SIMPLE_FIELD_METHOD; static final Method TRANSACTION_READ_SIMPLE_FIELD_METHOD;
Expand Down Expand Up @@ -148,6 +149,7 @@ class ClassGenerator<T> {


// EnumConverter // EnumConverter
ENUM_CONVERTER_CREATE_METHOD = EnumConverter.class.getMethod("createEnumConverter", Class.class); ENUM_CONVERTER_CREATE_METHOD = EnumConverter.class.getMethod("createEnumConverter", Class.class);
ENUM_CONVERTER_CREATE_ARRAY_METHOD = EnumConverter.class.getMethod("createEnumArrayConverter", Class.class, int.class);


// ObjDumper // ObjDumper
OBJ_DUMPER_TO_STRING_METHOD = ObjDumper.class.getMethod("toString", Transaction.class, ObjId.class, int.class); OBJ_DUMPER_TO_STRING_METHOD = ObjDumper.class.getMethod("toString", Transaction.class, ObjId.class, int.class);
Expand Down
118 changes: 118 additions & 0 deletions permazen-main/src/main/java/io/permazen/ConvertedJSimpleField.java
@@ -0,0 +1,118 @@

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

package io.permazen;

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

import io.permazen.core.FieldType;

import java.lang.reflect.Method;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
* Support superclass for {@link JSimpleField}'s that require convertion between Java and core API values.
*/
abstract class ConvertedJSimpleField<A, B> extends JSimpleField {

final Converter<A, B> converter; // converts Java value -> Core API value in the forward direction

ConvertedJSimpleField(Permazen jdb, String name, int storageId, TypeToken<A> typeToken, FieldType<B> fieldType,
boolean indexed, io.permazen.annotation.JField annotation, String description, Method getter, Method setter,
Converter<A, B> converter) {
super(jdb, name, storageId, typeToken, fieldType, indexed, annotation, description, getter, setter);
Preconditions.checkArgument(converter != null, "null converter");
this.converter = converter;
}

@Override
public Converter<B, A> getConverter(JTransaction jtx) {
return this.converter.reverse();
}

@Override
boolean isSameAs(JField that0) {
if (!super.isSameAs(that0))
return false;
final ConvertedJSimpleField<?, ?> that = (ConvertedJSimpleField)that0;
if (!this.converter.equals(that.converter))
return false;
return true;
}

// POJO import/export

@SuppressWarnings({ "rawtypes", "unchecked" })
Object importCoreValue(ImportContext context, Object value) {
return this.converter.convert((A)value);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
Object exportCoreValue(ExportContext context, Object value) {
return this.converter.reverse().convert((B)value);
}

// Bytecode generation

@Override
void outputFields(ClassGenerator<?> generator, ClassWriter cw) {
final FieldVisitor valueField = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
ClassGenerator.CONVERTER_FIELD_PREFIX + this.storageId, Type.getDescriptor(Converter.class), null, null);
valueField.visitEnd();
}

@Override
boolean hasClassInitializerBytecode() {
return true;
}

@Override
void outputClassInitializerBytecode(ClassGenerator<?> generator, MethodVisitor mv) {
this.outputCreateConverterBytecode(generator, mv);
mv.visitFieldInsn(Opcodes.PUTSTATIC, generator.getClassName(),
ClassGenerator.CONVERTER_FIELD_PREFIX + this.storageId, Type.getDescriptor(Converter.class));
}

abstract void outputCreateConverterBytecode(ClassGenerator<?> generator, MethodVisitor mv);

@Override
void outputMethods(final ClassGenerator<?> generator, ClassWriter cw) {

// 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));
mv.visitFieldInsn(Opcodes.GETSTATIC, generator.getClassName(),
ClassGenerator.CONVERTER_FIELD_PREFIX + this.storageId, Type.getDescriptor(Converter.class));
this.outputReadCoreValueBytecode(generator, mv);
generator.emitInvoke(mv, ClassGenerator.CONVERTER_CONVERT_METHOD);
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(this.getter.getReturnType()));
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();

// 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));
mv.visitFieldInsn(Opcodes.GETSTATIC, generator.getClassName(),
ClassGenerator.CONVERTER_FIELD_PREFIX + this.storageId, Type.getDescriptor(Converter.class));
generator.emitInvoke(mv, ClassGenerator.CONVERTER_REVERSE_METHOD);
mv.visitVarInsn(Opcodes.ALOAD, 1);
generator.emitInvoke(mv, ClassGenerator.CONVERTER_CONVERT_METHOD);
this.outputWriteCoreValueBytecode(generator, mv);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}

25 changes: 24 additions & 1 deletion permazen-main/src/main/java/io/permazen/EnumConverter.java
Expand Up @@ -12,6 +12,7 @@


import io.permazen.core.EnumValue; import io.permazen.core.EnumValue;


import java.lang.reflect.Array;
import java.util.EnumSet; import java.util.EnumSet;


/** /**
Expand Down Expand Up @@ -75,6 +76,29 @@ public static EnumConverter<?> createEnumConverter(Class<? extends Enum<?>> enum
return new EnumConverter(enumType); return new EnumConverter(enumType);
} }


/**
* Create a {@link Converter} for arrays based on converting the base elements with an {@link EnumConverter}.
*
* @param enumType base enum type
* @param dimensions number of array dimensions
* @return new converter
* @throws IllegalArgumentException if {@code enumType} is null
* @throws IllegalArgumentException if {@code dimemsions} is not between 1 and 255 (inclusive)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Converter<?, ?> createEnumArrayConverter(Class<? extends Enum<?>> enumType, int dimensions) {
Preconditions.checkArgument(dimensions >= 1 && dimensions <= 255, "invalid dimensions");
Class<?> aType = enumType;
Class<?> bType = EnumValue.class;
Converter<?, ?> converter = EnumConverter.createEnumConverter(enumType);
for (int i = 0; i < dimensions; i++) {
converter = new ArrayConverter(aType, bType, converter);
aType = Array.newInstance(aType, 0).getClass();
bType = Array.newInstance(bType, 0).getClass();
}
return converter;
}

// Object // Object


@Override @Override
Expand All @@ -97,4 +121,3 @@ public String toString() {
return this.getClass().getSimpleName() + "[type=" + this.enumType.getName() + "]"; return this.getClass().getSimpleName() + "[type=" + this.enumType.getName() + "]";
} }
} }

0 comments on commit 6b73f18

Please sign in to comment.