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

groovy.util.ObjectGraphBuilder Maven / Gradle / Ivy

There is a newer version: 3.0.22
Show newest version
/*
 * Copyright 2003-2007 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 groovy.util;

import groovy.lang.Closure;
import groovy.lang.MetaProperty;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.codehaus.groovy.runtime.InvokerHelper;

/**
 * A builder for creating object graphs.
* Each node defines the class to be created and the property on its parent (if * any) at the same time. * * @author Scott Vlaminck (http://refactr.com) * @author Andres Almiray */ public class ObjectGraphBuilder extends FactoryBuilderSupport { public static final String NODE_CLASS = "_NODE_CLASS_"; public static final String NODE_NAME = "_NODE_NAME_"; public static final String OBJECT_ID = "_OBJECT_ID_"; private ChildPropertySetter childPropertySetter; private ClassNameResolver classNameResolver; private IdentifierResolver identifierResolver; private NewInstanceResolver newInstanceResolver; private ObjectFactory objectFactory = new ObjectFactory(); private ObjectRefFactory objectRefFactory = new ObjectRefFactory(); private ReferenceResolver referenceResolver; private RelationNameResolver relationNameResolver; private Map/* */resolvedClasses = new HashMap/* */(); private ClassLoader classLoader; public ObjectGraphBuilder() { classNameResolver = new DefaultClassNameResolver(); newInstanceResolver = new DefaultNewInstanceResolver(); relationNameResolver = new DefaultRelationNameResolver(); childPropertySetter = new DefaultChildPropertySetter(); identifierResolver = new DefaultIdentifierResolver(); referenceResolver = new DefaultReferenceResolver(); } /** * Returns the current ChildPropertySetter. */ public ChildPropertySetter getChildPropertySetter() { return childPropertySetter; } /** * Returns the classLoader used to load a node's class. */ public ClassLoader getClassLoader() { return classLoader; } /** * Returns the current ClassNameResolver. */ public ClassNameResolver getClassNameResolver() { return classNameResolver; } /** * Returns the current NewInstanceResolver. */ public NewInstanceResolver getNewInstanceResolver() { return newInstanceResolver; } /** * Returns the current RelationNameResolver. */ public RelationNameResolver getRelationNameResolver() { return relationNameResolver; } /** * Sets the current ChildPropertySetter.
* It will assign DefaultChildPropertySetter if null.
* It accepts a ChildPropertySetter instance or a Closure. */ public void setChildPropertySetter( final Object childPropertySetter ) { if( childPropertySetter instanceof ChildPropertySetter ){ this.childPropertySetter = (ChildPropertySetter) childPropertySetter; }else if( childPropertySetter instanceof Closure ){ this.childPropertySetter = new ChildPropertySetter(){ public void setChild( Object parent, Object child, String parentName, String propertyName ) { ((Closure) childPropertySetter).call( new Object[] { parent, child, parentName, propertyName } ); } }; }else{ this.childPropertySetter = new DefaultChildPropertySetter(); } } /** * Sets the classLoader used to load a node's class. */ public void setClassLoader( ClassLoader classLoader ){ this.classLoader = classLoader; } /** * Sets the current ClassNameResolver.
* It will assign DefaultClassNameResolver if null.
* It accepts a ClassNameResolver instance, a String or a Closure. */ public void setClassNameResolver( final Object classNameResolver ) { if( classNameResolver instanceof ClassNameResolver ){ this.classNameResolver = (ClassNameResolver) classNameResolver; }else if( classNameResolver instanceof String ){ this.classNameResolver = new ClassNameResolver(){ public String resolveClassname( String classname ) { return classNameResolver + "." + classname.substring( 0, 1 ) .toUpperCase() + classname.substring( 1 ); } }; }else if( classNameResolver instanceof Closure ){ this.classNameResolver = new ClassNameResolver(){ public String resolveClassname( String classname ) { return (String) ((Closure) classNameResolver).call( new Object[] { classname } ); } }; }else{ this.classNameResolver = new DefaultClassNameResolver(); } } /** * Sets the current IdentifierResolver.
* It will assign DefaultIdentifierResolver if null.
* It accepts a IdentifierResolver instance or a Closure. */ public void setIdentifierResolver( final Object identifierResolver ) { if( identifierResolver instanceof IdentifierResolver ){ this.identifierResolver = (IdentifierResolver) identifierResolver; }else if( identifierResolver instanceof Closure ){ this.identifierResolver = new IdentifierResolver(){ public String getIdentifierFor( String nodeName ) { return (String) (((Closure) identifierResolver).call( new Object[] { nodeName } )); } }; }else{ this.identifierResolver = new DefaultIdentifierResolver(); } } /** * Sets the current NewInstanceResolver.
* It will assign DefaultNewInstanceResolver if null.
* It accepts a NewInstanceResolver instance or a Closure. */ public void setNewInstanceResolver( final Object newInstanceResolver ) { if( newInstanceResolver instanceof NewInstanceResolver ){ this.newInstanceResolver = (NewInstanceResolver) newInstanceResolver; }else if( newInstanceResolver instanceof Closure ){ this.newInstanceResolver = new NewInstanceResolver(){ public Object newInstance( Class klass, Map attributes ) throws InstantiationException, IllegalAccessException { return ((Closure) newInstanceResolver).call( new Object[] { klass, attributes } ); } }; }else{ this.newInstanceResolver = new DefaultNewInstanceResolver(); } } /** * Sets the current ReferenceResolver.
* It will assign DefaultReferenceResolver if null.
* It accepts a ReferenceResolver instance or a Closure. */ public void setReferenceResolver( final Object referenceResolver ) { if( referenceResolver instanceof ReferenceResolver ){ this.referenceResolver = (ReferenceResolver) referenceResolver; }else if( referenceResolver instanceof Closure ){ this.referenceResolver = new ReferenceResolver(){ public String getReferenceFor( String nodeName ) { return (String) (((Closure) referenceResolver).call( new Object[] { nodeName } )); } }; }else{ this.referenceResolver = new DefaultReferenceResolver(); } } /** * Sets the current RelationNameResolver.
* It will assign DefaultRelationNameResolver if null. */ public void setRelationNameResolver( RelationNameResolver relationNameResolver ) { this.relationNameResolver = relationNameResolver != null ? relationNameResolver : new DefaultRelationNameResolver(); } protected void postInstantiate( Object name, Map attributes, Object node ) { super.postInstantiate( name, attributes, node ); Map context = getContext(); String objectId = (String) context.get( OBJECT_ID ); if( objectId != null && node != null ){ setVariable( objectId, node ); } } protected void preInstantiate( Object name, Map attributes, Object value ) { super.preInstantiate( name, attributes, value ); Map context = getContext(); context.put( OBJECT_ID, attributes.remove( identifierResolver.getIdentifierFor( (String) name ) ) ); } protected Factory resolveFactory( Object name, Map attributes, Object value ) { // let custom factories to be resolved first Factory factory = super.resolveFactory( name, attributes, value ); if( factory != null ){ return factory; } if( attributes.get( referenceResolver.getReferenceFor( (String) name ) ) != null ){ return objectRefFactory; } return objectFactory; } /** * Strategy for setting a child node on its parent.
* Useful for handling Lists/Arrays vs normal properties. */ public interface ChildPropertySetter { /** * @param parent the parent's node value * @param child the child's node value * @param parentName the name of the parent node * @param propertyName the resolved relation name of the child */ void setChild( Object parent, Object child, String parentName, String propertyName ); } /** * Strategy for resolving a classname. */ public interface ClassNameResolver { /** * @param classname the node name as written on the building code */ String resolveClassname( String classname ); } /** * Default impl that calls parent.propertyName = child
* If parent.propertyName is a Collection it will try to add child to the * collection. */ public static class DefaultChildPropertySetter implements ChildPropertySetter { public void setChild( Object parent, Object child, String parentName, String propertyName ) { Object property = InvokerHelper.getProperty( parent, propertyName ); if( property != null && Collection.class.isAssignableFrom( property.getClass() ) ){ ((Collection) property).add( child ); }else{ InvokerHelper.setProperty( parent, propertyName, child ); } } } /** * Default impl that capitalizes the classname. */ public static class DefaultClassNameResolver implements ClassNameResolver { public String resolveClassname( String classname ) { if( classname.length() == 1 ){ return classname.toUpperCase(); } return classname.substring( 0, 1 ) .toUpperCase() + classname.substring( 1 ); } } /** * Default impl, always returns 'id' */ public static class DefaultIdentifierResolver implements IdentifierResolver { public String getIdentifierFor( String nodeName ) { return "id"; } } /** * Default impl that calls Class.newInstance() */ public static class DefaultNewInstanceResolver implements NewInstanceResolver { public Object newInstance( Class klass, Map attributes ) throws InstantiationException, IllegalAccessException { return klass.newInstance(); } } /** * Default impl, always returns 'refId' */ public static class DefaultReferenceResolver implements ReferenceResolver { public String getReferenceFor( String nodeName ) { return "refId"; } } /** * Default impl that returns parentName & childName accordingly. */ public static class DefaultRelationNameResolver implements RelationNameResolver { /** * Follow the most conventional plural in English, add 's' to childName.
* If the property does not exist then it will return childName * unchanged. */ public String resolveChildRelationName( String parentName, Object parent, String childName, Object child ) { MetaProperty metaProperty = InvokerHelper.getMetaClass( parent ) .hasProperty( parent, childName + "s" ); if( metaProperty != null ){ return childName + "s"; } return childName; } /** * Follow the most conventional pattern, returns the parentName * unchanged. */ public String resolveParentRelationName( String parentName, Object parent, String childName, Object child ) { return parentName; } } /** * Strategy for picking the correct synthetic identifier. */ public interface IdentifierResolver { /** * Returns the name of the property that will identify the node.
* * @param nodeName the name of the node */ String getIdentifierFor( String nodeName ); } /** * Strategy for creating new instances of a class.
* Useful for plug-in calls to non-default constructors. */ public interface NewInstanceResolver { /** * Create a new instance of Class klass. * * @param klass the resolved class name * @param attributes the attribute Map available for the node */ Object newInstance( Class klass, Map attributes ) throws InstantiationException, IllegalAccessException; } /** * Strategy for picking the correct synthetic reference identifier. */ public interface ReferenceResolver { /** * Returns the name of the property that references another node.
* * @param nodeName the name of the node */ String getReferenceFor( String nodeName ); } /** * Strategy for resolving a relationship property name. */ public interface RelationNameResolver { /** * Returns the mapping name of child -> parent * * @param parentName the name of the parent node * @param parent the parent node * @param childName the name of the child node * @param child the child node */ String resolveChildRelationName( String parentName, Object parent, String childName, Object child ); /** * Returns the mapping name of parent -> child * * @param parentName the name of the parent node * @param parent the parent node * @param childName the name of the child node * @param child the child node */ String resolveParentRelationName( String parentName, Object parent, String childName, Object child); } private static class ObjectFactory extends AbstractFactory { public Object newInstance( FactoryBuilderSupport builder, Object name, Object value, Map properties ) throws InstantiationException, IllegalAccessException { ObjectGraphBuilder ogbuilder = (ObjectGraphBuilder) builder; String classname = ogbuilder.classNameResolver.resolveClassname( (String) name ); Class klass = (Class) ogbuilder.resolvedClasses.get( classname ); if( klass == null ){ klass = loadClass( ogbuilder.classLoader, classname ); if( klass == null ){ klass = loadClass( ogbuilder.getClass().getClassLoader(), classname ); } if( klass == null ){ try{ klass = Class.forName( classname ); }catch( ClassNotFoundException e ){ // ignore } } if( klass == null ){ klass = loadClass( Thread.currentThread().getContextClassLoader(), classname ); } if( klass == null ){ throw new RuntimeException(new ClassNotFoundException(classname)); } ogbuilder.resolvedClasses.put( classname, klass ); } Map context = ogbuilder.getContext(); context.put( ObjectGraphBuilder.NODE_NAME, name ); context.put( ObjectGraphBuilder.NODE_CLASS, klass ); return ogbuilder.newInstanceResolver.newInstance( klass, properties ); } public void setChild( FactoryBuilderSupport builder, Object parent, Object child ) { ObjectGraphBuilder ogbuilder = (ObjectGraphBuilder) builder; if( parent != null ){ Map context = ogbuilder.getContext(); Map parentContext = ogbuilder.getParentContext(); String parentName = null; String childName = (String) context.get( NODE_NAME ); Class parentClass = null; Class childClass = (Class) context.get( NODE_CLASS ); if( parentContext != null ){ parentName = (String) parentContext.get( NODE_NAME ); parentClass = (Class) parentContext.get( NODE_CLASS ); } String propertyName = ogbuilder.relationNameResolver.resolveParentRelationName( parentName, parent, childName, child ); MetaProperty metaProperty = InvokerHelper.getMetaClass( child ) .hasProperty( child, propertyName ); if( metaProperty != null ){ metaProperty.setProperty( child, parent ); } } } public void setParent( FactoryBuilderSupport builder, Object parent, Object child ) { ObjectGraphBuilder ogbuilder = (ObjectGraphBuilder) builder; if( parent != null ){ Map context = ogbuilder.getContext(); Map parentContext = ogbuilder.getParentContext(); String parentName = null; String childName = (String) context.get( NODE_NAME ); Class parentClass = null; Class childClass = (Class) context.get( NODE_CLASS ); if( parentContext != null ){ parentName = (String) parentContext.get( NODE_NAME ); parentClass = (Class) parentContext.get( NODE_CLASS ); } ogbuilder.childPropertySetter.setChild( parent, child, parentName, ogbuilder.relationNameResolver.resolveChildRelationName( parentName, parent, childName, child ) ); } } private Class loadClass( ClassLoader classLoader, String classname ){ if( classLoader == null || classname == null ){ return null; } try{ return classLoader.loadClass( classname ); }catch( ClassNotFoundException e ){ return null; } } } private static class ObjectRefFactory extends ObjectFactory { public boolean isLeaf() { return true; } public Object newInstance( FactoryBuilderSupport builder, Object name, Object value, Map properties ) throws InstantiationException, IllegalAccessException { ObjectGraphBuilder ogbuilder = (ObjectGraphBuilder) builder; String refProperty = ogbuilder.referenceResolver.getReferenceFor( (String) name ); String refId = (String) properties.remove( refProperty ); Object object = ogbuilder.getProperty( refId ); if( object == null ){ throw new IllegalArgumentException( "There is no previous node with " + ogbuilder.identifierResolver.getIdentifierFor( (String) name ) + "=" + refId ); } if( !properties.isEmpty() ){ throw new IllegalArgumentException( "You can not modify the properties of a referenced object." ); } Map context = ogbuilder.getContext(); context.put( ObjectGraphBuilder.NODE_NAME, name ); context.put( ObjectGraphBuilder.NODE_CLASS, object.getClass() ); return object; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy