001// Generated by delombok at Sun Jul 30 17:21:56 UTC 2023
002package de.cuioss.test.jsf.config.decorator;
003
004import static de.cuioss.tools.base.Preconditions.checkArgument;
005import static java.util.Objects.requireNonNull;
006import javax.faces.application.Application;
007import javax.faces.component.FacesComponent;
008import javax.faces.component.UIComponent;
009import javax.faces.component.UIForm;
010import javax.faces.component.UIInput;
011import javax.faces.component.UIOutput;
012import javax.faces.component.UISelectBoolean;
013import javax.faces.component.UISelectOne;
014import javax.faces.component.UIViewRoot;
015import javax.faces.component.behavior.ClientBehavior;
016import javax.faces.component.behavior.FacesBehavior;
017import javax.faces.component.html.HtmlCommandButton;
018import javax.faces.component.html.HtmlForm;
019import javax.faces.component.html.HtmlInputText;
020import javax.faces.component.html.HtmlOutputText;
021import javax.faces.component.html.HtmlSelectBooleanCheckbox;
022import javax.faces.component.html.HtmlSelectOneRadio;
023import javax.faces.context.FacesContext;
024import javax.faces.convert.Converter;
025import javax.faces.convert.FacesConverter;
026import javax.faces.render.FacesRenderer;
027import javax.faces.render.Renderer;
028import javax.faces.validator.FacesValidator;
029import javax.faces.validator.Validator;
030import de.cuioss.test.jsf.mocks.CuiMockComponent;
031import de.cuioss.test.jsf.mocks.CuiMockRenderer;
032import de.cuioss.test.jsf.mocks.CuiMockUIViewRoot;
033import de.cuioss.test.jsf.mocks.CuiMockViewHandler;
034import de.cuioss.test.valueobjects.objects.impl.DefaultInstantiator;
035import lombok.NonNull;
036
037/**
038 * Helper class acting as runtime-registry for {@link UIComponent},
039 * {@link Converter}, {@link Validator} and {@link Renderer}
040 *
041 * @author Oliver Wolff
042 */
043public class ComponentConfigDecorator {
044    static final String FORM_RENDERER_ID = "javax.faces.Form";
045    static final String TEXT_RENDERER_ID = "javax.faces.Text";
046    static final String SELECTBOOLEAN_RENDERER_ID = "javax.faces.SelectBoolean";
047    static final String SELECTONE_RENDERER_ID = "javax.faces.SelectOne";
048    private static final String BEHAVIOR_CLASS_MUST_NOT_BE_NULL = "behaviorClass must not be null";
049    private static final String BEHAVIOR_ID_MUST_NOT_BE_NULL = "behaviorId must not be null";
050    private static final String RENDERER_MUST_NOT_BE_NULL = "renderer must not be null";
051    private static final String RENDERER_TYPE_MUST_NOT_BE_NULL = "rendererType must not be null";
052    private static final String FAMILY_MUST_NOT_BE_NULL = "family must not be null";
053    private static final String VALIDATOR_ID_MUST_NOT_BE_NULL = "validatorId must not be null";
054    private static final String CONVERTER_ID_MUST_NOT_BE_NULL = "converterId must not be null";
055    private static final String TARGET_CLASS_MUST_NOT_BE_NULL = "targetClass  must not be null";
056    private static final String VALIDATOR_MUST_NOT_BE_NULL = "validator must not be null";
057    private static final String COMPONENT_MUST_NOT_BE_NULL = "component must not be null";
058    private static final String COMPONENT_TYPE_NOT_BE_NULL = "component must not be null";
059    private static final String CONVERTER_MUST_NOT_BE_NULL = "converter  must not be null";
060    @NonNull
061    private final Application application;
062    @NonNull
063    private final FacesContext facesContext;
064
065    /**
066     * Adds add {@link Validator} to the given id.
067     *
068     * @param validatorId the id the {@link Validator} should be registered with,
069     *                    must not be null
070     * @param validator   the actual {@link Validator} class, must not be null.
071     * @return the {@link ComponentConfigDecorator} itself in order to enable a
072     *         fluent-api style usage
073     */
074    public ComponentConfigDecorator registerValidator(final String validatorId, final Class<? extends Validator> validator) {
075        requireNonNull(validator, VALIDATOR_MUST_NOT_BE_NULL);
076        requireNonNull(validatorId, VALIDATOR_ID_MUST_NOT_BE_NULL);
077        application.addValidator(validatorId, validator.getName());
078        return this;
079    }
080
081    /**
082     * Adds add {@link Validator} the needed validatorId is retrieved from
083     * {@link FacesValidator}.
084     *
085     * @param validator the actual {@link Validator} class, must not be null. In
086     *                  order to work the {@link Validator} must provide the
087     *                  {@link FacesValidator} annotation in order to identify the
088     *                  the needed validatorId
089     * @return the {@link ComponentConfigDecorator} itself in order to enable a
090     *         fluent-api style usage
091     */
092    public ComponentConfigDecorator registerValidator(final Class<? extends Validator> validator) {
093        requireNonNull(validator, VALIDATOR_MUST_NOT_BE_NULL);
094        checkArgument(validator.isAnnotationPresent(FacesValidator.class), "In order to work this method needs a validator annotated with \'javax.faces.validator.FacesValidator\', validatorClass:" + validator.getName());
095        return registerValidator(validator.getAnnotation(FacesValidator.class).value(), validator);
096    }
097
098    /**
099     * Register a {@link Converter} for a given target class.
100     *
101     * @param converter   to be registered , must not be null
102     * @param targetClass must not be null
103     * @return the {@link ComponentConfigDecorator} itself in order to enable a
104     *         fluent-api style usage
105     */
106    public ComponentConfigDecorator registerConverter(final Class<? extends Converter> converter, final Class<?> targetClass) {
107        requireNonNull(targetClass, TARGET_CLASS_MUST_NOT_BE_NULL);
108        requireNonNull(converter, CONVERTER_MUST_NOT_BE_NULL);
109        application.addConverter(targetClass, converter.getName());
110        return this;
111    }
112
113    /**
114     * Register a {@link Converter} for a given target class.
115     *
116     * @param converter to be registered , must not be null and must provide a
117     *                  {@link FacesConverter} annotation for deriving targetType or
118     *                  converter.id
119     * @return the {@link ComponentConfigDecorator} itself in order to enable a
120     *         fluent-api style usage
121     */
122    public ComponentConfigDecorator registerConverter(final Class<? extends Converter> converter) {
123        requireNonNull(converter, CONVERTER_MUST_NOT_BE_NULL);
124        checkArgument(converter.isAnnotationPresent(FacesConverter.class), "In order to work this method needs a converter annotated with \'javax.faces.convert.FacesConverter\', converterClass:" + converter.getName());
125        final var facesConverter = converter.getAnnotation(FacesConverter.class);
126        if (!Object.class.equals(facesConverter.forClass())) {
127            registerConverter(converter, facesConverter.forClass());
128        }
129        if (!facesConverter.value().isEmpty()) {
130            registerConverter(converter, facesConverter.value());
131        }
132        return this;
133    }
134
135    /**
136     * Register a {@link Converter} to a given converterId.
137     *
138     * @param converter   to be registered , must not be null
139     * @param converterId must not be null
140     * @return the {@link ComponentConfigDecorator} itself in order to enable a
141     *         fluent-api style usage
142     */
143    public ComponentConfigDecorator registerConverter(final Class<? extends Converter> converter, final String converterId) {
144        requireNonNull(converterId, CONVERTER_ID_MUST_NOT_BE_NULL);
145        requireNonNull(converter, CONVERTER_MUST_NOT_BE_NULL);
146        application.addConverter(converterId, converter.getName());
147        return this;
148    }
149
150    /**
151     * Registers a {@link UIComponent}
152     *
153     * @param componentType identifying the component, must not be null
154     * @param component     the actual component, must not be null
155     * @return the {@link ComponentConfigDecorator} itself in order to enable a
156     *         fluent-api style usage
157     */
158    public ComponentConfigDecorator registerUIComponent(final String componentType, final Class<? extends UIComponent> component) {
159        requireNonNull(componentType, COMPONENT_TYPE_NOT_BE_NULL);
160        requireNonNull(component, COMPONENT_MUST_NOT_BE_NULL);
161        application.addComponent(componentType, component.getName());
162        return this;
163    }
164
165    /**
166     * Registers a {@link UIComponent}
167     *
168     * @param component the actual component, must not be null must provide the
169     *                  {@link FacesComponent} annotation to derive the componentId
170     *                  to be registered to.
171     * @return the {@link ComponentConfigDecorator} itself in order to enable a
172     *         fluent-api style usage
173     */
174    public ComponentConfigDecorator registerUIComponent(final Class<? extends UIComponent> component) {
175        requireNonNull(component, COMPONENT_MUST_NOT_BE_NULL);
176        checkArgument(component.isAnnotationPresent(FacesComponent.class), "In order to work this method needs a UIComponent annotated with \'javax.faces.component.FacesComponent\', component:" + component.getName());
177        return registerUIComponent(component.getAnnotation(FacesComponent.class).value(), component);
178    }
179
180    /**
181     * Shorthand for registering a {@link CuiMockComponent} with a
182     * {@link CuiMockRenderer}.
183     *
184     * @return the {@link ComponentConfigDecorator} itself in order to enable a
185     *         fluent-api style usage
186     */
187    public ComponentConfigDecorator registerCuiMockComponentWithRenderer() {
188        registerUIComponent(CuiMockComponent.class);
189        registerMockRenderer(CuiMockComponent.FAMILY, CuiMockComponent.RENDERER_TYPE);
190        return this;
191    }
192
193    /**
194     * Registers a {@link CuiMockRenderer} for the given attributes.
195     *
196     * @param family       identifying the component-family the renderer is related
197     *                     to, must not be null
198     * @param rendererType identifying the type of the renderer is related to, must
199     *                     not be null
200     * @return the {@link ComponentConfigDecorator} itself in order to enable a
201     *         fluent-api style usage
202     */
203    public ComponentConfigDecorator registerMockRenderer(final String family, final String rendererType) {
204        requireNonNull(family, FAMILY_MUST_NOT_BE_NULL);
205        requireNonNull(rendererType, RENDERER_TYPE_MUST_NOT_BE_NULL);
206        facesContext.getRenderKit().addRenderer(family, rendererType, new CuiMockRenderer());
207        return this;
208    }
209
210    /**
211     * Shorthand for registering a {@link CuiMockRenderer} for
212     * {@link HtmlOutputText}.
213     *
214     * @return the {@link ComponentConfigDecorator} itself in order to enable a
215     *         fluent-api style usage
216     */
217    public ComponentConfigDecorator registerMockRendererForHtmlOutputText() {
218        registerMockRenderer(UIOutput.COMPONENT_FAMILY, TEXT_RENDERER_ID);
219        return this;
220    }
221
222    /**
223     * Shorthand for registering a {@link CuiMockRenderer} for
224     * {@link HtmlSelectBooleanCheckbox}.
225     *
226     * @return the {@link ComponentConfigDecorator} itself in order to enable a
227     *         fluent-api style usage
228     */
229    public ComponentConfigDecorator registerMockRendererForHtmlSelectBooleanCheckbox() {
230        registerMockRenderer(UISelectBoolean.COMPONENT_FAMILY, SELECTBOOLEAN_RENDERER_ID);
231        return this;
232    }
233
234    /**
235     * Shorthand for registering a {@link CuiMockRenderer} for
236     * {@link HtmlSelectOneRadio}.
237     *
238     * @return the {@link ComponentConfigDecorator} itself in order to enable a
239     *         fluent-api style usage
240     */
241    public ComponentConfigDecorator registerMockRendererForHtmlSelectOneRadio() {
242        registerMockRenderer(UISelectOne.COMPONENT_FAMILY, SELECTONE_RENDERER_ID);
243        return this;
244    }
245
246    /**
247     * Shorthand for registering a {@link CuiMockRenderer} for
248     * {@link HtmlInputText}.
249     *
250     * @return the {@link ComponentConfigDecorator} itself in order to enable a
251     *         fluent-api style usage
252     */
253    public ComponentConfigDecorator registerMockRendererForHtmlInputText() {
254        registerMockRenderer(UIInput.COMPONENT_FAMILY, TEXT_RENDERER_ID);
255        return this;
256    }
257
258    /**
259     * Shorthand for registering a {@link CuiMockRenderer} for {@link HtmlForm}.
260     *
261     * @return the {@link ComponentConfigDecorator} itself in order to enable a
262     *         fluent-api style usage
263     */
264    public ComponentConfigDecorator registerMockRendererForHtmlForm() {
265        registerMockRenderer(UIForm.COMPONENT_FAMILY, FORM_RENDERER_ID);
266        return this;
267    }
268
269    /**
270     * Shorthand for registering a {@link CuiMockRenderer} for
271     * {@link HtmlCommandButton}.
272     *
273     * @return the {@link ComponentConfigDecorator} itself in order to enable a
274     *         fluent-api style usage
275     */
276    public ComponentConfigDecorator registerMockRendererForCommandButton() {
277        facesContext.getRenderKit().addRenderer("javax.faces.Command", "javax.faces.Button", new CuiMockRenderer("CommandButton"));
278        return this;
279    }
280
281    /**
282     * Registers a {@link Renderer}
283     *
284     * @param family       identifying the component-family the renderer is related
285     *                     to, must not be null
286     * @param rendererType identifying the type of the renderer is related to, must
287     *                     not be null
288     * @param renderer     the actual renderer to be registered, must not be null
289     * @return the {@link ComponentConfigDecorator} itself in order to enable a
290     *         fluent-api style usage
291     */
292    public ComponentConfigDecorator registerRenderer(final String family, final String rendererType, final Renderer renderer) {
293        requireNonNull(family, FAMILY_MUST_NOT_BE_NULL);
294        requireNonNull(rendererType, RENDERER_TYPE_MUST_NOT_BE_NULL);
295        requireNonNull(renderer, RENDERER_MUST_NOT_BE_NULL);
296        facesContext.getRenderKit().addRenderer(family, rendererType, renderer);
297        return this;
298    }
299
300    /**
301     * Registers a {@link Renderer} The family and rendererType will be derived by
302     * the mandatory {@link FacesRenderer} annotation
303     *
304     * @param renderer the actual renderer to be registered, must not be null, must
305     *                 provide {@link FacesRenderer} annotation and a no arg public
306     *                 constructor.
307     * @return the {@link ComponentConfigDecorator} itself in order to enable a
308     *         fluent-api style usage
309     */
310    public ComponentConfigDecorator registerRenderer(final Class<? extends Renderer> renderer) {
311        requireNonNull(renderer, RENDERER_MUST_NOT_BE_NULL);
312        checkArgument(renderer.isAnnotationPresent(FacesRenderer.class), "In order to work this method needs a Renderer annotated with \'javax.faces.render.FacesRenderer\', renderer:" + renderer.getName());
313        final Renderer instance;
314        instance = new DefaultInstantiator<>(renderer).newInstance();
315        final var config = renderer.getAnnotation(FacesRenderer.class);
316        return registerRenderer(config.componentFamily(), config.rendererType(), instance);
317    }
318
319    /**
320     * Register a {@link ClientBehavior} for a given behaviorId
321     *
322     * @param behaviorId    the id the {@link ClientBehavior} should be registered
323     *                      with, must not be null
324     * @param behaviorClass the actual type of the {@link ClientBehavior} must not
325     *                      be null
326     * @return the {@link ComponentConfigDecorator} itself in order to enable a
327     *         fluent-api style usage
328     */
329    public ComponentConfigDecorator registerBehavior(final String behaviorId, final Class<? extends ClientBehavior> behaviorClass) {
330        requireNonNull(behaviorId, BEHAVIOR_ID_MUST_NOT_BE_NULL);
331        requireNonNull(behaviorClass, BEHAVIOR_CLASS_MUST_NOT_BE_NULL);
332        application.addBehavior(behaviorId, behaviorClass.getName());
333        return this;
334    }
335
336    /**
337     * Register a {@link ClientBehavior}. The behaviorId will be extracted by the
338     * mandatory {@link FacesBehavior} annotation
339     *
340     * @param behaviorClass the actual type of the {@link ClientBehavior} must not
341     *                      be null and provide the {@link FacesBehavior} annotation
342     *                      in order to extract the correct behaviorId
343     * @return the {@link ComponentConfigDecorator} itself in order to enable a
344     *         fluent-api style usage
345     */
346    public ComponentConfigDecorator registerBehavior(final Class<? extends ClientBehavior> behaviorClass) {
347        requireNonNull(behaviorClass, BEHAVIOR_CLASS_MUST_NOT_BE_NULL);
348        checkArgument(behaviorClass.isAnnotationPresent(FacesBehavior.class), "In order to work this method needs a ClientBehavior annotated with \'javax.faces.component.behavior.FacesBehavior\', behaviorClass:" + behaviorClass.getName());
349        return registerBehavior(behaviorClass.getAnnotation(FacesBehavior.class).value(), behaviorClass);
350    }
351
352    /**
353     * Register a composite component to be rendered.
354     *
355     * @param libraryName the library name like
356     *                    "http://xmlns.jcp.org/jsf/composite/" or "
357     * @param tagName     the tag name
358     * @param uiComponent the component that should be returned as mock /
359     *                    placeholder for the composite component
360     * @return the {@link ComponentConfigDecorator} itself in order to enable a
361     *         fluent-api style usage
362     */
363    public ComponentConfigDecorator registerCompositeComponent(final String libraryName, final String tagName, final UIComponent uiComponent) {
364        if (!(application.getViewHandler() instanceof CuiMockViewHandler)) {
365            application.setViewHandler(new CuiMockViewHandler());
366        }
367        ((CuiMockViewHandler) application.getViewHandler()).registerCompositeComponent(libraryName, tagName, uiComponent);
368        return this;
369    }
370
371    /**
372     * Add a component to the view root to be found when searching via
373     * {@link UIViewRoot#findComponent(String)}.
374     *
375     * @param expr      the expression the component should be found with
376     * @param component the component
377     * @return the {@link ComponentConfigDecorator} itself in order to enable a
378     *         fluent-api style usage
379     */
380    public ComponentConfigDecorator addUiComponent(String expr, UIComponent component) {
381        if (!(facesContext.getViewRoot() instanceof CuiMockUIViewRoot)) {
382            facesContext.setViewRoot(new CuiMockUIViewRoot());
383        }
384        ((CuiMockUIViewRoot) facesContext.getViewRoot()).addUiComponent(expr, component);
385        return this;
386    }
387
388    @java.lang.SuppressWarnings("all")
389    @lombok.Generated
390    public ComponentConfigDecorator(@NonNull final Application application, @NonNull final FacesContext facesContext) {
391        if (application == null) {
392            throw new java.lang.NullPointerException("application is marked non-null but is null");
393        }
394        if (facesContext == null) {
395            throw new java.lang.NullPointerException("facesContext is marked non-null but is null");
396        }
397        this.application = application;
398        this.facesContext = facesContext;
399    }
400}