diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java index 4534219e1..287c33f66 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java @@ -40,355 +40,358 @@ public class DeployCreateProperties { private final DetermineManyType determineManyType; private final TypeManager typeManager; - - public DeployCreateProperties(TypeManager typeManager) { - this.typeManager = typeManager; - this.determineManyType = new DetermineManyType(); - } - - /** - * Create the appropriate properties for a bean. - */ - public void createProperties(DeployBeanDescriptor desc) { - - createProperties(desc, desc.getBeanType(), 0); - desc.sortProperties(); - - // check the transient properties... - Iterator it = desc.propertiesAll(); - - while (it.hasNext()) { - DeployBeanProperty prop = it.next(); - if (prop.isTransient()){ - if (prop.getWriteMethod() == null || prop.getReadMethod() == null){ - // Typically a helper method ... this is expected - logger.trace("... transient: "+prop.getFullBeanName()); - } else { - // dubious, possible error... - String msg = Message.msg("deploy.property.nofield", desc.getFullName(), prop.getName()); - logger.warn(msg); - } - } - } - } - /** - * Return true if we should ignore this field. - *

- * We want to ignore ebean internal fields and some others as well. - *

- */ - private boolean ignoreFieldByName(String fieldName) { - if (fieldName.startsWith("_ebean_")){ - // ignore Ebean internal fields - return true; - } - if (fieldName.startsWith("ajc$instance$")) { - // ignore AspectJ internal fields - return true; - } + public DeployCreateProperties(TypeManager typeManager) { + this.typeManager = typeManager; + this.determineManyType = new DetermineManyType(); + } - // we are interested in this field - return false; - } - - /** - * reflect the bean properties from Class. Some of these properties may not - * map to database columns. - */ - private void createProperties(DeployBeanDescriptor desc, Class beanType, int level) { + /** + * Create the appropriate properties for a bean. + */ + public void createProperties(DeployBeanDescriptor desc) { - boolean scalaObject = desc.isScalaObject(); + createProperties(desc, desc.getBeanType(), 0); + desc.sortProperties(); - try { - Method[] declaredMethods = beanType.getDeclaredMethods(); - Field[] fields = beanType.getDeclaredFields(); - - for (int i = 0; i < fields.length; i++) { - - Field field = fields[i]; - if (Modifier.isStatic(field.getModifiers())) { - // not interested in static fields - - } else if (Modifier.isTransient(field.getModifiers())) { - // not interested in transient fields - logger.trace("Skipping transient field "+field.getName()+" in "+beanType.getName()); + // check the transient properties... + Iterator it = desc.propertiesAll(); - } else if (ignoreFieldByName(field.getName())) { - // not interested this field (ebean or aspectJ field) - - } else { - - String fieldName = getFieldName(field, beanType); - String initFieldName = initCap(fieldName); - - Method getter = findGetter(field, initFieldName, declaredMethods, scalaObject); - Method setter = findSetter(field, initFieldName, declaredMethods, scalaObject); - - DeployBeanProperty prop = createProp(level, desc, field, beanType, getter, setter); - if (prop == null){ - // transient annotation on unsupported type - - } else { - // set a order that gives priority to inherited properties - // push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down - int sortOverride = prop.getSortOverride(); - prop.setSortOrder((level*10000+100-i + sortOverride)); - - DeployBeanProperty replaced = desc.addBeanProperty(prop); - if (replaced != null){ - if (replaced.isTransient()) { - // expected for inheritance... - } else { - String msg = "Huh??? property "+prop.getFullBeanName()+" being defined twice"; - msg += " but replaced property was not transient? This is not expected?"; - logger.warn(msg); - } - } - } - } - } - - Class superClass = beanType.getSuperclass(); - - if (!superClass.equals(Object.class)) { - // recursively add any properties in the inheritance heirarchy - // up to the Object.class level... - createProperties(desc, superClass, level + 1); - } - - } catch (PersistenceException ex) { - throw ex; - - } catch (Exception ex) { - throw new PersistenceException(ex); - } - } - - /** - * Make the first letter of the string upper case. - */ - private String initCap(String str){ - if (str.length() > 1){ - return Character.toUpperCase(str.charAt(0))+str.substring(1); - } else { - // only a single char - return str.toUpperCase(); - } - } - - /** - * Return the bean spec field name (trim of "is" from boolean types) - */ - private String getFieldName(Field field, Class beanType){ - - String name = field.getName(); - - if ((Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())) - && name.startsWith("is") && name.length() > 2){ - - // it is a boolean type field starting with "is" - char c = name.charAt(2); - if (Character.isUpperCase(c)){ - String msg = "trimming off 'is' from boolean field name "+name+" in class "+beanType.getName(); - logger.info(msg); - - return name.substring(2); - } - } - return name; - } - - /** - * Find a public non-static getter method that matches this field (according to bean-spec rules). - */ - private Method findGetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){ - - String methGetName = "get"+initFieldName; - String methIsName = "is"+initFieldName; - String scalaGet = field.getName(); - - for (int i = 0; i < declaredMethods.length; i++) { - Method m = declaredMethods[i]; - if ((scalaObject && m.getName().equals(scalaGet)) - || m.getName().equals(methGetName) || m.getName().equals(methIsName)){ - - Class[] params = m.getParameterTypes(); - if (params.length == 0){ - if (field.getType().equals(m.getReturnType())){ - int modifiers = m.getModifiers(); - if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { - // we find it... - return m; - } - } - } - } - } - return null; - } - - /** - * Find a public non-static setter method that matches this field (according to bean-spec rules). - */ - private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){ - - String methSetName = "set"+initFieldName; - String scalaSetName = field.getName()+"_$eq"; - - for (int i = 0; i < declaredMethods.length; i++) { - Method m = declaredMethods[i]; - - if ((scalaObject && m.getName().equals(scalaSetName)) - || m.getName().equals(methSetName)){ - - Class[] params = m.getParameterTypes(); - if (params.length == 1 && field.getType().equals(params[0])){ - if (void.class.equals(m.getReturnType())){ - int modifiers = m.getModifiers(); - if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { - return m; - } - } - } - } - } - return null; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private DeployBeanProperty createManyType(DeployBeanDescriptor desc, Class targetType, ManyType manyType) { - - try { - ScalarType scalarType = typeManager.getScalarType(targetType); - if (scalarType != null) { - return new DeployBeanPropertySimpleCollection(desc, targetType, scalarType, manyType); - } - } catch (NullPointerException e) { - logger.debug("expected non-scalar type"+e.getMessage()); - } - //TODO: Handle Collection of CompoundType and Embedded Type - return new DeployBeanPropertyAssocMany(desc, targetType, manyType); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private DeployBeanProperty createProp(DeployBeanDescriptor desc, Field field) { - - Class propertyType = field.getType(); - Class innerType = propertyType; - - // check for Collection type (list, set or map) - ManyType manyType = determineManyType.getManyType(propertyType); - - if (manyType != null) { - // List, Set or Map based object - Class targetType = determineTargetType(field); - if (targetType == null){ - Transient transAnnotation = field.getAnnotation(Transient.class); - if (transAnnotation != null) { - // not supporting this field (generic type used) - return null; - } - logger.warn("Could not find parameter type (via reflection) on "+desc.getFullName()+" "+field.getName()); - } - return createManyType(desc, targetType, manyType); - } - - if (innerType.isEnum() || innerType.isPrimitive()){ - return new DeployBeanProperty(desc, propertyType, null, null); - } - - ScalarType scalarType = typeManager.getScalarType(innerType); - if (scalarType != null) { - return new DeployBeanProperty(desc, propertyType, scalarType, null); - } - - CtCompoundType compoundType = typeManager.getCompoundType(innerType); - if (compoundType != null) { - return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null); - } - - if (!isTransientField(field)){ - try { - CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType); - if (checkImmutable.isImmutable()){ - if (checkImmutable.isCompoundType()){ - // use reflection to support compound immutable value objects - typeManager.recursiveCreateScalarDataReader(innerType); - compoundType = typeManager.getCompoundType(innerType); - if (compoundType != null) { - return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null); - } - - } else { - // use reflection to support simple immutable value objects - scalarType = typeManager.recursiveCreateScalarTypes(innerType); - return new DeployBeanProperty(desc, propertyType, scalarType, null); - } - } - } catch (Exception e){ - logger.error("Error with " + desc + " field:" + field.getName(), e); - } - } - - return new DeployBeanPropertyAssocOne(desc, propertyType); - } - - private boolean isTransientField(Field field) { - - Transient t = field.getAnnotation(Transient.class); - return (t != null); - } - - private DeployBeanProperty createProp(int level, DeployBeanDescriptor desc, Field field, Class beanType, Method getter, Method setter) { - - DeployBeanProperty prop = createProp(desc, field); - if (prop == null){ - // transient annotation on unsupported type - return null; + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop.isTransient()) { + if (prop.getWriteMethod() == null || prop.getReadMethod() == null) { + // Typically a helper method ... this is expected + logger.trace("... transient: " + prop.getFullBeanName()); } else { - prop.setOwningType(beanType); - prop.setName(field.getName()); - - // the getter or setter could be null if we are using - // javaagent type enhancement. If we are using subclass - // generation then we do need to find the getter and setter - prop.setReadMethod(getter); - prop.setWriteMethod(setter); - prop.setField(field); - return prop; + // dubious, possible error... + String msg = Message.msg("deploy.property.nofield", desc.getFullName(), prop.getName()); + logger.warn(msg); } + } + } + } + + /** + * Return true if we should ignore this field. + *

+ * We want to ignore ebean internal fields and some others as well. + *

+ */ + private boolean ignoreFieldByName(String fieldName) { + if (fieldName.startsWith("_ebean_")) { + // ignore Ebean internal fields + return true; + } + if (fieldName.startsWith("ajc$instance$")) { + // ignore AspectJ internal fields + return true; } - /** - * Determine the type of the List,Set or Map. Not been set explicitly so - * determine this from ParameterizedType. - */ - private Class determineTargetType(Field field) { - - Type genType = field.getGenericType(); - if (genType instanceof ParameterizedType) { - ParameterizedType ptype = (ParameterizedType) genType; + // we are interested in this field + return false; + } - Type[] typeArgs = ptype.getActualTypeArguments(); - if (typeArgs.length == 1) { - // probably a Set or List - if (typeArgs[0] instanceof Class){ - return (Class) typeArgs[0]; - } - //throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]); - return null; - } - if (typeArgs.length == 2) { - // this is probably a Map - if (typeArgs[1] instanceof ParameterizedType) { - // not supporting ParameterizedType on Map. - return null; - } - return (Class) typeArgs[1]; - } - } - // if targetType is null, then must be set in annotations - return null; - } + /** + * reflect the bean properties from Class. Some of these properties may not map to database + * columns. + */ + private void createProperties(DeployBeanDescriptor desc, Class beanType, int level) { + + boolean scalaObject = desc.isScalaObject(); + + try { + Method[] declaredMethods = beanType.getDeclaredMethods(); + Field[] fields = beanType.getDeclaredFields(); + + for (int i = 0; i < fields.length; i++) { + + Field field = fields[i]; + if (Modifier.isStatic(field.getModifiers())) { + // not interested in static fields + + } else if (Modifier.isTransient(field.getModifiers())) { + // not interested in transient fields + logger.trace("Skipping transient field " + field.getName() + " in " + beanType.getName()); + + } else if (ignoreFieldByName(field.getName())) { + // not interested this field (ebean or aspectJ field) + + } else { + + String fieldName = getFieldName(field, beanType); + String initFieldName = initCap(fieldName); + + Method getter = findGetter(field, initFieldName, declaredMethods, scalaObject); + Method setter = findSetter(field, initFieldName, declaredMethods, scalaObject); + + DeployBeanProperty prop = createProp(level, desc, field, beanType, getter, setter); + if (prop == null) { + // transient annotation on unsupported type + + } else { + // set a order that gives priority to inherited properties + // push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down + int sortOverride = prop.getSortOverride(); + prop.setSortOrder((level * 10000 + 100 - i + sortOverride)); + + DeployBeanProperty replaced = desc.addBeanProperty(prop); + if (replaced != null) { + if (replaced.isTransient()) { + // expected for inheritance... + } else { + String msg = "Huh??? property " + prop.getFullBeanName() + " being defined twice"; + msg += " but replaced property was not transient? This is not expected?"; + logger.warn(msg); + } + } + } + } + } + + Class superClass = beanType.getSuperclass(); + + if (!superClass.equals(Object.class)) { + // recursively add any properties in the inheritance heirarchy + // up to the Object.class level... + createProperties(desc, superClass, level + 1); + } + + } catch (PersistenceException ex) { + throw ex; + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * Make the first letter of the string upper case. + */ + private String initCap(String str) { + if (str.length() > 1) { + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } else { + // only a single char + return str.toUpperCase(); + } + } + + /** + * Return the bean spec field name (trim of "is" from boolean types) + */ + private String getFieldName(Field field, Class beanType) { + + String name = field.getName(); + + if ((Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())) && name.startsWith("is") + && name.length() > 2) { + + // it is a boolean type field starting with "is" + char c = name.charAt(2); + if (Character.isUpperCase(c)) { + String msg = "trimming off 'is' from boolean field name " + name + " in class " + beanType.getName(); + logger.info(msg); + + return name.substring(2); + } + } + return name; + } + + /** + * Find a public non-static getter method that matches this field (according to bean-spec rules). + */ + private Method findGetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject) { + + String methGetName = "get" + initFieldName; + String methIsName = "is" + initFieldName; + String scalaGet = field.getName(); + + for (int i = 0; i < declaredMethods.length; i++) { + Method m = declaredMethods[i]; + if ((scalaObject && m.getName().equals(scalaGet)) || m.getName().equals(methGetName) + || m.getName().equals(methIsName)) { + + Class[] params = m.getParameterTypes(); + if (params.length == 0) { + if (field.getType().equals(m.getReturnType())) { + int modifiers = m.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + // we find it... + return m; + } + } + } + } + } + return null; + } + + /** + * Find a public non-static setter method that matches this field (according to bean-spec rules). + */ + private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject) { + + String methSetName = "set" + initFieldName; + String scalaSetName = field.getName() + "_$eq"; + + for (int i = 0; i < declaredMethods.length; i++) { + Method m = declaredMethods[i]; + + if ((scalaObject && m.getName().equals(scalaSetName)) || m.getName().equals(methSetName)) { + + Class[] params = m.getParameterTypes(); + if (params.length == 1 && field.getType().equals(params[0])) { + if (void.class.equals(m.getReturnType())) { + int modifiers = m.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + return m; + } + } + } + } + } + return null; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private DeployBeanProperty createManyType(DeployBeanDescriptor desc, Class targetType, ManyType manyType) { + + try { + ScalarType scalarType = typeManager.getScalarType(targetType); + if (scalarType != null) { + return new DeployBeanPropertySimpleCollection(desc, targetType, scalarType, manyType); + } + } catch (NullPointerException e) { + logger.debug("expected non-scalar type" + e.getMessage()); + } + // TODO: Handle Collection of CompoundType and Embedded Type + return new DeployBeanPropertyAssocMany(desc, targetType, manyType); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private DeployBeanProperty createProp(DeployBeanDescriptor desc, Field field) { + + Class propertyType = field.getType(); + Class innerType = propertyType; + + // check for Collection type (list, set or map) + ManyType manyType = determineManyType.getManyType(propertyType); + + if (manyType != null) { + // List, Set or Map based object + Class targetType = determineTargetType(field); + if (targetType == null) { + Transient transAnnotation = field.getAnnotation(Transient.class); + if (transAnnotation != null) { + // not supporting this field (generic type used) + return null; + } + logger.warn("Could not find parameter type (via reflection) on " + desc.getFullName() + " " + field.getName()); + } + return createManyType(desc, targetType, manyType); + } + + if (innerType.isEnum() || innerType.isPrimitive()) { + return new DeployBeanProperty(desc, propertyType, null, null); + } + + ScalarType scalarType = typeManager.getScalarType(innerType); + if (scalarType != null) { + return new DeployBeanProperty(desc, propertyType, scalarType, null); + } + + CtCompoundType compoundType = typeManager.getCompoundType(innerType); + if (compoundType != null) { + return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null); + } + + if (isTransientField(field)) { + return null; + } + try { + CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType); + if (checkImmutable.isImmutable()) { + if (checkImmutable.isCompoundType()) { + // use reflection to support compound immutable value objects + typeManager.recursiveCreateScalarDataReader(innerType); + compoundType = typeManager.getCompoundType(innerType); + if (compoundType != null) { + return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null); + } + + } else { + // use reflection to support simple immutable value objects + scalarType = typeManager.recursiveCreateScalarTypes(innerType); + return new DeployBeanProperty(desc, propertyType, scalarType, null); + } + } + + return new DeployBeanPropertyAssocOne(desc, propertyType); + + } catch (Exception e) { + logger.error("Error with " + desc + " field:" + field.getName(), e); + return null; + } + } + + private boolean isTransientField(Field field) { + + Transient t = field.getAnnotation(Transient.class); + return (t != null); + } + + private DeployBeanProperty createProp(int level, DeployBeanDescriptor desc, Field field, Class beanType, + Method getter, Method setter) { + + DeployBeanProperty prop = createProp(desc, field); + if (prop == null) { + // transient annotation on unsupported type + return null; + } else { + prop.setOwningType(beanType); + prop.setName(field.getName()); + + // the getter or setter could be null if we are using + // javaagent type enhancement. If we are using subclass + // generation then we do need to find the getter and setter + prop.setReadMethod(getter); + prop.setWriteMethod(setter); + prop.setField(field); + return prop; + } + } + + /** + * Determine the type of the List,Set or Map. Not been set explicitly so determine this from + * ParameterizedType. + */ + private Class determineTargetType(Field field) { + + Type genType = field.getGenericType(); + if (genType instanceof ParameterizedType) { + ParameterizedType ptype = (ParameterizedType) genType; + + Type[] typeArgs = ptype.getActualTypeArguments(); + if (typeArgs.length == 1) { + // probably a Set or List + if (typeArgs[0] instanceof Class) { + return (Class) typeArgs[0]; + } + // throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]); + return null; + } + if (typeArgs.length == 2) { + // this is probably a Map + if (typeArgs[1] instanceof ParameterizedType) { + // not supporting ParameterizedType on Map. + return null; + } + return (Class) typeArgs[1]; + } + } + // if targetType is null, then must be set in annotations + return null; + } } diff --git a/src/test/java/com/avaje/tests/model/basic/TSMaster.java b/src/test/java/com/avaje/tests/model/basic/TSMaster.java index 453f87cac..a86311697 100644 --- a/src/test/java/com/avaje/tests/model/basic/TSMaster.java +++ b/src/test/java/com/avaje/tests/model/basic/TSMaster.java @@ -10,6 +10,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; +import javax.persistence.Transient; import com.avaje.ebean.annotation.PrivateOwned; @@ -30,6 +31,9 @@ public class TSMaster { boolean active; + @Transient + Object[] someArray; + @OneToMany(cascade=CascadeType.ALL,mappedBy="master") @PrivateOwned List details; @@ -80,4 +84,13 @@ public class TSMaster { } details.add(detail); } + + public Object[] getSomeArray() { + return someArray; + } + + public void setSomeArray(Object[] someArray) { + this.someArray = someArray; + } + }