001package 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
022import 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 */
029public 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}