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.parser.filter;
018
019import java.text.ParseException;
020import java.util.ArrayDeque;
021import java.util.Deque;
022
023import org.apache.wicket.Component;
024import org.apache.wicket.MarkupContainer;
025import org.apache.wicket.markup.ComponentTag;
026import org.apache.wicket.markup.ComponentTag.IAutoComponentFactory;
027import org.apache.wicket.markup.MarkupElement;
028import org.apache.wicket.markup.MarkupResourceStream;
029import org.apache.wicket.markup.MarkupStream;
030import org.apache.wicket.markup.WicketParseException;
031import org.apache.wicket.markup.WicketTag;
032import org.apache.wicket.markup.html.internal.Enclosure;
033import org.apache.wicket.markup.parser.AbstractMarkupFilter;
034import org.apache.wicket.markup.resolver.IComponentResolver;
035import org.apache.wicket.util.string.Strings;
036
037
038/**
039 * This is a markup inline filter. It identifies <wicket:enclosure> tags. If the 'child'
040 * attribute is empty it determines the wicket:id of the child component automatically by analyzing
041 * the wicket component (in this case on one wicket component is allowed) in between the open and
042 * close tags. If the enclosure tag has a 'child' attribute like
043 * <code>&lt;wicket:enclosure child="xxx"&gt;</code> than more than just one wicket component inside
044 * the enclosure tags are allowed and the child component which determines the visibility of the
045 * enclosure is identified by the 'child' attribute value which must be equal to the relative child
046 * id path.
047 * 
048 * @see Enclosure
049 * 
050 * @author Juergen Donnerstag
051 */
052public final class EnclosureHandler extends AbstractMarkupFilter implements IComponentResolver
053{
054        private static final long serialVersionUID = 1L;
055
056        private static final IAutoComponentFactory FACTORY = new IAutoComponentFactory()
057        {
058                @Override
059                public Component newComponent(MarkupContainer container, ComponentTag tag)
060                {
061                        return new Enclosure(tag.getId(), tag
062                                .getAttribute(EnclosureHandler.CHILD_ATTRIBUTE));
063                }
064        };
065
066        /** */
067        public static final String ENCLOSURE = "enclosure";
068
069        /** The child attribute */
070        public static final String CHILD_ATTRIBUTE = "child";
071
072        /** Stack of &lt;wicket:enclosure&gt; tags */
073        private Deque<ComponentTag> stack;
074
075        /** The id of the first wicket tag inside the enclosure */
076        private String childId;
077
078        /**
079         * Construct.
080         */
081        public EnclosureHandler()
082        {
083                this(null);
084        }
085
086        public EnclosureHandler(MarkupResourceStream resourceStream)
087        {
088                super(resourceStream);
089        }
090
091        @Override
092        protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException
093        {
094                final boolean isWicketTag = tag instanceof WicketTag;
095                final boolean isEnclosureTag = isWicketTag && ((WicketTag)tag).isEnclosureTag();
096
097                // If wicket:enclosure
098                if (isEnclosureTag)
099                {
100                        // If open tag, than put the tag onto the stack
101                        if (tag.isOpen())
102                        {
103                                tag.setId(tag.getId() + getRequestUniqueId());
104                                tag.setModified(true);
105                                tag.setAutoComponentFactory(FACTORY);
106
107                                if (stack == null)
108                                {
109                                        stack = new ArrayDeque<>();
110                                }
111                                stack.push(tag);
112                        }
113                        // If close tag, then remove the tag from the stack and update
114                        // the child attribute of the open tag if required
115                        else if (tag.isClose())
116                        {
117                                if (stack == null)
118                                {
119                                        throw new WicketParseException("Missing open tag for Enclosure:", tag);
120                                }
121
122                                // Remove the open tag from the stack
123                                ComponentTag lastEnclosure = stack.pop();
124
125                                // If the child attribute has not been given by the user,
126                                // then ...
127                                if (childId != null)
128                                {
129                                        lastEnclosure.put(CHILD_ATTRIBUTE, childId);
130                                        lastEnclosure.setModified(true);
131                                        childId = null;
132                                }
133
134                                if (stack.size() == 0)
135                                {
136                                        stack = null;
137                                }
138                        }
139                        else
140                        {
141                                throw new WicketParseException("Open-close tag not allowed for Enclosure:", tag);
142                        }
143                }
144                // Are we inside a wicket:enclosure tag?
145                else if (stack != null)
146                {
147                        ComponentTag lastEnclosure = stack.getFirst();
148
149                        // If the enclosure tag has NO child attribute, then ...
150                        if (Strings.isEmpty(lastEnclosure.getAttribute(CHILD_ATTRIBUTE)))
151                        {
152                                String id = tag.getAttribute(getWicketNamespace() + ":id");
153                                if (id != null)
154                                {
155                                        // We encountered more than one child component inside
156                                        // the enclosure and are not able to automatically
157                                        // determine the child component to delegate the
158                                        // isVisible() to => Exception
159                                        if (childId != null)
160                                        {
161                                                throw new WicketParseException("Use <" + getWicketNamespace() +
162                                                        ":enclosure child='xxx'> to name the child component:", tag);
163                                        }
164                                        // Remember the child id. The open tag will be updated
165                                        // once the close tag is found. See above.
166                                        childId = id;
167                                }
168                        }
169                }
170
171                return tag;
172        }
173
174        @Override
175        public Component resolve(final MarkupContainer container, final MarkupStream markupStream,
176                final ComponentTag tag)
177        {
178                if ((tag instanceof WicketTag) && ((WicketTag)tag).isEnclosureTag())
179                {
180                        // Yes, we handled the tag
181                        return new Enclosure(tag.getId(), tag.getAttribute(EnclosureHandler.CHILD_ATTRIBUTE));
182                }
183
184                // We were not able to handle the tag
185                return null;
186        }
187}