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.util.Iterator;
020
021import org.apache.wicket.markup.parser.filter.HtmlHandler;
022import org.apache.wicket.util.lang.Args;
023import org.apache.wicket.util.string.AppendingStringBuffer;
024
025/**
026 * Represents a portion of a markup file, but always spans a complete tag. E.g.
027 * 
028 * <pre>
029 * open-body-close: &lt;span&gt;body&lt;/span&gt;
030 * open-close:      &lt;span/&gt;
031 * open-no-close:   &lt;input ...&gt;body
032 * </pre>
033 * 
034 * @see Markup
035 * @see MarkupElement
036 * 
037 * @author Juergen Donnerstag
038 */
039public class MarkupFragment extends AbstractMarkupFragment
040{
041        /** The parent markup. Must not be null. */
042        private final IMarkupFragment markup;
043
044        /** The index at which the fragment starts, relative to the parent markup */
045        private final int startIndex;
046
047        /** The size of the fragment (usually from open to close tag) */
048        private final int size;
049
050        /**
051         * Construct.
052         * 
053         * @param markup
054         *            The parent markup. May not be null.
055         * @param startIndex
056         *            The start index of the child markup
057         * @throws IndexOutOfBoundsException
058         *             if the index is out of range (<tt>index &lt; 0 || index &gt;= size()</tt>)
059         */
060        public MarkupFragment(final IMarkupFragment markup, final int startIndex)
061        {
062                Args.notNull(markup, "markup");
063
064                if (startIndex < 0)
065                {
066                        throw new IllegalArgumentException("Parameter 'startIndex' must not be < 0");
067                }
068
069                // cache the value for better performance
070                int markupSize = markup.size();
071
072                if (startIndex >= markupSize)
073                {
074                        throw new IllegalArgumentException(
075                                "Parameter 'startIndex' must not be >= markup.size()");
076                }
077
078                this.markup = markup;
079                this.startIndex = startIndex;
080
081                // Make sure we are at an open tag
082                MarkupElement startElem = markup.get(startIndex);
083                if ((startElem instanceof ComponentTag) == false)
084                {
085                        throw new IllegalArgumentException(
086                                "Parameter 'startIndex' does not point to a Wicket open tag");
087                }
088
089                // Determine the size. Find the close tag
090                int endIndex;
091                ComponentTag startTag = (ComponentTag)startElem;
092                if (startTag.isOpenClose())
093                {
094                        endIndex = startIndex;
095                }
096                else if (startTag.hasNoCloseTag())
097                {
098                        if (HtmlHandler.requiresCloseTag(startTag.getName()) == false)
099                        {
100                                // set endIndex to a "good" value
101                                endIndex = startIndex;
102                        }
103                        else
104                        {
105                                // set endIndex to a value which will indicate an error
106                                endIndex = markupSize;
107                        }
108                }
109                else
110                {
111                        for (endIndex = startIndex + 1; endIndex < markupSize; endIndex++)
112                        {
113                                MarkupElement elem = markup.get(endIndex);
114                                if (elem instanceof ComponentTag)
115                                {
116                                        ComponentTag tag = (ComponentTag)elem;
117                                        if (tag.closes(startTag))
118                                        {
119                                                break;
120                                        }
121                                }
122                        }
123                }
124
125                if (endIndex >= markupSize)
126                {
127                        throw new MarkupException("Unable to find close tag for: '" + startTag.toString() +
128                                "' in " + getRootMarkup().getMarkupResourceStream().toString());
129                }
130
131                size = endIndex - startIndex + 1;
132        }
133
134        @Override
135        public final MarkupElement get(final int index)
136        {
137                if ((index < 0) || (index > size))
138                {
139                        throw new IndexOutOfBoundsException("Parameter 'index' is out of range: 0 <= " + index +
140                                " <= " + size);
141                }
142
143                // Ask the parent markup
144                return markup.get(startIndex + index);
145        }
146
147        @Override
148        public final IMarkupFragment find(final String id)
149        {
150                if (size < 2)
151                {
152                        return null;
153                }
154                return find(id, 1);
155        }
156
157        @Override
158        public final MarkupResourceStream getMarkupResourceStream()
159        {
160                return markup.getMarkupResourceStream();
161        }
162
163        @Override
164        public final int size()
165        {
166                return size;
167        }
168
169        /**
170         * @return The parent markup. Null if that is a markup file.
171         */
172        private IMarkupFragment getParentMarkup()
173        {
174                return markup;
175        }
176
177        /**
178         * @return The Markup representing the underlying markup file with all its content
179         */
180        public final Markup getRootMarkup()
181        {
182                IMarkupFragment markup = getParentMarkup();
183                while ((markup != null) && !(markup instanceof Markup))
184                {
185                        markup = ((MarkupFragment)markup).getParentMarkup();
186                }
187                return (Markup)markup;
188        }
189
190        @Override
191        public String toString(boolean markupOnly)
192        {
193                final AppendingStringBuffer buf = new AppendingStringBuffer(400);
194                if (markupOnly == false)
195                {
196                        buf.append(getRootMarkup().getMarkupResourceStream().toString());
197                        buf.append('\n');
198                }
199
200                for (int i = 0; i < size(); i++)
201                {
202                        buf.append(get(i));
203                }
204                return buf.toString();
205        }
206
207        @Override
208        public Iterator<MarkupElement> iterator()
209        {
210                return new Iterator<MarkupElement>() {
211                        int index = 0;
212
213                        @Override
214                        public boolean hasNext() {
215                                return index < size;
216                        }
217
218                        @Override
219                        public MarkupElement next() {
220                                return get(index++);
221                        }
222
223                        @Override
224                        public void remove() {
225                                throw new UnsupportedOperationException("Cannot remove");
226                        }
227                };
228        }
229}