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; 018 019import java.util.ArrayDeque; 020 021import org.apache.wicket.markup.ComponentTag; 022import org.apache.wicket.markup.IMarkupFragment; 023import org.apache.wicket.markup.MarkupElement; 024 025/** 026 * Context for component dequeueing. Keeps track of markup position and container stack. 027 * 028 * @author igor 029 * 030 */ 031public final class DequeueContext 032{ 033 private final IMarkupFragment markup; 034 private int index; 035 private ComponentTag next; 036 private ArrayDeque<ComponentTag> tags = new ArrayDeque<>(); 037 private final boolean skipFirst; 038 private ComponentTag first; 039 040 private ArrayDeque<MarkupContainer> containers = new ArrayDeque<>(); 041 042 /** A bookmark for the DequeueContext stack */ 043 public static final class Bookmark 044 { 045 private final int index; 046 private final ComponentTag next; 047 private final ArrayDeque<ComponentTag> tags; 048 private final ArrayDeque<MarkupContainer> containers; 049 050 private Bookmark(DequeueContext parser) 051 { 052 this.index = parser.index; 053 this.next = parser.next; 054 this.tags = new ArrayDeque<>(parser.tags); 055 this.containers = new ArrayDeque<>(parser.containers); 056 } 057 058 private void restore(DequeueContext parser) 059 { 060 parser.index = index; 061 parser.next = next; 062 parser.tags = new ArrayDeque<>(tags); 063 parser.containers = new ArrayDeque<>(containers); 064 } 065 } 066 067 public DequeueContext(IMarkupFragment markup, MarkupContainer root, boolean skipFirst) 068 { 069 this.markup = markup; 070 this.skipFirst = skipFirst; 071 this.containers.push(root); 072 this.next = nextTag(); 073 } 074 075 /** 076 * Saves the state of the context into a bookmark which can later be used to restore it. 077 */ 078 public Bookmark save() 079 { 080 return new Bookmark(this); 081 } 082 083 /** 084 * Restores the state of the context from the bookmark 085 * 086 * @param bookmark 087 */ 088 public void restore(Bookmark bookmark) 089 { 090 bookmark.restore(this); 091 } 092 093 /** 094 * Peeks markup tag that would be retrieved by call to {@link #takeTag()} 095 * 096 * @return 097 */ 098 public ComponentTag peekTag() 099 { 100 return next; 101 } 102 103 /** 104 * Retrieves the next markup tag 105 * 106 * @return 107 */ 108 public ComponentTag takeTag() 109 { 110 ComponentTag taken = next; 111 112 if (taken == null) 113 { 114 return null; 115 } 116 117 if (taken.isOpen() && !taken.hasNoCloseTag()) 118 { 119 tags.push(taken); 120 } 121 else if (tags.size() > 0 && taken.closes(tags.peek())) 122 { 123 tags.pop(); 124 } 125 next = nextTag(); 126 return taken; 127 } 128 129 /** 130 * Skips to the closing tag of the tag retrieved from last call to {@link #takeTag()} 131 */ 132 public void skipToCloseTag() 133 { 134 while (!next.closes(tags.peek())) 135 { 136 next = nextTag(); 137 } 138 } 139 140 private ComponentTag nextTag() 141 { 142 if (skipFirst && first == null) 143 { 144 for (; index < markup.size(); index++) 145 { 146 MarkupElement element = markup.get(index); 147 if (element instanceof ComponentTag) 148 { 149 first = (ComponentTag)element; 150 index++; 151 break; 152 } 153 } 154 } 155 156 for (; index < markup.size(); index++) 157 { 158 MarkupElement element = markup.get(index); 159 if (element instanceof ComponentTag) 160 { 161 ComponentTag tag = (ComponentTag)element; 162 163 if (tag.isOpen() || tag.isOpenClose()) 164 { 165 DequeueTagAction action = canDequeueTag(tag); 166 switch (action) 167 { 168 case IGNORE : 169 continue; 170 case DEQUEUE : 171 index++; 172 return tag; 173 case SKIP : // skip to close tag 174 boolean found = false; 175 for (; index < markup.size(); index++) 176 { 177 if ((markup.get(index) instanceof ComponentTag) 178 && markup.get(index).closes(tag)) 179 { 180 found = true; 181 break; 182 } 183 } 184 if (!found) 185 { 186 throw new IllegalStateException(String.format( 187 "Could not find close tag for tag '%s' in markup: %s ", tag, 188 markup)); 189 } 190 191 } 192 } 193 else 194 { 195 // closed tag 196 ComponentTag open = tag.isClose() ? tag.getOpenTag() : tag; 197 198 if (skipFirst && first != null && open == first) 199 { 200 continue; 201 } 202 203 switch (canDequeueTag(open)) 204 { 205 case DEQUEUE : 206 index++; 207 return tag; 208 case IGNORE : 209 continue; 210 case SKIP : 211 throw new IllegalStateException(String.format( 212 "Should not see closed tag of skipped open tag '%s' in markup:%s", 213 tag, markup)); 214 } 215 } 216 } 217 } 218 return null; 219 } 220 221 private DequeueTagAction canDequeueTag(ComponentTag open) 222 { 223 if (containers.size() < 1) 224 { 225 // TODO queueing message: called too early 226 throw new IllegalStateException(); 227 } 228 229 DequeueTagAction action; 230 for (MarkupContainer container : containers) 231 { 232 action = container.canDequeueTag(open); 233 if (action != null) 234 { 235 return action; 236 } 237 } 238 return DequeueTagAction.IGNORE; 239 } 240 241 /** 242 * Checks if the tag returned by {@link #peekTag()} is either open or open-close. 243 * 244 * @return 245 */ 246 public boolean isAtOpenOrOpenCloseTag() 247 { 248 ComponentTag tag = peekTag(); 249 return tag != null && (tag.isOpen() || tag.isOpenClose()); 250 } 251 252 /** 253 * Retrieves the container on the top of the containers stack 254 * 255 * @return 256 */ 257 public MarkupContainer peekContainer() 258 { 259 return containers.peek(); 260 } 261 262 /** 263 * Pushes a container onto the container stack 264 * 265 * @param container 266 */ 267 public void pushContainer(MarkupContainer container) 268 { 269 containers.push(container); 270 } 271 272 /** 273 * Pops a container from the container stack 274 * 275 * @return 276 */ 277 public MarkupContainer popContainer() 278 { 279 return containers.pop(); 280 } 281 282 /** 283 * Searches the container stack for a component that can be dequeude 284 * 285 * @param tag 286 * @return 287 */ 288 public Component findComponentToDequeue(ComponentTag tag) 289 { 290 for (MarkupContainer container : containers) 291 { 292 Component child = container.findComponentToDequeue(tag); 293 if (child != null) 294 { 295 return child; 296 } 297 } 298 return null; 299 } 300 301}