001    package org.apache.commons.digester3.binder;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.commons.digester3.Rule;
023    
024    /**
025     * Builder invoked to bind one or more rules to a pattern.
026     *
027     * @since 3.0
028     */
029    public final class LinkedRuleBuilder
030    {
031    
032        private final RulesBinder mainBinder;
033    
034        private final FromBinderRuleSet fromBinderRuleSet;
035    
036        private final ClassLoader classLoader;
037    
038        private final String keyPattern;
039    
040        private String namespaceURI;
041    
042        LinkedRuleBuilder( final RulesBinder mainBinder, final FromBinderRuleSet fromBinderRuleSet,
043                           final ClassLoader classLoader, final String keyPattern )
044        {
045            this.mainBinder = mainBinder;
046            this.fromBinderRuleSet = fromBinderRuleSet;
047            this.classLoader = classLoader;
048            this.keyPattern = keyPattern;
049        }
050    
051        /**
052         * Construct rule that automatically sets a property from the body text, taking the property
053         * name the same as the current element.
054         *
055         * @return a new {@link BeanPropertySetterBuilder} instance.
056         */
057        public BeanPropertySetterBuilder setBeanProperty()
058        {
059            return addProvider( new BeanPropertySetterBuilder( keyPattern, namespaceURI, mainBinder, this ) );
060        }
061    
062        /**
063         * Calls a method on an object on the stack (normally the top/parent object), passing arguments collected from
064         * subsequent {@link #callParam()} rule or from the body of this element.
065         *
066         * @param methodName Method name of the parent object to call
067         * @return a new {@link CallMethodBuilder} instance.
068         */
069        public CallMethodBuilder callMethod( String methodName )
070        {
071            if ( methodName == null || methodName.length() == 0 )
072            {
073                mainBinder.addError( "{ forPattern( \"%s\" ).callMethod( String ) } empty 'methodName' not allowed",
074                                     keyPattern );
075            }
076    
077            return this.addProvider( new CallMethodBuilder( keyPattern, namespaceURI, mainBinder, this, methodName,
078                                                            classLoader ) );
079        }
080    
081        /**
082         * Saves a parameter for use by a surrounding {@link #callMethod(String)}.
083         *
084         * @return a new {@link CallParamBuilder} instance.
085         */
086        public CallParamBuilder callParam()
087        {
088            return this.addProvider( new CallParamBuilder( keyPattern, namespaceURI, mainBinder, this ) );
089        }
090    
091        /**
092         * Construct a "call parameter" rule that will save the body text of this element as the parameter value.
093         *
094         * @return a new {@link PathCallParamBuilder} instance.
095         */
096        public PathCallParamBuilder callParamPath()
097        {
098            return addProvider( new PathCallParamBuilder( keyPattern, namespaceURI, mainBinder, this ) );
099        }
100    
101        /**
102         * Uses an {@link org.apache.commons.digester3.ObjectCreationFactory} to create a new object which it
103         * pushes onto the object stack.
104         *
105         * When the element is complete, the object will be popped.
106         *
107         * @return a new {@link FactoryCreateBuilder} instance.
108         */
109        public FactoryCreateBuilder factoryCreate()
110        {
111            return addProvider( new FactoryCreateBuilder( keyPattern, namespaceURI, mainBinder, this, classLoader ) );
112        }
113    
114        /**
115         * Construct an object.
116         *
117         * @return a new {@link ObjectCreateBuilder} instance.
118         */
119        public ObjectCreateBuilder createObject()
120        {
121            return addProvider( new ObjectCreateBuilder( keyPattern, namespaceURI, mainBinder, this, classLoader ) );
122        }
123    
124        /**
125         * Saves a parameter for use by a surrounding {@link #callMethod(String)}.
126         *
127         * @param <T> The parameter type to pass along
128         * @param paramObj The parameter to pass along
129         * @return a new {@link ObjectParamBuilder} instance.
130         */
131        public <T> ObjectParamBuilder<T> objectParam( /* @Nullable */T paramObj )
132        {
133            return addProvider( new ObjectParamBuilder<T>( keyPattern, namespaceURI, mainBinder, this, paramObj ) );
134        }
135    
136        /**
137         * Sets properties on the object at the top of the stack,
138         * based on child elements with names matching properties on that  object.
139         *
140         * @return a new {@link NestedPropertiesBuilder} instance.
141         */
142        public NestedPropertiesBuilder setNestedProperties()
143        {
144            // that would be useful when adding rules via automatically generated rules binding (such annotations)
145            NestedPropertiesBuilder nestedPropertiesBuilder =
146                fromBinderRuleSet.getProvider( keyPattern, namespaceURI, NestedPropertiesBuilder.class );
147            if ( nestedPropertiesBuilder != null )
148            {
149                return nestedPropertiesBuilder;
150            }
151    
152            return addProvider( new NestedPropertiesBuilder( keyPattern, namespaceURI, mainBinder, this ) );
153        }
154    
155        /**
156         * Calls a method on the (top-1) (parent) object, passing the top object (child) as an argument,
157         * commonly used to establish parent-child relationships.
158         *
159         * @param methodName Method name of the parent method to call
160         * @return a new {@link SetNextBuilder} instance.
161         */
162        public SetNextBuilder setNext( String methodName )
163        {
164            if ( methodName == null || methodName.length() == 0 )
165            {
166                mainBinder.addError( "{ forPattern( \"%s\" ).setNext( String ) } empty 'methodName' not allowed",
167                                     keyPattern );
168            }
169            return this.addProvider( new SetNextBuilder( keyPattern, namespaceURI, mainBinder, this, methodName,
170                                                         classLoader ) );
171        }
172    
173        /**
174         * Sets properties on the object at the top of the stack, based on attributes with corresponding names.
175         *
176         * @return a new {@link SetPropertiesBuilder} instance.
177         */
178        public SetPropertiesBuilder setProperties()
179        {
180            // that would be useful when adding rules via automatically generated rules binding (such annotations)
181            SetPropertiesBuilder setPropertiesBuilder =
182                fromBinderRuleSet.getProvider( keyPattern, namespaceURI, SetPropertiesBuilder.class );
183            if ( setPropertiesBuilder != null )
184            {
185                return setPropertiesBuilder;
186            }
187    
188            return addProvider( new SetPropertiesBuilder( keyPattern, namespaceURI, mainBinder, this ) );
189        }
190    
191        /**
192         * Sets an individual property on the object at the top of the stack, based on attributes with specified names.
193         *
194         * @param attributePropertyName Name of the attribute that will contain the name of the property to be set
195         * @return a new {@link SetPropertyBuilder} instance.
196         */
197        public SetPropertyBuilder setProperty( String attributePropertyName )
198        {
199            if ( attributePropertyName == null || attributePropertyName.length() == 0 )
200            {
201                mainBinder
202                    .addError( "{ forPattern( \"%s\" ).setProperty( String ) } empty 'attributePropertyName' not allowed",
203                               keyPattern );
204            }
205    
206            return addProvider( new SetPropertyBuilder( keyPattern,
207                                                        namespaceURI,
208                                                        mainBinder,
209                                                        this,
210                                                        attributePropertyName ) );
211        }
212    
213        /**
214         * Calls a method on the root object on the stack, passing the top object (child) as an argument.
215         *
216         * @param methodName Method name of the parent method to call
217         * @return a new {@link SetRootBuilder} instance.
218         */
219        public SetRootBuilder setRoot( String methodName )
220        {
221            if ( methodName == null || methodName.length() == 0 )
222            {
223                mainBinder.addError( "{ forPattern( \"%s\" ).setRoot( String ) } empty 'methodName' not allowed",
224                                     keyPattern );
225            }
226    
227            return addProvider( new SetRootBuilder( keyPattern, namespaceURI, mainBinder, this, methodName, classLoader ) );
228        }
229    
230        /**
231         * Calls a "set top" method on the top (child) object, passing the (top-1) (parent) object as an argument.
232         *
233         * @param methodName Method name of the "set parent" method to call
234         * @return a new {@link SetTopBuilder} instance.
235         */
236        public SetTopBuilder setTop( String methodName )
237        {
238            if ( methodName == null || methodName.length() == 0 )
239            {
240                mainBinder.addError( "{ forPattern( \"%s\" ).setTop( String ) } empty 'methodName' not allowed",
241                                     keyPattern );
242            }
243    
244            return addProvider( new SetTopBuilder( keyPattern, namespaceURI, mainBinder, this, methodName, classLoader ) );
245        }
246    
247        /**
248         * A Digester rule which allows the user to pre-declare a class which is to
249         * be referenced later at a plugin point by a PluginCreateRule.
250         *
251         * NOTE: when using this rule, make sure {@link org.apache.commons.digester3.Digester} instances
252         * will be created using {@link org.apache.commons.digester3.plugins.PluginRules} rules strategy.
253         *
254         * @return a new {@link PluginDeclarationRuleBuilder} instance.
255         */
256        public PluginDeclarationRuleBuilder declarePlugin()
257        {
258            return addProvider( new PluginDeclarationRuleBuilder( keyPattern, namespaceURI, mainBinder, this ) );
259        }
260    
261        /**
262         * A Digester rule which allows the user to declare a plugin.
263         *
264         * NOTE: when using this rule, make sure {@link org.apache.commons.digester3.Digester} instances
265         * will be created using {@link org.apache.commons.digester3.plugins.PluginRules} rules strategy.
266         *
267         * @return a new {@link PluginDeclarationRuleBuilder} instance.
268         */
269        public PluginCreateRuleBuilder createPlugin()
270        {
271            return addProvider( new PluginCreateRuleBuilder( keyPattern, namespaceURI, mainBinder, this ) );
272        }
273    
274        /**
275         * A rule implementation that creates a DOM Node containing the XML at the element that matched the rule.
276         *
277         * @return a new {@link NodeCreateRuleProvider} instance.
278         */
279        public NodeCreateRuleProvider createNode()
280        {
281            return addProvider( new NodeCreateRuleProvider( keyPattern, namespaceURI, mainBinder, this ) );
282        }
283    
284        /**
285         * Add a custom user rule in the specified pattern.
286         *
287         * <b>WARNING</b> keep away from this method as much as you can, since there's the risk
288         * same input {@link Rule} instance is plugged to more than one Digester;
289         * use {@link #addRuleCreatedBy(RuleProvider)} instead!!!
290         *
291         * @see #addRuleCreatedBy(RuleProvider)
292         * @see Rule#setDigester(org.apache.commons.digester3.Digester)
293         * @param <R> The rule type
294         * @param rule The custom user rule
295         * @return a new {@link ByRuleBuilder} instance.
296         */
297        public <R extends Rule> ByRuleBuilder<R> addRule( R rule )
298        {
299            if ( rule == null )
300            {
301                mainBinder.addError( "{ forPattern( \"%s\" ).addRule( R ) } NULL rule not valid", keyPattern );
302            }
303    
304            return this.addProvider( new ByRuleBuilder<R>( keyPattern, namespaceURI, mainBinder, this, rule ) );
305        }
306    
307        /**
308         * Add a custom user rule in the specified pattern built by the given provider.
309         *
310         * @param <R> The rule type
311         * @param provider The rule provider
312         * @return a new {@link ByRuleProviderBuilder} instance.
313         */
314        public <R extends Rule> ByRuleProviderBuilder<R> addRuleCreatedBy( RuleProvider<R> provider )
315        {
316            if ( provider == null )
317            {
318                mainBinder.addError( "{ forPattern( \"%s\" ).addRuleCreatedBy() } null rule provider not valid",
319                                     keyPattern );
320            }
321    
322            return addProvider( new ByRuleProviderBuilder<R>( keyPattern, namespaceURI, mainBinder, this, provider ) );
323        }
324    
325        /**
326         * Sets the namespace URI for the current rule pattern.
327         *
328         * @param namespaceURI the namespace URI associated to the rule pattern.
329         * @return this {@link LinkedRuleBuilder} instance
330         */
331        public LinkedRuleBuilder withNamespaceURI( /* @Nullable */String namespaceURI )
332        {
333            if ( namespaceURI == null || namespaceURI.length() > 0 )
334            {
335                this.namespaceURI = namespaceURI;
336            }
337            else
338            {
339                // ignore empty namespaces, null is better
340                this.namespaceURI = null;
341            }
342    
343            return this;
344        }
345    
346        /**
347         * Add a provider in the data structure where storing the providers binding.
348         *
349         * @param <R> The rule will be created by the given provider
350         * @param provider The provider has to be stored in the data structure
351         * @return The provider itself has to be stored in the data structure
352         */
353        private <R extends Rule, RB extends AbstractBackToLinkedRuleBuilder<R>> RB addProvider( RB provider )
354        {
355            fromBinderRuleSet.registerProvider( provider );
356            return provider;
357        }
358    
359    }