Skip to content
Permalink
Browse files

Add missing support for object properties having Enum array type.

  • Loading branch information...
archiecobbs committed Feb 17, 2019
1 parent 575c2bb commit 6b73f1894fbae516c7f57e9e9e143d53776f10f5
@@ -1,5 +1,6 @@
Version Next

- Added missing support for object properties having Enum array type
- Added utility method AbstractXMLStreaming.newInvalidAttributeException()
- Fixed bug where ArrayType.getDimensions() was sometimes incorrect
- Added NullSafeType.getInnerType()
@@ -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;
}
}
@@ -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() + "[]]";
}
}

@@ -53,7 +53,7 @@
static final String ID_FIELD_NAME = "$id";
static final String CACHED_VALUE_FIELD_PREFIX = "$cached_";
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";

// JObject method handles
@@ -84,6 +84,7 @@

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

// Transaction method handles
static final Method TRANSACTION_READ_SIMPLE_FIELD_METHOD;
@@ -148,6 +149,7 @@

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

// ObjDumper
OBJ_DUMPER_TO_STRING_METHOD = ObjDumper.class.getMethod("toString", Transaction.class, ObjId.class, int.class);
@@ -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();
}
}

@@ -12,6 +12,7 @@

import io.permazen.core.EnumValue;

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

/**
@@ -75,6 +76,29 @@ protected T doBackward(EnumValue enumValue) {
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

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

Oops, something went wrong.

0 comments on commit 6b73f18

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.