<commit_before>/*
 * 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.GroovyObjectSupport;
import groovy.lang.MissingMethodException;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.codehaus.groovy.runtime.InvokerHelper;

/**
 * Mix of BuilderSupport and SwingBuilder's factory support.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <NAME> <<EMAIL>>
 */
public abstract class FactoryBuilderSupport extends GroovyObjectSupport {
    public static final String CURRENT_FACTORY = "_CURRENT_FACTORY_";
    public static final String CURRENT_NODE = "_CURRENT_NODE_";
    private static final Logger LOG = Logger.getLogger( FactoryBuilderSupport.class.getName() );

    /**
     * Throws an exception if value is null.
     */
    public static void checkValueIsNull( Object value, Object name ) {
        if( value != null ){
            throw new RuntimeException( "'" + name + "' elements do not accept a value argument." );
        }
    }

    /**
     * Returns true if type is assignale to the value's class, false if value is
     * null.
     */
    public static boolean checkValueIsType( Object value, Object name, Class type ) {
        if( value != null ){
            if( type.isAssignableFrom( value.getClass() ) ){
                return true;
            }else{
                throw new RuntimeException( "The value argument of '" + name + "' must be of type "
                        + type.getName() );
            }
        }else{
            return false;
        }
    }

    /**
     * Returns true if type is assignale to the value's class, false if value is
     * null or a String.
     */
    public static boolean checkValueIsTypeNotString( Object value, Object name, Class type ) {
        if( value != null ){
            if( type.isAssignableFrom( value.getClass() ) ){
                return true;
            }else if( value instanceof String ){
                return false;
            }else{
                throw new RuntimeException( "The value argument of '" + name + "' must be of type "
                        + type.getName() + " or a String." );
            }
        }else{
            return false;
        }
    }

    private LinkedList contexts = new LinkedList();
    private LinkedList/*<Closure>*/ attributeDelegates = new LinkedList/*<Closure>*/(); //
    private Map factories = new HashMap();
    private Closure nameMappingClosure;
    private FactoryBuilderSupport proxyBuilder;

    public FactoryBuilderSupport() {
        this.proxyBuilder = this;
    }

    public FactoryBuilderSupport( Closure nameMappingClosure ) {
        this.nameMappingClosure = nameMappingClosure;
        this.proxyBuilder = this;
    }

    public Map getContext() {
        if( !contexts.isEmpty() ){
            return (Map) contexts.peek();
        }
        return null;
    }

    public Object getCurrent() {
        if( !contexts.isEmpty() ){
            Map context = (Map) contexts.peek();
            return context.get( CURRENT_NODE );
        }
        return null;
    }

    public Factory getCurrentFactory() {
        if( !contexts.isEmpty() ){
            Map context = (Map) contexts.peek();
            return (Factory) context.get( CURRENT_FACTORY );
        }
        return null;
    }

    /**
     * Convenience method when no arguments are required
     *
     * @return the result of the call
     * @param methodName the name of the method to invoke
     */
    public Object invokeMethod( String methodName ) {
        return invokeMethod( methodName, null );
    }

    public Object invokeMethod( String methodName, Object args ) {
        Object name = getName( methodName );
        Object result = null;
        try{
            result = doInvokeMethod( methodName, name, args );
        }catch( RuntimeException e ){
            reset();
            throw e;
        }
        return result;
    }

    /**
     * Add an attribute delegate so it can intercept attributes being set.
     * Attribute delegates are fire in a FILO pattern, so that nested delegates
     * getfirst crack.
     * @param attrDelegate
     */
    public void addAttributeDelegate( Closure attrDelegate ) {
        attributeDelegates.addFirst( attrDelegate );
    }

    /**
     * remove the most recently added instance of teh attribute delegate.
     * @param attrDelegate
     */
    public void removeAttributeDelegat( Closure attrDelegate ) {
        attributeDelegates.remove( attrDelegate );
    }

    public void registerBeanFactory( String theName, final Class beanClass ) {
        registerFactory( theName, new AbstractFactory(){
            public Object newInstance( FactoryBuilderSupport builder, Object name, Object value,
                    Map properties ) throws InstantiationException, IllegalAccessException {
                if( checkValueIsTypeNotString( value, name, beanClass ) ){
                    return value;
                }else{
                    return beanClass.newInstance();
                }
            }
        } );
    }

    public void registerFactory( String name, Factory factory ) {
        factories.put( name, factory );
    }

    protected Object createNode( Object name, Map attributes, Object value ) {
        Object node = null;

        Factory factory = (Factory) factories.get( name );
        if( factory == null ){
            LOG.log( Level.WARNING, "Could not find match for name '" + name + "'" );
            return null;
        }
        getContext().put( CURRENT_FACTORY, factory );
        preInstantiate( name, attributes, value );
        try{
            node = factory.newInstance( this, name, value, attributes );
            if( node == null ){
                LOG.log( Level.WARNING, "Factory for name '" + name + "' returned null" );
                return null;
            }

            if( LOG.isLoggable( Level.FINE ) ){
                LOG.fine( "For name: " + name + " created node: " + node );
            }
        }catch( Exception e ){
            throw new RuntimeException( "Failed to create component for '" + name + "' reason: "
                    + e, e );
        }
        postInstantiate( name, attributes, node );
        handleNodeAttributes( node, attributes );
        return node;
    }

    protected Object doInvokeMethod( String methodName, Object name, Object args ) {
        Object node = null;
        Closure closure = null;
        List list = InvokerHelper.asList( args );

        if( contexts.isEmpty() ){
            // should be called on first build method only
            newContext();
        }
        switch( list.size() ){
            case 0:
                node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, null );
                break;
            case 1: {
                Object object = list.get( 0 );
                if( object instanceof Map ){
                    node = proxyBuilder.createNode( name, (Map) object, null );
                }else if( object instanceof Closure ){
                    closure = (Closure) object;
                    node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, null );
                }else{
                    node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, object );
                }
            }
                break;
            case 2: {
                Object object1 = list.get( 0 );
                Object object2 = list.get( 1 );
                if( object1 instanceof Map ){
                    if( object2 instanceof Closure ){
                        closure = (Closure) object2;
                        node = proxyBuilder.createNode( name, (Map) object1, null );
                    }else{
                        node = proxyBuilder.createNode( name, (Map) object1, object2 );
                    }
                }else{
                    if( object2 instanceof Closure ){
                        closure = (Closure) object2;
                        node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, object1 );
                    }else if( object2 instanceof Map ){
                        node = proxyBuilder.createNode( name, (Map) object2, object1 );
                    }else{
                        throw new MissingMethodException( name.toString(), getClass(),
                                list.toArray(), false );
                    }
                }
            }
                break;
            case 3: {
                Object arg0 = list.get( 0 );
                Object arg1 = list.get( 1 );
                Object arg2 = list.get( 2 );
                if( arg0 instanceof Map && arg2 instanceof Closure ){
                    closure = (Closure) arg2;
                    node = proxyBuilder.createNode( name, (Map) arg0, arg1 );
                }else if( arg1 instanceof Map && arg2 instanceof Closure ){
                    closure = (Closure) arg2;
                    node = proxyBuilder.createNode( name, (Map) arg1, arg0 );
                }else{
                    throw new MissingMethodException( name.toString(), getClass(), list.toArray(),
                            false );
                }
            }
                break;
            default: {
                throw new MissingMethodException( name.toString(), getClass(), list.toArray(),
                        false );
            }

        }

        if( node == null ){
            if( contexts.size() == 1 ){
                // pop the first context
                popContext();
            }
            return node;
        }

        Object current = getCurrent();
        if( current != null ){
            proxyBuilder.setParent( current, node );
        }

        if( closure != null ){
            if( getCurrentFactory().isLeaf() ){
                throw new RuntimeException( "'" + name + "' doesn't support nesting." );
            }
            // push new node on stack
            newContext();
            getContext().put( CURRENT_NODE, node );
            // lets register the builder as the delegate
            setClosureDelegate( closure, node );
            closure.call();
            popContext();
        }

        proxyBuilder.nodeCompleted( current, node );
        if( contexts.size() == 1 ){
            // pop the first context
            popContext();
        }
        return proxyBuilder.postNodeCompletion( current, node );
    }

    /**
     * A hook to allow names to be converted into some other object such as a
     * QName in XML or ObjectName in JMX.
     *
     * @param methodName the name of the desired method
     * @return the object representing the name
     */
    protected Object getName( String methodName ) {
        if( nameMappingClosure != null ){
            return nameMappingClosure.call( methodName );
        }
        return methodName;
    }

    protected FactoryBuilderSupport getProxyBuilder() {
        return proxyBuilder;
    }

    protected void handleNodeAttributes( Object node, Map attributes ) {
        // first, short circuit
        if( attributes.isEmpty() || (node == null) ){
            return;
        }

        for( Iterator iter = attributeDelegates.iterator(); iter.hasNext(); ) {
            ( (Closure) iter.next()).call( new Object[] {this, node, attributes} ) ;
        }

        if( getCurrentFactory().onHandleNodeAttributes( this, node, attributes ) ){
            setNodeAttributes( node, attributes );
        }
    }

    protected void newContext() {
        contexts.addFirst( new HashMap() );
    }

    /**
     * A hook to allow nodes to be processed once they have had all of their
     * children applied.
     *
     * @param node the current node being processed
     * @param parent the parent of the node being processed
     */
    protected void nodeCompleted( Object parent, Object node ) {
        getCurrentFactory().onNodeCompleted( this, parent, node );
    }

    protected Map popContext() {
        return (Map) contexts.removeFirst();
    }

    /**
     * A hook after the factory creates the node and before attributes are set
     */
    protected void postInstantiate( Object name, Map attributes, Object node ) {
    }

    /**
     * A hook to allow nodes to be processed once they have had all of their
     * children applied and allows the actual node object that represents the
     * Markup element to be changed
     *
     * @param node the current node being processed
     * @param parent the parent of the node being processed
     * @return the node, possibly new, that represents the markup element
     */
    protected Object postNodeCompletion( Object parent, Object node ) {
        return node;
    }

    /**
     * A hook before the factory creates the node
     */
    protected void preInstantiate( Object name, Map attributes, Object value ) {
    }

    /**
     * Clears the context stack
     */ 
    protected void reset() {
        contexts.clear();
    }

    /**
     * A strategy method to allow derived builders to use builder-trees and
     * switch in different kinds of builders. This method should call the
     * setDelegate() method on the closure which by default passes in this but
     * if node is-a builder we could pass that in instead (or do something wacky
     * too)
     *
     * @param closure the closure on which to call setDelegate()
     * @param node the node value that we've just created, which could be a
     *        builder
     */
    protected void setClosureDelegate( Closure closure, Object node ) {
        closure.setDelegate( this );
    }

    /**
     * Maps attributes key/values to properties on node.
     */
    protected void setNodeAttributes( Object node, Map attributes ) {
        // set the properties
        for( Iterator iter = attributes.entrySet()
                .iterator(); iter.hasNext(); ){
            Map.Entry entry = (Map.Entry) iter.next();
            String property = entry.getKey()
                    .toString();
            Object value = entry.getValue();
            InvokerHelper.setProperty( node, property, value );
        }
    }

    protected void setParent( Object parent, Object child ) {
        getCurrentFactory().setParent( this, parent, child );
    }

    protected void setProxyBuilder( FactoryBuilderSupport proxyBuilder ) {
        this.proxyBuilder = proxyBuilder;
    }
}
<commit_msg>replace 1.5 call by equivalent 1.4 call<commit_after>/*
 * 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.GroovyObjectSupport;
import groovy.lang.MissingMethodException;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.codehaus.groovy.runtime.InvokerHelper;

/**
 * Mix of BuilderSupport and SwingBuilder's factory support.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <NAME> <<EMAIL>>
 */
public abstract class FactoryBuilderSupport extends GroovyObjectSupport {
    public static final String CURRENT_FACTORY = "_CURRENT_FACTORY_";
    public static final String CURRENT_NODE = "_CURRENT_NODE_";
    private static final Logger LOG = Logger.getLogger( FactoryBuilderSupport.class.getName() );

    /**
     * Throws an exception if value is null.
     */
    public static void checkValueIsNull( Object value, Object name ) {
        if( value != null ){
            throw new RuntimeException( "'" + name + "' elements do not accept a value argument." );
        }
    }

    /**
     * Returns true if type is assignale to the value's class, false if value is
     * null.
     */
    public static boolean checkValueIsType( Object value, Object name, Class type ) {
        if( value != null ){
            if( type.isAssignableFrom( value.getClass() ) ){
                return true;
            }else{
                throw new RuntimeException( "The value argument of '" + name + "' must be of type "
                        + type.getName() );
            }
        }else{
            return false;
        }
    }

    /**
     * Returns true if type is assignale to the value's class, false if value is
     * null or a String.
     */
    public static boolean checkValueIsTypeNotString( Object value, Object name, Class type ) {
        if( value != null ){
            if( type.isAssignableFrom( value.getClass() ) ){
                return true;
            }else if( value instanceof String ){
                return false;
            }else{
                throw new RuntimeException( "The value argument of '" + name + "' must be of type "
                        + type.getName() + " or a String." );
            }
        }else{
            return false;
        }
    }

    private LinkedList contexts = new LinkedList();
    private LinkedList/*<Closure>*/ attributeDelegates = new LinkedList/*<Closure>*/(); //
    private Map factories = new HashMap();
    private Closure nameMappingClosure;
    private FactoryBuilderSupport proxyBuilder;

    public FactoryBuilderSupport() {
        this.proxyBuilder = this;
    }

    public FactoryBuilderSupport( Closure nameMappingClosure ) {
        this.nameMappingClosure = nameMappingClosure;
        this.proxyBuilder = this;
    }

    public Map getContext() {
        if( !contexts.isEmpty() ){
            return (Map) contexts.get(contexts.size() - 1);
        }
        return null;
    }

    public Object getCurrent() {
        if( !contexts.isEmpty() ){
            Map context = (Map) contexts.get(contexts.size() - 1);
            return context.get( CURRENT_NODE );
        }
        return null;
    }

    public Factory getCurrentFactory() {
        if( !contexts.isEmpty() ){
            Map context = (Map) contexts.get(contexts.size() - 1);
            return (Factory) context.get( CURRENT_FACTORY );
        }
        return null;
    }

    /**
     * Convenience method when no arguments are required
     *
     * @return the result of the call
     * @param methodName the name of the method to invoke
     */
    public Object invokeMethod( String methodName ) {
        return invokeMethod( methodName, null );
    }

    public Object invokeMethod( String methodName, Object args ) {
        Object name = getName( methodName );
        Object result = null;
        try{
            result = doInvokeMethod( methodName, name, args );
        }catch( RuntimeException e ){
            reset();
            throw e;
        }
        return result;
    }

    /**
     * Add an attribute delegate so it can intercept attributes being set.
     * Attribute delegates are fire in a FILO pattern, so that nested delegates
     * getfirst crack.
     * @param attrDelegate
     */
    public void addAttributeDelegate( Closure attrDelegate ) {
        attributeDelegates.addFirst( attrDelegate );
    }

    /**
     * remove the most recently added instance of teh attribute delegate.
     * @param attrDelegate
     */
    public void removeAttributeDelegat( Closure attrDelegate ) {
        attributeDelegates.remove( attrDelegate );
    }

    public void registerBeanFactory( String theName, final Class beanClass ) {
        registerFactory( theName, new AbstractFactory(){
            public Object newInstance( FactoryBuilderSupport builder, Object name, Object value,
                    Map properties ) throws InstantiationException, IllegalAccessException {
                if( checkValueIsTypeNotString( value, name, beanClass ) ){
                    return value;
                }else{
                    return beanClass.newInstance();
                }
            }
        } );
    }

    public void registerFactory( String name, Factory factory ) {
        factories.put( name, factory );
    }

    protected Object createNode( Object name, Map attributes, Object value ) {
        Object node = null;

        Factory factory = (Factory) factories.get( name );
        if( factory == null ){
            LOG.log( Level.WARNING, "Could not find match for name '" + name + "'" );
            return null;
        }
        getContext().put( CURRENT_FACTORY, factory );
        preInstantiate( name, attributes, value );
        try{
            node = factory.newInstance( this, name, value, attributes );
            if( node == null ){
                LOG.log( Level.WARNING, "Factory for name '" + name + "' returned null" );
                return null;
            }

            if( LOG.isLoggable( Level.FINE ) ){
                LOG.fine( "For name: " + name + " created node: " + node );
            }
        }catch( Exception e ){
            throw new RuntimeException( "Failed to create component for '" + name + "' reason: "
                    + e, e );
        }
        postInstantiate( name, attributes, node );
        handleNodeAttributes( node, attributes );
        return node;
    }

    protected Object doInvokeMethod( String methodName, Object name, Object args ) {
        Object node = null;
        Closure closure = null;
        List list = InvokerHelper.asList( args );

        if( contexts.isEmpty() ){
            // should be called on first build method only
            newContext();
        }
        switch( list.size() ){
            case 0:
                node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, null );
                break;
            case 1: {
                Object object = list.get( 0 );
                if( object instanceof Map ){
                    node = proxyBuilder.createNode( name, (Map) object, null );
                }else if( object instanceof Closure ){
                    closure = (Closure) object;
                    node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, null );
                }else{
                    node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, object );
                }
            }
                break;
            case 2: {
                Object object1 = list.get( 0 );
                Object object2 = list.get( 1 );
                if( object1 instanceof Map ){
                    if( object2 instanceof Closure ){
                        closure = (Closure) object2;
                        node = proxyBuilder.createNode( name, (Map) object1, null );
                    }else{
                        node = proxyBuilder.createNode( name, (Map) object1, object2 );
                    }
                }else{
                    if( object2 instanceof Closure ){
                        closure = (Closure) object2;
                        node = proxyBuilder.createNode( name, Collections.EMPTY_MAP, object1 );
                    }else if( object2 instanceof Map ){
                        node = proxyBuilder.createNode( name, (Map) object2, object1 );
                    }else{
                        throw new MissingMethodException( name.toString(), getClass(),
                                list.toArray(), false );
                    }
                }
            }
                break;
            case 3: {
                Object arg0 = list.get( 0 );
                Object arg1 = list.get( 1 );
                Object arg2 = list.get( 2 );
                if( arg0 instanceof Map && arg2 instanceof Closure ){
                    closure = (Closure) arg2;
                    node = proxyBuilder.createNode( name, (Map) arg0, arg1 );
                }else if( arg1 instanceof Map && arg2 instanceof Closure ){
                    closure = (Closure) arg2;
                    node = proxyBuilder.createNode( name, (Map) arg1, arg0 );
                }else{
                    throw new MissingMethodException( name.toString(), getClass(), list.toArray(),
                            false );
                }
            }
                break;
            default: {
                throw new MissingMethodException( name.toString(), getClass(), list.toArray(),
                        false );
            }

        }

        if( node == null ){
            if( contexts.size() == 1 ){
                // pop the first context
                popContext();
            }
            return node;
        }

        Object current = getCurrent();
        if( current != null ){
            proxyBuilder.setParent( current, node );
        }

        if( closure != null ){
            if( getCurrentFactory().isLeaf() ){
                throw new RuntimeException( "'" + name + "' doesn't support nesting." );
            }
            // push new node on stack
            newContext();
            getContext().put( CURRENT_NODE, node );
            // lets register the builder as the delegate
            setClosureDelegate( closure, node );
            closure.call();
            popContext();
        }

        proxyBuilder.nodeCompleted( current, node );
        if( contexts.size() == 1 ){
            // pop the first context
            popContext();
        }
        return proxyBuilder.postNodeCompletion( current, node );
    }

    /**
     * A hook to allow names to be converted into some other object such as a
     * QName in XML or ObjectName in JMX.
     *
     * @param methodName the name of the desired method
     * @return the object representing the name
     */
    protected Object getName( String methodName ) {
        if( nameMappingClosure != null ){
            return nameMappingClosure.call( methodName );
        }
        return methodName;
    }

    protected FactoryBuilderSupport getProxyBuilder() {
        return proxyBuilder;
    }

    protected void handleNodeAttributes( Object node, Map attributes ) {
        // first, short circuit
        if( attributes.isEmpty() || (node == null) ){
            return;
        }

        for( Iterator iter = attributeDelegates.iterator(); iter.hasNext(); ) {
            ( (Closure) iter.next()).call( new Object[] {this, node, attributes} ) ;
        }

        if( getCurrentFactory().onHandleNodeAttributes( this, node, attributes ) ){
            setNodeAttributes( node, attributes );
        }
    }

    protected void newContext() {
        contexts.addFirst( new HashMap() );
    }

    /**
     * A hook to allow nodes to be processed once they have had all of their
     * children applied.
     *
     * @param node the current node being processed
     * @param parent the parent of the node being processed
     */
    protected void nodeCompleted( Object parent, Object node ) {
        getCurrentFactory().onNodeCompleted( this, parent, node );
    }

    protected Map popContext() {
        return (Map) contexts.removeFirst();
    }

    /**
     * A hook after the factory creates the node and before attributes are set
     */
    protected void postInstantiate( Object name, Map attributes, Object node ) {
    }

    /**
     * A hook to allow nodes to be processed once they have had all of their
     * children applied and allows the actual node object that represents the
     * Markup element to be changed
     *
     * @param node the current node being processed
     * @param parent the parent of the node being processed
     * @return the node, possibly new, that represents the markup element
     */
    protected Object postNodeCompletion( Object parent, Object node ) {
        return node;
    }

    /**
     * A hook before the factory creates the node
     */
    protected void preInstantiate( Object name, Map attributes, Object value ) {
    }

    /**
     * Clears the context stack
     */ 
    protected void reset() {
        contexts.clear();
    }

    /**
     * A strategy method to allow derived builders to use builder-trees and
     * switch in different kinds of builders. This method should call the
     * setDelegate() method on the closure which by default passes in this but
     * if node is-a builder we could pass that in instead (or do something wacky
     * too)
     *
     * @param closure the closure on which to call setDelegate()
     * @param node the node value that we've just created, which could be a
     *        builder
     */
    protected void setClosureDelegate( Closure closure, Object node ) {
        closure.setDelegate( this );
    }

    /**
     * Maps attributes key/values to properties on node.
     */
    protected void setNodeAttributes( Object node, Map attributes ) {
        // set the properties
        for( Iterator iter = attributes.entrySet()
                .iterator(); iter.hasNext(); ){
            Map.Entry entry = (Map.Entry) iter.next();
            String property = entry.getKey()
                    .toString();
            Object value = entry.getValue();
            InvokerHelper.setProperty( node, property, value );
        }
    }

    protected void setParent( Object parent, Object child ) {
        getCurrentFactory().setParent( this, parent, child );
    }

    protected void setProxyBuilder( FactoryBuilderSupport proxyBuilder ) {
        this.proxyBuilder = proxyBuilder;
    }
}

{'fraction_non_alphanumeric': 0.08784042961258151, 'fraction_numerical': 0.003484209180411712, 'mean_word_length': 1.6860994247445693, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5518943', 'n_tokens_mistral': 8013, 'n_tokens_neox': 7364, 'n_words': 2581}