001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup;
018
019import java.lang.ref.WeakReference;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.wicket.Component;
028import org.apache.wicket.MarkupContainer;
029import org.apache.wicket.behavior.Behavior;
030import org.apache.wicket.markup.parser.XmlTag;
031import org.apache.wicket.markup.parser.XmlTag.TagType;
032import org.apache.wicket.markup.parser.filter.HtmlHandler;
033import org.apache.wicket.request.Response;
034import org.apache.wicket.util.lang.Args;
035import org.apache.wicket.util.lang.Generics;
036import org.apache.wicket.util.string.AppendingStringBuffer;
037import org.apache.wicket.util.string.StringValue;
038import org.apache.wicket.util.string.Strings;
039import org.apache.wicket.util.value.IValueMap;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043
044/**
045 * A subclass of MarkupElement which represents a "significant" markup tag, such as a component open
046 * tag. Insignificant markup tags (those which are merely concerned with markup formatting
047 * operations and do not denote components or component nesting) are coalesced into instances of
048 * RawMarkup (also a subclass of MarkupElement).
049 * 
050 * @author Jonathan Locke
051 */
052public class ComponentTag extends MarkupElement
053{
054        /**
055         * Factory that creates component during markup root container's initialization. These
056         * components get queued, which allows other components to be dequeued under these auto
057         * components.
058         * 
059         * @author igor
060         */
061        public interface IAutoComponentFactory
062        {
063                /**
064                 * Creates a new instance of auto component to be queued
065                 *
066                 * @param container
067                 *                The component that will become a parent of the newly created auto component
068                 * @param tag
069                 *                The markup element for the newly created auto component
070                 */
071                Component newComponent(MarkupContainer container, ComponentTag tag);
072        }
073
074
075        /** Log. */
076        private static final Logger log = LoggerFactory.getLogger(ComponentTag.class);
077
078        /** True if a href attribute is available and autolinking is on */
079        private final static int AUTOLINK = 0x0001;
080
081        /** True, if attributes have been modified or added */
082        private final static int MODIFIED = 0x0002;
083
084        /** If true, then the MarkupParser will ignore (remove) it. Temporary working variable */
085        private final static int IGNORE = 0x0004;
086
087        /** If true, then the tag contain an automatically created wicket id */
088        private final static int AUTO_COMPONENT = 0x0008;
089
090        /** Some HTML tags are allow to have no close tag, e.g. 'br' */
091        private final static int NO_CLOSE_TAG = 0x0010;
092
093        /** Render the tag as RawMarkup even if no Component can be found */
094        public final static int RENDER_RAW = 0x0020;
095        
096        /** If true, the current tag contains a child or a descendant with the "wicket:id" attribute */
097        public final static int CONTAINS_WICKET_ID = 0x0040;
098
099        /** If close tag, than reference to the corresponding open tag */
100        private ComponentTag openTag;
101
102        /** The underlying xml tag */
103        protected final XmlTag xmlTag;
104
105        /** Boolean flags. See above */
106        private int flags = 0;
107
108        /**
109         * By default this is equal to the wicket:id="xxx" attribute value, but may be provided e.g. for
110         * auto-tags
111         */
112        private String id;
113
114        /**
115         * In case of inherited markup, the base and the extended markups are merged and the information
116         * about the tags origin is lost. In some cases like wicket:head and wicket:link this
117         * information however is required.
118         */
119        private WeakReference<Class<? extends Component>> markupClassRef = null;
120
121        /** added behaviors */
122        private List<Behavior> behaviors;
123
124        /** Filters and Handlers may add their own attributes to the tag */
125        private Map<String, Object> userData;
126
127        private IAutoComponentFactory autoComponentFactory;
128
129        /**
130         * Automatically create a XmlTag, assign the name and the type, and construct a ComponentTag
131         * based on this XmlTag.
132         * 
133         * @param name
134         *            The name of html tag
135         * @param type
136         *            The type of tag
137         */
138        public ComponentTag(final String name, final XmlTag.TagType type)
139        {
140                final XmlTag tag = new XmlTag();
141                tag.setName(name);
142                tag.setType(type);
143                xmlTag = tag;
144        }
145
146        /**
147         * Construct.
148         * 
149         * @param tag
150         *            The underlying xml tag
151         */
152        public ComponentTag(final XmlTag tag)
153        {
154                super();
155                xmlTag = tag;
156        }
157
158        /**
159         * Constructor
160         * 
161         * @param tag
162         *            The ComponentTag tag which this wicket tag is based upon.
163         */
164        public ComponentTag(final ComponentTag tag)
165        {
166                this(tag.getXmlTag());
167                tag.copyPropertiesTo(this);
168        }
169
170        /**
171         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
172         * 
173         * @param flag
174         *            The flag to set
175         * @param set
176         *            True to turn the flag on, false to turn it off
177         */
178        public final void setFlag(final int flag, final boolean set)
179        {
180                if (set)
181                {
182                        flags |= flag;
183                }
184                else
185                {
186                        flags &= ~flag;
187                }
188        }
189
190        /**
191         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
192         * 
193         * @param flag
194         *            The flag to test
195         * @return True if the flag is set
196         */
197        public final boolean getFlag(final int flag)
198        {
199                return (flags & flag) != 0;
200        }
201
202        /**
203         * Adds a behavior to this component tag.
204         * 
205         * @param behavior
206         */
207        public final void addBehavior(final Behavior behavior)
208        {
209                Args.notNull(behavior, "behavior");
210
211                if (behaviors == null)
212                {
213                        behaviors = Generics.newArrayList();
214                }
215                behaviors.add(behavior);
216        }
217
218        /**
219         * @return true if this tag has any behaviors added, false otherwise
220         */
221        public final boolean hasBehaviors()
222        {
223                return behaviors != null;
224        }
225
226        /**
227         * @return read only iterator over added behaviors
228         */
229        // TODO change to return unmodifiable list which will never be null. See Component.getBehavior
230        public final Iterator<? extends Behavior> getBehaviors()
231        {
232                if (behaviors == null)
233                {
234                        List<Behavior> lst = Collections.emptyList();
235                        return lst.iterator();
236                }
237
238                return Collections.unmodifiableCollection(behaviors).iterator();
239        }
240
241        /**
242         * Gets whether this tag closes the provided open tag.
243         * 
244         * @param open
245         *            The open tag
246         * @return True if this tag closes the given open tag
247         */
248        @Override
249        public final boolean closes(final MarkupElement open)
250        {
251                if (open instanceof ComponentTag)
252                {
253                        return (openTag == open) || getXmlTag().closes(((ComponentTag)open).getXmlTag());
254                }
255
256                return false;
257        }
258
259        /**
260         * If autolink is set to true, href attributes will automatically be converted into Wicket
261         * bookmarkable URLs.
262         * 
263         * @param autolink
264         *            enable/disable automatic href conversion
265         */
266        public final void enableAutolink(final boolean autolink)
267        {
268                setFlag(AUTOLINK, autolink);
269        }
270
271        /**
272         * @see org.apache.wicket.markup.parser.XmlTag#getAttributes()
273         * @return The tag#s attributes
274         */
275        public final IValueMap getAttributes()
276        {
277                return xmlTag.getAttributes();
278        }
279
280        /**
281         * A convenient method. The same as getAttributes().getString(name)
282         * 
283         * @param name
284         * @return The attributes value
285         */
286        public final String getAttribute(String name)
287        {
288                return xmlTag.getAttributes().getString(name);
289        }
290
291        /**
292         * Get the tag's component id
293         * 
294         * @return The component id attribute of this tag
295         */
296        public final String getId()
297        {
298                return id;
299        }
300
301        /**
302         * Gets the length of the tag in characters.
303         * 
304         * @return The tag's length
305         */
306        public final int getLength()
307        {
308                return xmlTag.getLength();
309        }
310
311        /**
312         * @return The tag's name
313         */
314        public final String getName()
315        {
316                return xmlTag.getName();
317        }
318
319        /**
320         * @return The tag's namespace
321         */
322        public final String getNamespace()
323        {
324                return xmlTag.getNamespace();
325        }
326
327        /**
328         * If set, return the corresponding open tag (ComponentTag).
329         * 
330         * @return The corresponding open tag
331         */
332        public final ComponentTag getOpenTag()
333        {
334                return openTag;
335        }
336
337        /**
338         * @see org.apache.wicket.markup.parser.XmlTag#getPos()
339         * @return Tag location (index in input string)
340         */
341        public final int getPos()
342        {
343                return xmlTag.getPos();
344        }
345
346        /**
347         * @return the tag type (OPEN, CLOSE or OPEN_CLOSE).
348         */
349        public final TagType getType()
350        {
351                return xmlTag.getType();
352        }
353
354        /**
355         * True if autolink is enabled and the tag contains a href attribute.
356         * 
357         * @return True, if the href contained should automatically be converted
358         */
359        public final boolean isAutolinkEnabled()
360        {
361                return getFlag(AUTOLINK);
362        }
363
364        /**
365         * @see org.apache.wicket.markup.parser.XmlTag#isClose()
366         * @return True if this tag is a close tag
367         */
368        public final boolean isClose()
369        {
370                return xmlTag.isClose();
371        }
372
373        /**
374         * @see org.apache.wicket.markup.parser.XmlTag#isOpen()
375         * @return True if this tag is an open tag
376         */
377        public final boolean isOpen()
378        {
379                return xmlTag.isOpen();
380        }
381
382        /**
383         * @param id
384         *            Required component id
385         * @return True if this tag is an open tag with the given component name
386         * @see org.apache.wicket.markup.parser.XmlTag#isOpen()
387         */
388        public final boolean isOpen(String id)
389        {
390                return xmlTag.isOpen() && this.id.equals(id);
391        }
392
393        /**
394         * @see org.apache.wicket.markup.parser.XmlTag#isOpenClose()
395         * @return True if this tag is an open and a close tag
396         */
397        public final boolean isOpenClose()
398        {
399                return xmlTag.isOpenClose();
400        }
401
402        /**
403         * @param id
404         *            Required component id
405         * @return True if this tag is an openclose tag with the given component id
406         * @see org.apache.wicket.markup.parser.XmlTag#isOpenClose()
407         */
408        public final boolean isOpenClose(String id)
409        {
410                return xmlTag.isOpenClose() && this.id.equals(id);
411        }
412
413        /**
414         * Makes this tag object immutable by making the attribute map unmodifiable. Immutable tags
415         * cannot be made mutable again. They can only be copied into new mutable tag objects.
416         */
417        public final void makeImmutable()
418        {
419                xmlTag.makeImmutable();
420        }
421
422        /**
423         * Gets this tag if it is already mutable, or a mutable copy of this tag if it is immutable.
424         * 
425         * @return This tag if it is already mutable, or a mutable copy of this tag if it is immutable.
426         */
427        public ComponentTag mutable()
428        {
429                if (xmlTag.isMutable())
430                {
431                        return this;
432                }
433                else
434                {
435                        ComponentTag tag = new ComponentTag(xmlTag.mutable());
436                        copyPropertiesTo(tag);
437                        return tag;
438                }
439        }
440
441        /**
442         * Copies all internal properties from this tag to <code>dest</code>. This is basically cloning
443         * without instance creation.
444         * 
445         * @param dest
446         *            tag whose properties will be set
447         */
448        void copyPropertiesTo(final ComponentTag dest)
449        {
450                dest.id = id;
451                dest.flags = flags;
452                dest.autoComponentFactory = autoComponentFactory;
453
454                if (markupClassRef != null)
455                {
456                        dest.setMarkupClass(markupClassRef.get());
457                }
458                if (behaviors != null)
459                {
460                        dest.behaviors = new ArrayList<>(behaviors);
461                }
462                if (userData != null)
463                {
464                        dest.userData = new HashMap<>(userData);
465                }
466        }
467
468        /**
469         * @see org.apache.wicket.markup.parser.XmlTag#put(String, boolean)
470         * @param key
471         *            The key
472         * @param value
473         *            The value
474         */
475        public final void put(final String key, final boolean value)
476        {
477                xmlTag.put(key, value);
478                setModified(true);
479        }
480
481        /**
482         * @see org.apache.wicket.markup.parser.XmlTag#put(String, int)
483         * @param key
484         *            The key
485         * @param value
486         *            The value
487         */
488        public final void put(final String key, final int value)
489        {
490                xmlTag.put(key, value);
491                setModified(true);
492        }
493
494        /**
495         * @see org.apache.wicket.markup.parser.XmlTag#put(String, CharSequence)
496         * @param key
497         *            The key
498         * @param value
499         *            The value
500         */
501        public final void put(String key, CharSequence value)
502        {
503                checkIdAttribute(key);
504                putInternal(key, value);
505        }
506
507        /**
508         * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT
509         * 
510         * @see org.apache.wicket.markup.parser.XmlTag#put(String, CharSequence)
511         * @param key
512         *            The key
513         * @param value
514         *            The value
515         */
516        public final void putInternal(String key, CharSequence value)
517        {
518                xmlTag.put(key, value);
519                setModified(true);
520        }
521
522        /**
523         * @param key
524         */
525        private void checkIdAttribute(String key)
526        {
527                if ("id".equalsIgnoreCase(key))
528                {
529                        log.warn("Please use component.setMarkupId(String) to change the tag's 'id' attribute.");
530                }
531        }
532
533        /**
534         * Appends specified {@code value} to the attribute
535         * 
536         * @param key
537         *            The key
538         * @param value
539         *            The value
540         * @param separator
541         *            The separator used to append the value
542         */
543        public final void append(String key, CharSequence value, String separator)
544        {
545                String current = getAttribute(key);
546                if (Strings.isEmpty(current))
547                {
548                        xmlTag.put(key, value);
549                }
550                else
551                {
552                        xmlTag.put(key, current + separator + value);
553                }
554
555                setModified(true);
556        }
557
558        /**
559         * @see org.apache.wicket.markup.parser.XmlTag#put(String, StringValue)
560         * @param key
561         *            The key
562         * @param value
563         *            The value
564         */
565        public final void put(String key, StringValue value)
566        {
567                xmlTag.put(key, value);
568                setModified(true);
569        }
570
571        /**
572         * @see org.apache.wicket.markup.parser.XmlTag#putAll(Map)
573         * @param map
574         *            a key/value map
575         */
576        public final void putAll(final Map<String, Object> map)
577        {
578                xmlTag.putAll(map);
579                setModified(true);
580        }
581
582        /**
583         * @see org.apache.wicket.markup.parser.XmlTag#remove(String)
584         * @param key
585         *            The key to remove
586         */
587        public final void remove(String key)
588        {
589                xmlTag.remove(key);
590                setModified(true);
591        }
592
593        /**
594         * Gets whether this tag does not require a closing tag.
595         * 
596         * @return True if this tag does not require a closing tag
597         */
598        public final boolean requiresCloseTag()
599        {
600                if (getNamespace() == null)
601                {
602                        return HtmlHandler.requiresCloseTag(getName());
603                }
604                else
605                {
606                        return HtmlHandler.requiresCloseTag(getNamespace() + ":" + getName());
607                }
608        }
609
610        /**
611         * Set the component's id. The value is usually taken from the tag's id attribute, e.g.
612         * wicket:id="componentId".
613         * 
614         * @param id
615         *            The component's id assigned to the tag.
616         */
617        public final void setId(final String id)
618        {
619                this.id = id;
620        }
621
622        /**
623         * @see org.apache.wicket.markup.parser.XmlTag#setName(String)
624         * @param name
625         *            New tag name
626         */
627        public final void setName(String name)
628        {
629                xmlTag.setName(name);
630        }
631
632        /**
633         * @see org.apache.wicket.markup.parser.XmlTag#setNamespace(String)
634         * @param namespace
635         *            New tag name namespace
636         */
637        public final void setNamespace(String namespace)
638        {
639                xmlTag.setNamespace(namespace);
640        }
641
642        /**
643         * Assuming this is a close tag, assign it's corresponding open tag.
644         * 
645         * @param tag
646         *            the open-tag
647         * @throws RuntimeException
648         *             if 'this' is not a close tag
649         */
650        public final void setOpenTag(final ComponentTag tag)
651        {
652                openTag = tag;
653                getXmlTag().setOpenTag(tag.getXmlTag());
654        }
655
656        /**
657         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
658         * 
659         * @param type
660         *            The new type
661         */
662        public final void setType(final TagType type)
663        {
664                if (type != xmlTag.getType())
665                {
666                        xmlTag.setType(type);
667                        setModified(true);
668                }
669        }
670
671        /**
672         * @return A synthetic close tag for this tag
673         * @deprecated use {{@link #writeSyntheticCloseTag(Response)}} instead
674         */
675        @Deprecated(since = "9.10.0", forRemoval = true)
676        public final CharSequence syntheticCloseTagString()
677        {
678                AppendingStringBuffer buf = new AppendingStringBuffer();
679                buf.append("</");
680                if (getNamespace() != null)
681                {
682                        buf.append(getNamespace()).append(':');
683                }
684                buf.append(getName()).append('>');
685
686                return buf;
687        }
688
689        /**
690         * Writes the synthetic close tag for this tag to the response
691         * 
692         * @param response
693         *            The response to write to
694         */
695        public final void writeSyntheticCloseTag(Response response)
696        {
697                response.write("</");
698                if (getNamespace() != null)
699                {
700                        response.write(getNamespace());
701                        response.write(":");
702                }
703                response.write(getName());
704                response.write(">");
705        }
706
707        /**
708         * @see org.apache.wicket.markup.MarkupElement#toCharSequence()
709         */
710        @Override
711        public CharSequence toCharSequence()
712        {
713                return xmlTag.toCharSequence();
714        }
715
716        /**
717         * Converts this object to a string representation.
718         * 
719         * @return String version of this object
720         */
721        @Override
722        public final String toString()
723        {
724                return toCharSequence().toString();
725        }
726
727        /**
728         * Write the tag to the response
729         * 
730         * @param response
731         *            The response to write to
732         * @param stripWicketAttributes
733         *            if true, wicket:id are removed from output
734         * @param namespace
735         *            Wicket's namespace to use
736         */
737        public final void writeOutput(final Response response, final boolean stripWicketAttributes,
738                final String namespace)
739        {
740                response.write("<");
741
742                if (getType() == TagType.CLOSE)
743                {
744                        response.write("/");
745                }
746
747                if (getNamespace() != null)
748                {
749                        response.write(getNamespace());
750                        response.write(":");
751                }
752
753                response.write(getName());
754
755                String namespacePrefix = null;
756                if (stripWicketAttributes == true)
757                {
758                        namespacePrefix = namespace + ":";
759                }
760
761                if (getAttributes().size() > 0)
762                {
763                        for (String key : getAttributes().keySet())
764                        {
765                                if (key == null)
766                                {
767                                        continue;
768                                }
769
770                                if ((namespacePrefix == null) || (key.startsWith(namespacePrefix) == false))
771                                {
772                                        response.write(" ");
773                                        response.write(key);
774                                        CharSequence value = getAttribute(key);
775
776                                        // attributes without values are possible, e.g.' disabled'
777                                        if (value != null)
778                                        {
779                                                response.write("=\"");
780                                                value = Strings.escapeMarkup(value);
781                                                response.write(value);
782                                                response.write("\"");
783                                        }
784                                }
785                        }
786                }
787
788                if (getType() == TagType.OPEN_CLOSE)
789                {
790                        response.write("/");
791                }
792
793                response.write(">");
794        }
795
796        /**
797         * Converts this object to a string representation including useful information for debugging
798         * 
799         * @return String version of this object
800         */
801        @Override
802        public final String toUserDebugString()
803        {
804                return xmlTag.toUserDebugString();
805        }
806
807        /**
808         * @return Returns the underlying xml tag.
809         */
810        public final XmlTag getXmlTag()
811        {
812                return xmlTag;
813        }
814
815        /**
816         * Manually mark the ComponentTag being modified. Flagging the tag being modified does not
817         * happen automatically.
818         * 
819         * @param modified
820         */
821        public final void setModified(final boolean modified)
822        {
823                setFlag(MODIFIED, modified);
824        }
825
826        /**
827         * 
828         * @return True, if the component tag has been marked modified
829         */
830        final boolean isModified()
831        {
832                return getFlag(MODIFIED);
833        }
834
835        /**
836         * 
837         * @return True if the HTML tag (e.g. br) has no close tag
838         */
839        public boolean hasNoCloseTag()
840        {
841                return getFlag(NO_CLOSE_TAG);
842        }
843
844        /**
845         * True if the HTML tag (e.g. br) has no close tag
846         * 
847         * @param hasNoCloseTag
848         */
849        public void setHasNoCloseTag(boolean hasNoCloseTag)
850        {
851                setFlag(NO_CLOSE_TAG, hasNoCloseTag);
852        }
853        
854        /**
855         * Sets the flag to indicate if the current tag contains a child 
856         * or a descendant with the "wicket:id" attribute. 
857         *
858         * @param containsWicketId
859         */
860        public void setContainsWicketId(boolean containsWicketId)
861        {
862                setFlag(CONTAINS_WICKET_ID, containsWicketId);
863        }
864
865        /**
866         * Says if the current tag contains a child or a descendant with the "wicket:id" attribute.
867         * @return true if the current tag contains a child or a descendant with the "wicket:id" attribute.
868         */
869    public boolean containsWicketId()
870    {
871        return getFlag(CONTAINS_WICKET_ID);
872    }
873    
874        /**
875         * In case of inherited markup, the base and the extended markups are merged and the information
876         * about the tags origin is lost. In some cases like wicket:head and wicket:link this
877         * information however is required.
878         * 
879         * @return wicketHeaderClass
880         */
881        public Class<? extends Component> getMarkupClass()
882        {
883                return (markupClassRef == null ? null : markupClassRef.get());
884        }
885
886        /**
887         * Set the class of wicket component which contains the wicket:head tag.
888         * 
889         * @param <C>
890         * 
891         * @param wicketHeaderClass
892         *            wicketHeaderClass
893         */
894        public <C extends Component> void setMarkupClass(Class<C> wicketHeaderClass)
895        {
896                if (wicketHeaderClass == null)
897                {
898                        markupClassRef = null;
899                }
900                else
901                {
902                        markupClassRef = new WeakReference<Class<? extends Component>>(wicketHeaderClass);
903                }
904        }
905
906        /**
907         * @see org.apache.wicket.markup.MarkupElement#equalTo(org.apache.wicket.markup.MarkupElement)
908         */
909        @Override
910        public boolean equalTo(final MarkupElement element)
911        {
912                if (element instanceof ComponentTag)
913                {
914                        final ComponentTag that = (ComponentTag)element;
915                        return getXmlTag().equalTo(that.getXmlTag());
916                }
917                return false;
918        }
919
920        /**
921         * Gets ignore.
922         * 
923         * @return If true than MarkupParser will remove it from the markup
924         */
925        public boolean isIgnore()
926        {
927                return getFlag(IGNORE);
928        }
929
930        /**
931         * Sets ignore.
932         * 
933         * @param ignore
934         *            If true than MarkupParser will remove it from the markup
935         */
936        public void setIgnore(boolean ignore)
937        {
938                setFlag(IGNORE, ignore);
939        }
940
941        /**
942         * @return True, if wicket:id has been automatically created (internal component)
943         */
944        public boolean isAutoComponentTag()
945        {
946                return getFlag(AUTO_COMPONENT);
947        }
948
949        /**
950         * @param auto
951         *            True, if wicket:id has been automatically created (internal component)
952         */
953        public void setAutoComponentTag(boolean auto)
954        {
955                setFlag(AUTO_COMPONENT, auto);
956        }
957
958        /**
959         * Gets userData.
960         * 
961         * @param key
962         *            The key to store and retrieve the value
963         * @return userData
964         */
965        public Object getUserData(final String key)
966        {
967                if (userData == null)
968                {
969                        return null;
970                }
971
972                return userData.get(key);
973        }
974
975        /**
976         * Sets userData.
977         * 
978         * @param key
979         *            The key to store and retrieve the value
980         * @param value
981         *            The user specific value to store
982         */
983        public void setUserData(final String key, final Object value)
984        {
985                if (userData == null)
986                {
987                        userData = new HashMap<>();
988                }
989                userData.put(key, value);
990        }
991
992        /**
993         * For subclasses to override. Gets called just before a Component gets rendered. It is
994         * guaranteed that the markupStream is set on the Component and determineVisibility is not yet
995         * called.
996         * 
997         * @param component
998         *            The component that is about to be rendered
999         * @param markupStream
1000         *            The current markup stream
1001         */
1002        public void onBeforeRender(final Component component, final MarkupStream markupStream)
1003        {
1004        }
1005
1006        public IAutoComponentFactory getAutoComponentFactory()
1007        {
1008                return autoComponentFactory;
1009        }
1010
1011        public void setAutoComponentFactory(IAutoComponentFactory autoComponentFactory)
1012        {
1013                this.autoComponentFactory = autoComponentFactory;
1014        }
1015
1016
1017}