Skip to content

Commit

Permalink
Merge pull request #10666 from tkvw/feature-databinding-initializer
Browse files Browse the repository at this point in the history
Created BindInitializer annotation
  • Loading branch information
graemerocher committed Jun 19, 2017
2 parents 8cde2c5 + f3c3375 commit 62bc6c1
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 12 deletions.
@@ -0,0 +1,54 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package grails.databinding;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* This annotation may be applied to a a field to
* customize initialization of object properties in the data binding process.
*
* When the annotation is applied to a field, the value assigned to the
* annotation should be a Closure which accepts 1 parameter. The
* parameter is the object that data binding is being applied to.
* The value returned by the Closure will be bound to the field. The
* following code demonstrates using this technique to bind a contact
* to user with the same account as the user.
*
<pre>
class Contact{
Account account
String firstName
}
class User {
&#064;BindInitializer({
obj -> new Contact(account:obj.account)
})
Contact contact
Account account
}
</pre>
*
* @since 3.2.11
* @see BindingHelper
* @see DataBindingSource
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface BindInitializer {
Class<?> value();
}
Expand Up @@ -18,26 +18,22 @@ package grails.databinding
import grails.databinding.converters.FormattedValueConverter
import grails.databinding.converters.ValueConverter
import grails.databinding.events.DataBindingListener
import grails.databinding.initializers.ValueInitializer
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import groovy.util.slurpersupport.GPathResult
import org.grails.databinding.ClosureValueConverter
import org.grails.databinding.ClosureValueInitializer
import org.grails.databinding.IndexedPropertyReferenceDescriptor
import org.grails.databinding.converters.*
import org.grails.databinding.errors.SimpleBindingError
import org.grails.databinding.xml.GPathResultMap

import java.lang.annotation.Annotation
import java.lang.reflect.Array
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType

import grails.databinding.BindUsing
import org.grails.databinding.ClosureValueConverter
import org.grails.databinding.IndexedPropertyReferenceDescriptor
import org.grails.databinding.converters.ConversionService
import org.grails.databinding.converters.FormattedDateValueConverter
import org.grails.databinding.converters.StructuredCalendarBindingEditor
import org.grails.databinding.converters.StructuredDateBindingEditor
import org.grails.databinding.converters.StructuredSqlDateBindingEditor
import org.grails.databinding.errors.SimpleBindingError
import org.grails.databinding.xml.GPathResultMap

/**
* A data binder that will bind nested Maps to an object.
*
Expand Down Expand Up @@ -713,9 +709,51 @@ class SimpleDataBinder implements DataBinder {
}

protected initializeProperty(obj, String propName, Class propertyType, DataBindingSource source) {
obj[propName] = propertyType.newInstance()
def initializer = getPropertyInitializer(obj,propName)
if(initializer){
obj[propName] = initializer.initialize()
}
else{
obj[propName] = propertyType.newInstance()
}
}

protected ValueInitializer getPropertyInitializer(obj, String propName){
def initializer = getValueInitializerForField obj, propName
initializer
}

protected ValueInitializer getValueInitializerForField(obj, String propName) {
def initializer
try {
def field = getField(obj.getClass(), propName)
if (field) {
def annotation = field.getAnnotation(BindInitializer)
if (annotation) {
def valueClass = getValueOfBindInitializer(annotation)
if (Closure.isAssignableFrom(valueClass)) {
Closure closure = (Closure)valueClass.newInstance(null, null)
initializer = new ClosureValueInitializer(initializerClosure: closure.curry(obj), targetType: field.type)
}
}
}
} catch (Exception e) {
}
initializer
}
/**
* @param annotation An instance of grails.databinding.BindInitializer
* @return the value Class of the annotation
*/
protected Class getValueOfBindInitializer(Annotation annotation) {
assert annotation instanceof BindInitializer
def value
if(annotation instanceof BindInitializer) {
value = ((BindInitializer)annotation).value()
}
value
}

protected convert(Class typeToConvertTo, value) {
if (value == null || typeToConvertTo.isAssignableFrom(value?.getClass())) {
return value
Expand Down
@@ -0,0 +1,7 @@
package grails.databinding.initializers;


public interface ValueInitializer {
Object initialize();
Class<?> getTargetType();
}
@@ -0,0 +1,17 @@
package org.grails.databinding

import grails.databinding.initializers.ValueInitializer
import groovy.transform.CompileStatic

@CompileStatic
class ClosureValueInitializer implements ValueInitializer {

Closure initializerClosure
Class targetType


@Override
Object initialize() {
initializerClosure.call()
}
}
@@ -0,0 +1,32 @@
package grails.databinding

import spock.lang.Specification


class BindInitializerSpec extends Specification {

void 'Test BindInitializer for specific property'() {
given:
def binder = new SimpleDataBinder()
def obj = new ClassWithBindInitializerOnProperty()
when:
binder.bind(obj, new SimpleMapDataBindingSource(['association': [valueBound:'valueBound']]))

then:
obj.association.valueBound == 'valueBound'
obj.association.valueInitialized == 'valueInitialized'
}


static class ReferencedClass{
String valueInitialized
String valueBound
}
class ClassWithBindInitializerOnProperty {
@BindInitializer({
obj ->
new ReferencedClass(valueInitialized:'valueInitialized')
})
ReferencedClass association
}
}

0 comments on commit 62bc6c1

Please sign in to comment.