001/**
002 * Get more info at : www.jrebirth.org .
003 * Copyright JRebirth.org © 2011-2013
004 * Contact : sebastien.bordes@jrebirth.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jrebirth.af.core.component.basic;
019
020import static org.jrebirth.af.core.wave.WBuilder.wave;
021
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Method;
024import java.util.ArrayList;
025import java.util.IdentityHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Optional;
029
030import org.jrebirth.af.api.annotation.AfterInit;
031import org.jrebirth.af.api.annotation.BeforeInit;
032import org.jrebirth.af.api.annotation.OnRelease;
033import org.jrebirth.af.api.annotation.Releasable;
034import org.jrebirth.af.api.annotation.SkipAnnotation;
035import org.jrebirth.af.api.command.Command;
036import org.jrebirth.af.api.command.CommandBean;
037import org.jrebirth.af.api.component.basic.Component;
038import org.jrebirth.af.api.component.basic.InnerComponent;
039import org.jrebirth.af.api.component.behavior.Behavior;
040import org.jrebirth.af.api.exception.CoreException;
041import org.jrebirth.af.api.exception.CoreRuntimeException;
042import org.jrebirth.af.api.exception.JRebirthThreadException;
043import org.jrebirth.af.api.facade.JRebirthEventType;
044import org.jrebirth.af.api.key.UniqueKey;
045import org.jrebirth.af.api.link.Notifier;
046import org.jrebirth.af.api.log.JRLogger;
047import org.jrebirth.af.api.service.Service;
048import org.jrebirth.af.api.ui.Model;
049import org.jrebirth.af.api.wave.Wave;
050import org.jrebirth.af.api.wave.Wave.Status;
051import org.jrebirth.af.api.wave.WaveBean;
052import org.jrebirth.af.api.wave.WaveGroup;
053import org.jrebirth.af.api.wave.checker.WaveChecker;
054import org.jrebirth.af.api.wave.contract.WaveData;
055import org.jrebirth.af.api.wave.contract.WaveType;
056import org.jrebirth.af.core.concurrent.AbstractJrbRunnable;
057import org.jrebirth.af.core.concurrent.JRebirth;
058import org.jrebirth.af.core.concurrent.JrbReferenceRunnable;
059import org.jrebirth.af.core.link.AbstractReady;
060import org.jrebirth.af.core.link.ComponentEnhancer;
061import org.jrebirth.af.core.link.LinkMessages;
062import org.jrebirth.af.core.log.JRLoggerFactory;
063import org.jrebirth.af.core.util.CheckerUtility;
064import org.jrebirth.af.core.util.ClassUtility;
065import org.jrebirth.af.core.util.ObjectUtility;
066import org.jrebirth.af.core.wave.JRebirthItems;
067
068/**
069 *
070 * The class <strong>AbstractComponent</strong>.
071 *
072 * This is the base class for all of each of JRebirth pattern subclasses.<br />
073 * It allow to send waves.
074 *
075 * All things related to wave management must be execute into the JRebirth Thread
076 *
077 * @author Sébastien Bordes
078 *
079 * @param <C> the class type of the subclass
080 */
081@SkipAnnotation(false)
082public abstract class AbstractComponent<C extends Component<C>> extends AbstractReady<C> implements Component<C>, JRebirthItems, LinkMessages {
083
084    /** The default fallback wave handle method. */
085    public static final String PROCESS_WAVE_METHOD_NAME = "processWave";
086
087    /** The class logger. */
088    private static final JRLogger LOGGER = JRLoggerFactory.getLogger(AbstractComponent.class);
089
090    /** The return wave type map. */
091    // private final Map<WaveType, WaveType> returnWaveTypeMap = new HashMap<>();
092
093    /** A map that store all annotated methods to call sorted by lifecycle phase. */
094    private Map<String, List<Method>> lifecycleMethod;
095
096    /** The root component not null for inner component. */
097    protected Component<?> rootComponent;
098
099    /** The map that store inner models loaded. */
100    protected Map<InnerComponent<?>, Component<?>> innerComponentMap;
101
102    /** The list that store sorted inner models loaded. */
103    // protected List<Component<?>> innerComponentList;
104
105    /**
106     * Short cut method used to retrieve the notifier.
107     *
108     * @return the notifier retrieved from global facade
109     */
110    private Notifier getNotifier() {
111        return localFacade().getGlobalFacade().notifier();
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    @Override
118    public final void listen(final WaveType... waveTypes) {
119        // Call the other method with null waveChecker & method
120        listen(null, null, waveTypes);
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public final void listen(final WaveChecker waveChecker, final WaveType... waveTypes) {
128        // Call the other method with null method
129        listen(waveChecker, null, waveTypes);
130    }
131
132    /**
133     * Return the human-readable list of Wave Type.
134     *
135     * @param waveTypes the list of wave type
136     *
137     * @return the string list of Wave Type
138     */
139    private String getWaveTypesString(final WaveType[] waveTypes) {
140        final StringBuilder sb = new StringBuilder();
141        for (final WaveType waveType : waveTypes) {
142            sb.append(waveType.toString()).append(" ");
143        }
144        return sb.toString();
145    }
146
147    /**
148     * {@inheritDoc}
149     */
150    @Override
151    public final void listen(final WaveChecker waveChecker, final Method method, final WaveType... waveTypes) {
152
153        // Check API compliance
154        CheckerUtility.checkWaveTypeContract(this.getClass(), waveTypes);
155
156        final Component<?> waveReady = this;
157
158        LOGGER.trace(LinkMessages.LISTEN_WAVE_TYPE, getWaveTypesString(waveTypes), waveReady.getClass().getSimpleName());
159
160        // Use the JRebirth Thread to add new subscriptions for given Wave Type
161        JRebirth.runIntoJIT(new AbstractJrbRunnable(LISTEN_WAVE_TYPE.getText(getWaveTypesString(waveTypes), waveReady.getClass().getSimpleName())) {
162            @Override
163            public void runInto() throws JRebirthThreadException {
164                getNotifier().listen(waveReady, waveChecker, method, waveTypes);
165            }
166        });
167    }
168
169    // /**
170    // * {@inheritDoc}
171    // */
172    // @Override
173    // public final void registerCallback(final WaveType callType, final WaveType responseType) {
174    //
175    // // Call the generic method
176    // registerCallback(null, callType, responseType, null);
177    // }
178
179    // /**
180    // * {@inheritDoc}
181    // */
182    // @Override
183    // public final WaveType getReturnWaveType(final WaveType waveType) {
184    // return this.returnWaveTypeMap.get(waveType);
185    // }
186
187    /**
188     * {@inheritDoc}
189     */
190    @Override
191    public final void unlisten(final WaveType... waveTypes) {
192
193        // Store an hard link to be able to use current class into the closure
194        final Component waveReady = this;
195
196        LOGGER.trace(LinkMessages.UNLISTEN_WAVE_TYPE, getWaveTypesString(waveTypes), waveReady.getClass().getSimpleName());
197
198        // Use the JRebirth Thread to manage Waves
199        JRebirth.runIntoJIT(new AbstractJrbRunnable(UNLISTEN_WAVE_TYPE.getText(getWaveTypesString(waveTypes), waveReady.getClass().getSimpleName())) {
200
201            @Override
202            protected void runInto() throws JRebirthThreadException {
203                getNotifier().unlisten(waveReady, waveTypes);
204            }
205        });
206    }
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212    public final void sendWave(final Wave wave) {
213        // Define the from class if it didn't been done before (manually)
214        if (wave.fromClass() == null) {
215            wave.fromClass(this.getClass());
216        }
217        sendWaveIntoJit(wave);
218    }
219
220    /**
221     * {@inheritDoc}
222     */
223    @Override
224    public final <WB extends WaveBean> Wave sendWave(final WaveType waveType, final WB waveBean) {
225        return sendWaveIntoJit(createWave(WaveGroup.UNDEFINED, waveType, (Class<?>) null, waveBean));
226    }
227
228    /**
229     * {@inheritDoc}
230     */
231    @Override
232    public final Wave sendWave(final WaveType waveType, final WaveData<?>... waveData) {
233        return sendWaveIntoJit(createWave(WaveGroup.UNDEFINED, waveType, null, waveData));
234    }
235
236    /**
237     * {@inheritDoc}
238     */
239    @Override
240    public final <WB extends WaveBean> Wave callCommand(final Class<? extends CommandBean<WB>> commandClass, final WB waveBean) {
241        return sendWaveIntoJit(createWave(WaveGroup.CALL_COMMAND, null, commandClass, waveBean));
242    }
243
244    /**
245     * {@inheritDoc}
246     */
247    @Override
248    public final Wave callCommand(final Class<? extends Command> commandClass, final WaveData<?>... data) {
249        return sendWaveIntoJit(createWave(WaveGroup.CALL_COMMAND, null, commandClass, data));
250    }
251
252    /**
253     * {@inheritDoc}
254     */
255    @Override
256    public final Wave returnData(final Class<? extends Service> serviceClass, final WaveType waveType, final WaveData<?>... data) {
257        return sendWaveIntoJit(createWave(WaveGroup.RETURN_DATA, waveType, serviceClass, data));
258    }
259
260    /**
261     * {@inheritDoc}
262     */
263    @Override
264    public final Wave attachUi(final Class<? extends Model> modelClass, final WaveData<?>... data) {
265        return sendWaveIntoJit(createWave(WaveGroup.ATTACH_UI, null, modelClass, data));
266    }
267
268    /**
269     * Send the given wave using the JRebirth Thread.
270     *
271     * @param wave the wave to send
272     *
273     * @return the wave sent to JIT (with Sent status)
274     */
275    private Wave sendWaveIntoJit(final Wave wave) {
276
277        CheckerUtility.checkWave(wave);
278
279        wave.status(Status.Sent);
280
281        // Use the JRebirth Thread to manage Waves
282        JRebirth.runIntoJIT(new AbstractJrbRunnable(SEND_WAVE.getText(wave.toString())) {
283            @Override
284            public void runInto() throws JRebirthThreadException {
285                getNotifier().sendWave(wave);
286            }
287        });
288
289        return wave;
290    }
291
292    /**
293     * Build a wave object.
294     *
295     * @param waveGroup the group of the wave
296     * @param waveType the type of the wave
297     * @param componentClass the component class if any
298     * @param waveData wave data to use
299     *
300     * @return the wave built
301     */
302    private Wave createWave(final WaveGroup waveGroup, final WaveType waveType, final Class<?> componentClass, final WaveData<?>... waveData) {
303
304        final Wave wave = wave()
305                                .waveGroup(waveGroup)
306                                .waveType(waveType)
307                                .fromClass(this.getClass())
308                                .componentClass(componentClass)
309                                .addDatas(waveData);
310
311        // Track wave creation
312        localFacade().getGlobalFacade().trackEvent(JRebirthEventType.CREATE_WAVE, this.getClass(), wave.getClass());
313
314        return wave;
315    }
316
317    /**
318     * Build a wave object with its dedicated WaveBean.
319     *
320     * @param waveGroup the group of the wave
321     * @param waveType the type of the wave
322     * @param componentClass the component class if any
323     * @param waveBean the wave bean that holds all required wave data
324     *
325     * @return the wave built
326     */
327    private Wave createWave(final WaveGroup waveGroup, final WaveType waveType, final Class<?> componentClass, final WaveBean waveBean) {
328
329        final Wave wave = wave()
330                                .waveGroup(waveGroup)
331                                .waveType(waveType)
332                                .fromClass(this.getClass())
333                                .componentClass(componentClass)
334                                .waveBean(waveBean);
335
336        // Track wave creation
337        localFacade().getGlobalFacade().trackEvent(JRebirthEventType.CREATE_WAVE, this.getClass(), wave.getClass());
338
339        return wave;
340    }
341
342    // /**
343    // * This method is called before each execution of the command.
344    // *
345    // * It will parse the given wave to store local command properties.
346    // *
347    // * Wave parsing mechanism is composed by three steps:
348    // * <ol>
349    // * <li>Parse WaveBean properties and copy them into command ones if they exist (by reflection)</li>
350    // * <li>Parse WaveData keys and copy them into command ones if they exist (by reflection)</li>
351    // * <li>Call {@link #parseWave(Wave)} method for later customization</li>
352    // * </ol>
353    // *
354    // * @param wave the wave to parse
355    // */
356    // private void parseInternalWave(final Wave wave) {
357    //
358    // // Parse WaveBean
359    // // WB waveBean = getWaveBean(wave);
360    // // if (waveBean != null && !(waveBean instanceof DefaultWaveBean)) {
361    // // for (Field f : ClassUtility.retrievePropertyList(waveBean.getClass())) {
362    // // try {
363    // // tryToSetProperty(f.getName(), f.get(waveBean));
364    // // } catch (IllegalArgumentException | IllegalAccessException e) {
365    // // LOGGER.error("Fail to get field value " + f.getName() + " from " + waveBean.getClass(), e);
366    // // }
367    // // }
368    // // }
369    //
370    // // Parse WaveData
371    // for (final WaveData<?> wd : wave.getWaveItems()) {
372    // tryToSetProperty(wd.getKey().toString(), wd.getValue());
373    // }
374    //
375    // // Call customized method
376    // // parseWave(wave);
377    // }
378    //
379    // /**
380    // * Try to set the value of the given property for the current class.
381    // *
382    // * @param fieldName the field to initialize
383    // * @param fieldValue the field value to set
384    // */
385    // private void tryToSetProperty(final String fieldName, final Object fieldValue) {
386    // try {
387    // this.getClass().getField(fieldName).set(this, fieldValue);
388    // } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
389    // LOGGER.error("Fail to set value for field " + fieldName, e);
390    // }
391    // }
392
393    /**
394     * Customizable method used to perform more action before command execution.
395     *
396     * @param wave the given wave to parser before command execution
397     */
398    // protected abstract void parseWave(final Wave wave);
399
400    // /**
401    // * {@inheritDoc}
402    // */
403    // @Override
404    // public final void handle(final Wave wave) throws WaveException {
405    // try {
406    // // Build parameter list of the searched method
407    // final List<Object> parameterValues = new ArrayList<>();
408    // for (final WaveData<?> wd : wave.getWaveItems()) {
409    // // Add only wave items defined as parameter
410    // if (wd.getKey().isParameter()) {
411    // parameterValues.add(wd.getValue());
412    // }
413    // }
414    // // Add the current wave to process
415    // parameterValues.add(wave);
416    //
417    // // Search the wave handler method
418    // final Method method = ClassUtility.getMethodByName(this.getClass(), ClassUtility.underscoreToCamelCase(wave.getWaveType().toString()));
419    // if (method != null) {
420    // // Call this method with right parameters
421    // method.invoke(this, parameterValues.toArray());
422    // }
423    // } catch (final NoSuchMethodException e) {
424    //
425    // LOGGER.info(CUSTOM_METHOD_NOT_FOUND, e.getMessage());
426    // // If no method was found, call the default method
427    // processWave(wave);
428    //
429    // } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
430    // LOGGER.error(WAVE_DISPATCH_ERROR, e);
431    // // Propagate the wave exception
432    // throw new WaveException(wave, e);
433    // }
434    // }
435
436    /**
437     * Process the wave. Typically by using a switch on the waveType.
438     *
439     * @param wave the wave received
440     */
441    protected abstract void processWave(final Wave wave);
442
443    /**
444     * Private method used to grab the right WaveType<?> java type.
445     *
446     * @return the this instance with the right generic type
447     */
448    private Component<?> getWaveReady() {
449        return this;
450    }
451
452    /**
453     * Call annotated methods corresponding at given lifecycle annotation.
454     *
455     * @param annotationClass the annotation related to the lifecycle
456     */
457    private void callAnnotatedMethod(final Class<? extends Annotation> annotationClass) {
458        if (this.lifecycleMethod.get(annotationClass.getName()) != null) {
459            for (final Method method : this.lifecycleMethod.get(annotationClass.getName())) {
460
461                try {
462                    ClassUtility.callMethod(method, this);
463                } catch (final CoreException e) {
464                    LOGGER.error(CALL_ANNOTATED_METHOD_ERROR, e);
465                }
466            }
467        }
468    }
469
470    /**
471     * The component is now ready to do custom initialization tasks.
472     *
473     * @throws CoreException if an error occurred
474     */
475    protected abstract void ready() throws CoreException;
476
477    /**
478     * {@inheritDoc}
479     */
480    @SuppressWarnings("unchecked")
481    @Override
482    public void setup() throws CoreException {
483
484        final boolean canProcessAnnotation = ComponentEnhancer.canProcessAnnotation((Class<? extends Component<?>>) this.getClass());
485        if (canProcessAnnotation) {
486
487            // Search Singleton and Multiton annotation on field
488            ComponentEnhancer.injectComponent(this);
489
490            // Attach custom method configured with custom Lifecycle annotation
491            this.lifecycleMethod = ComponentEnhancer.defineLifecycleMethod(this);
492
493            // Search OnWave annotation to manage auto wave handler setup
494            ComponentEnhancer.manageOnWaveAnnotation(this);
495
496        }
497
498        callAnnotatedMethod(BeforeInit.class);
499
500        manageOptionalData();
501
502        if (canProcessAnnotation) {
503            ComponentEnhancer.injectInnerComponent(this);
504        }
505
506        // Initialize all inner components
507        initInternalInnerComponents();
508
509        // Prepare the current component
510        ready();
511
512        callAnnotatedMethod(AfterInit.class);
513    }
514
515    /**
516     *
517     */
518    protected abstract void manageOptionalData();
519
520    // {
521    //
522    // for (final Object data : getKey().getOptionalData()) {
523    //
524    // if (data instanceof BehaviorData) {
525    //
526    // addBehavior((BehaviorData) data);
527    //
528    // } else if (data instanceof Class && ((Class<?>) data).isAssignableFrom(Behavior.class)) {
529    //
530    // addBehavior((Class<Behavior<BehaviorData>>) data);
531    //
532    // }
533    //
534    // }
535    //
536    // }
537
538    /**
539     * {@inheritDoc}
540     */
541    @Override
542    public boolean release() {
543
544        boolean released = false;
545        // Check if some method annotated by Releasable annotation are available
546        if (ObjectUtility.checkAllMethodReturnTrue(this, ClassUtility.getAnnotatedMethods(this.getClass(), Releasable.class))) {
547
548            // Release the component and its internal components into JIT
549            JRebirth.runIntoJIT(new JrbReferenceRunnable("Release " + this.getClass().getCanonicalName(), this::internalRelease));
550
551            released = true;
552        }
553
554        return released;
555    }
556
557    /**
558     * Perform the internal release.
559     */
560    private void internalRelease() {
561
562        // try {
563
564        // setKey(null);
565
566        // getNotifier().unlistenAll(getWaveReady());
567
568        callAnnotatedMethod(OnRelease.class);
569
570        if (localFacade() != null) {
571            localFacade().unregister(key());
572        }
573
574        // thisObject.ready = false;
575
576        // Shall also release all InnerComponent
577        if (getInnerComponentMap().isPresent()) {
578
579            final List<Component<?>> toRemove = new ArrayList<>();
580            for (final Component<?> innerComponent : getInnerComponentMap().get().values()) {
581
582                // Release the InnerComponent
583                innerComponent.release();
584
585                // Store it to avoid co-modification error
586                toRemove.add(innerComponent);
587            }
588
589            for (final Component<?> innerComponent : toRemove) {
590
591                // Remove it from the list
592                // getInnerComponentList().get().remove(innerComponent);
593
594                // Then remove it from map
595                getInnerComponentMap().get().remove(innerComponent.key());
596            }
597        }
598
599        // } catch (final JRebirthThreadException jte) {
600        // LOGGER.error(COMPONENT_RELEASE_ERROR, jte);
601        // }
602
603    }
604
605    // public boolean isReady() {
606    // return ready;
607    // }
608
609    /**
610     * {@inheritDoc}
611     */
612    @SuppressWarnings("unchecked")
613    private <IC extends Component<?>> void addInnerComponent(final InnerComponent<IC> innerComponent) {
614
615        if (!getInnerComponentMap().isPresent()) {
616            // Initialize default storage objects only on demand (to save some bits)
617            this.innerComponentMap = new IdentityHashMap<>(10);
618            // this.innerComponentList = new ArrayList<Component<?>>();
619        }
620
621        // If the inner model hasn't been loaded before, build it from UIFacade
622        if (!this.innerComponentMap.containsKey(innerComponent)) {
623
624            final Class<IC> innerComponentClass = innerComponent.key().classField();
625
626            IC childComponent = null;
627            // Use the right facade according to the inner component type
628            if (Command.class.isAssignableFrom(innerComponentClass)) {
629                // Initialize the Inner Command
630                childComponent = (IC) localFacade().getGlobalFacade().commandFacade().retrieve((UniqueKey<Command>) innerComponent.key());
631            } else if (Service.class.isAssignableFrom(innerComponentClass)) {
632                // Initialize the Inner Service
633                childComponent = (IC) localFacade().getGlobalFacade().serviceFacade().retrieve((UniqueKey<Service>) innerComponent.key());
634            } else if (Model.class.isAssignableFrom(innerComponentClass)) {
635                // Initialize the Inner Model
636                childComponent = (IC) localFacade().getGlobalFacade().uiFacade().retrieve((UniqueKey<Model>) innerComponent.key());
637            } else if (Behavior.class.isAssignableFrom(innerComponentClass)) {
638                // Cannot initialize Inner Behavior, they cannot be nested
639                throw new CoreRuntimeException("Behaviors can not be used as Inner EnhancedComponent"); // FIXME add MessageItem
640            }
641
642            // Inner EnhancedComponent creation has failed throw an exception
643            if (childComponent == null) {
644                throw new CoreRuntimeException("Impossible to create Inner EnhancedComponent"); // FIXME add MessageItem
645            }
646
647            // Store the component into the multitonKey map
648            this.innerComponentMap.put(innerComponent, childComponent);
649            // this.innerComponentList.add(childComponent);
650
651            // Link the current root model
652            childComponent.rootComponent(this);
653        }
654    }
655
656    /**
657     * {@inheritDoc}
658     */
659    @SuppressWarnings("unchecked")
660    @Override
661    public final <IC extends Component<?>> IC findInnerComponent(final InnerComponent<IC> innerComponent) {
662
663        if (!getInnerComponentMap().isPresent() || !getInnerComponentMap().get().containsKey(innerComponent)) {
664
665            // This InnerModel should be initialized into the initInnerModel method instead
666            // but in some cases a late initialization can help
667            addInnerComponent(innerComponent);
668        }
669        return (IC) getInnerComponentMap().get().get(innerComponent);
670    }
671
672    /**
673     * {@inheritDoc}
674     */
675    @Override
676    public Component<?> rootComponent() {
677        return this.rootComponent;
678    }
679
680    /**
681     * {@inheritDoc}
682     */
683    @Override
684    public void rootComponent(final Component<?> rootComponent) {
685        this.rootComponent = rootComponent;
686    }
687
688    /**
689     * Initialize the included models.
690     *
691     * This method is a hook to manage generic code before initializing inner models.
692     *
693     * You must implement the {@link #initInnerComponents()} method to setup your inner models.
694     */
695    protected final void initInternalInnerComponents() {
696        // Do generic stuff
697
698        // Do custom stuff
699        initInnerComponents();
700    }
701
702    /**
703     * Initialize method that should wrap all creation of {@link InnerComponent}.
704     *
705     * It shall contain only {@link EnhancedComponent}.addInnerComponent calls.
706     */
707    protected abstract void initInnerComponents();
708
709    /**
710     * Return the InnerComponent map wrapped into an Optional because it's initialized only on demand.
711     *
712     * @return the innerComponentMap.
713     */
714    protected Optional<Map<InnerComponent<?>, Component<?>>> getInnerComponentMap() {
715        return Optional.ofNullable(this.innerComponentMap);
716    }
717
718    /**
719     * Return the InnerComponent list wrapped into an Optional because it's initialized only on demand.
720     *
721     * @return the innerComponentList.
722     */
723    // protected Optional<List<Component<?>>> getInnerComponentList() {
724    // return Optional.ofNullable(getInnerComponentMap().isPresent() ? getInnerComponentMap().get().values().as : null);
725    // }
726
727}