/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket; import static org.hamcrest.CoreMatchers.equalToObject; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import java.lang.reflect.Field; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Random; import org.apache.commons.collections4.map.LinkedMap; import org.apache.wicket.core.util.lang.WicketObjects; import org.apache.wicket.markup.IMarkupResourceStreamProvider; import org.apache.wicket.markup.html.WebComponent; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.panel.EmptyPanel; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.resource.StringResourceStream; import org.apache.wicket.util.tester.WicketTestCase; import org.junit.Assert; import org.junit.Test; @SuppressWarnings({ "javadoc", "serial" }) public class MarkupContainerTest extends WicketTestCase { private static final int NUMBER_OF_CHILDREN_FOR_A_MAP = MarkupContainer.MAPIFY_THRESHOLD + 1; /** * Make sure components are iterated in the order they were added. Required e.g. for Repeaters */ @Test public void iteratorOrder() { MarkupContainer container = new WebMarkupContainer("component"); for (int i = 0; i < 10; i++) { container.add(new WebComponent(Integer.toString(i))); } int i = 0; for (Component component : container) { assertEquals(Integer.toString(i++), component.getId()); } } @Test public void markupId() throws Exception { executeTest(MarkupIdTestPage.class, "MarkupIdTestPageExpectedResult.html"); } @Test public void get() { WebMarkupContainer a = new WebMarkupContainer("a"); WebMarkupContainer b = new WebMarkupContainer("b"); WebMarkupContainer c = new WebMarkupContainer("c"); WebMarkupContainer d = new WebMarkupContainer("d"); WebMarkupContainer e = new WebMarkupContainer("e"); WebMarkupContainer f = new WebMarkupContainer("f"); // ....A // ...B....C // .......D..E // ...........F a.add(b); a.add(c); c.add(d); c.add(e); e.add(f); // basic gets assertTrue(a.get(null) == a); assertTrue(a.get("") == a); assertTrue(a.get("b") == b); assertTrue(a.get("c") == c); assertTrue(a.get("c:d") == d); assertTrue(a.get("c:e:f") == f); // parent path gets assertTrue(b.get("..") == a); assertTrue(e.get("..:..") == a); assertTrue(d.get("..:..:c:e:f") == f); assertTrue(e.get("..:d:..:e:f") == f); assertTrue(e.get("..:d:..:..") == a); // invalid gets assertNull(a.get("..")); assertNull(a.get("..:a")); assertNull(b.get("..|..")); assertNull(a.get("q")); } /** * https://issues.apache.org/jira/browse/WICKET-4006 */ @Test(expected = IllegalArgumentException.class) public void addMyself() { WebMarkupContainer me = new WebMarkupContainer("a"); me.add(me); } /** * https://issues.apache.org/jira/browse/WICKET-5911 */ @Test public void rerenderAfterRenderFailure() { FirstRenderFailsPage page = new FirstRenderFailsPage(); try { tester.startPage(page); } catch (WicketRuntimeException expected) { } tester.startPage(page); // rendering flags where properly reset, so second rendering works properly assertEquals(2, page.beforeRenderCalls); } /** * https://issues.apache.org/jira/browse/WICKET-4012 */ @Test public void afterRenderJustOnce() { AfterRenderJustOncePage page = new AfterRenderJustOncePage(); tester.startPage(page); assertEquals(1, page.afterRenderCalls); } /** * https://issues.apache.org/jira/browse/WICKET-4016 */ @Test public void callToStringFromConstructor() { ToStringComponent page = new ToStringComponent(); } private static class ToStringComponent extends WebMarkupContainer { private ToStringComponent() { super("id"); toString(true); } } private static class AfterRenderJustOncePage extends WebPage implements IMarkupResourceStreamProvider { private int afterRenderCalls = 0; private AfterRenderJustOncePage() { WebMarkupContainer a1 = new WebMarkupContainer("a1"); add(a1); WebMarkupContainer a2 = new WebMarkupContainer("a2"); a1.add(a2); WebMarkupContainer a3 = new WebMarkupContainer("a3") { @Override protected void onAfterRender() { super.onAfterRender(); afterRenderCalls++; } }; a2.add(a3); } @Override public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) { return new StringResourceStream( "<html><body><div wicket:id='a1'><div wicket:id='a2'><div wicket:id='a3'></div></div></div></body></html>"); } } private static class FirstRenderFailsPage extends WebPage implements IMarkupResourceStreamProvider { private boolean firstRender = true; private int beforeRenderCalls = 0; private FirstRenderFailsPage() { WebMarkupContainer a1 = new WebMarkupContainer("a1") { @Override protected void onBeforeRender() { super.onBeforeRender(); beforeRenderCalls++; if (firstRender) { firstRender = false; throw new WicketRuntimeException(); } } }; add(a1); } @Override public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) { return new StringResourceStream("<html><body><div wicket:id='a1'></div></body></html>"); } } /* * Iterator tests * * The tests below are specific for testing addition and removal of children while maintaining * the correct order of iterators without throwing ConcurrentModificationException. */ @Test public void noChildShouldNotIterate() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddingChildAfterIteratorAcquiredShouldIterateAndReturnNewChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); Label label1 = new Label("label1", "Label1"); wmc.add(label1); assertThat(wmc.size(), is(1)); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddingNChildrenAfterIteratorAcquiredShouldIterateAndReturnNewChildren() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); assertThat(wmc.size(), is(NUMBER_OF_CHILDREN_FOR_A_MAP)); Label label1 = new Label("label1", "Label1"); wmc.add(label1); Assert.assertThat(iterator.hasNext(), is(true)); takeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddingNChildrenAfterIteratorAcquiredShouldIterateAndReturnNewChildren2() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); assertThat(wmc.size(), is(NUMBER_OF_CHILDREN_FOR_A_MAP)); Iterator<Component> iterator = wmc.iterator(); takeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); Label label1 = new Label("label1", "Label1"); wmc.add(label1); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddingAndRemoveChildAfterIteratorAcquiredShouldNotIterate() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Iterator<Component> iterator = wmc.iterator(); wmc.add(label1); wmc.remove(label1); assertThat(wmc.size(), is(0)); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void addingNewChildAfterIterationHasStartedShouldIterateNewChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); // add one child addNChildren(wmc, 1); Iterator<Component> iterator = wmc.iterator(); // iterate takeNChildren(iterator, 1); // there are no more children to iterate Assert.assertThat(iterator.hasNext(), is(false)); // add the new child Label newChild = new Label("label1", "Label1"); wmc.add(newChild); assertThat(wmc.size(), is(2)); // ensure that the newChild is up next (as it was added) Assert.assertThat(iterator.next(), is(equalToObject(newChild))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void replacingTheFirstChildAfterIteratingDoesntIterateTheNewChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Component label2 = new Label("label2", "Label2"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); wmc.add(label1); wmc.add(label2); Iterator<Component> iterator = wmc.iterator(); takeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); iterator.next(); // replace the first child **after** we already passed the child with the iterator Label newChild = new Label("label1", "newChild"); wmc.replace(newChild); // the next child is still label 2 assertThat(iterator.next(), is(sameInstance(label2))); // and the new child is not iterated (was replaced before the current position of the // iterator). Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void removingComponentsDuringIterationDoesntFail() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Component label1 = new Label("label1", "Label1"); Component label2 = new Label("label2", "Label2"); Component label3 = new Label("label3", "Label3"); Component label4 = new Label("label4", "Label4"); Component label5 = new Label("label5", "Label5"); wmc.add(label1); wmc.add(label2); wmc.add(label3); wmc.add(label4); wmc.add(label5); // start iterating the 5 children Iterator<Component> iterator = wmc.iterator(); assertThat(iterator.next(), is(sameInstance(label1))); assertThat(iterator.next(), is(sameInstance(label2))); assertThat(iterator.next(), is(sameInstance(label3))); // remove the current, previous and next children wmc.remove(label3); wmc.remove(label2); wmc.remove(label4); // ensure that the next iterated child is the 5th label assertThat(iterator.next(), is(sameInstance(label5))); // and that there are no more children to iterate Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void childrenBecomesListWhenMoreThanOneChild() throws Exception { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, 5); Field childrenField = MarkupContainer.class.getDeclaredField("children"); childrenField.setAccessible(true); Object field = childrenField.get(wmc); assertThat(field, is(instanceOf(List.class))); } @Test public void childrenListBecomesMapWhenThresholdPassed() throws Exception { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP - 1); assertChildrenType(wmc, List.class); addNChildren(wmc, 1); assertChildrenType(wmc, LinkedMap.class); } @Test public void childrenBecomesLinkedMapWhenThresholdPassed() throws Exception { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP + 1); assertChildrenType(wmc, LinkedMap.class); } @Test public void linkedMapChildrenBecomesChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); wmc.add(new EmptyPanel("panel")); assertChildrenType(wmc, LinkedMap.class); Iterator<Component> iterator = wmc.iterator(); removeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); assertChildrenType(wmc, EmptyPanel.class); } @Test public void listChildrenBecomesChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP - 2); wmc.add(new EmptyPanel("panel")); assertChildrenType(wmc, List.class); Iterator<Component> iterator = wmc.iterator(); removeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP - 2); assertChildrenType(wmc, EmptyPanel.class); } @Test public void geenIdee3() throws Exception { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP + 1); Iterator<Component> iterator = wmc.iterator(); removeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); assertThat(iterator.hasNext(), is(true)); assertThat(wmc.size(), is(1)); iterator.next(); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddIterateAndRemoveChildShouldIterateChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Iterator<Component> iterator = wmc.iterator(); wmc.add(label1); Assert.assertThat(iterator.next(), is(equalToObject(label1))); wmc.remove(label1); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildAddIterateAndRemoveAndAddSameChildShouldIterateChildTwice() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Iterator<Component> iterator = wmc.iterator(); wmc.add(label1); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); wmc.remove(label1); Assert.assertThat(iterator.hasNext(), is(false)); wmc.add(label1); Assert.assertThat(iterator.next(), is(equalToObject(label1))); } @Test public void noChildAddIterateAndRemoveAndAddDifferentChildShouldIterateNewChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Label label2 = new Label("label1", "Label2"); Iterator<Component> iterator = wmc.iterator(); wmc.add(label1); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); wmc.remove(label1); Assert.assertThat(iterator.hasNext(), is(false)); wmc.add(label2); Assert.assertThat(iterator.next(), is(equalToObject(label2))); } @Test public void noChildAddingAndReplaceChildAfterIteratorAcquiredShouldIterateAndReturnNewReplacementChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Label label2 = new Label("label1", "Label2"); Iterator<Component> iterator = wmc.iterator(); wmc.add(label1); wmc.replace(label2); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void singleChildIterateOneChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; wmc.add(label1 = new Label("label1", "Label1")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void singleChildShouldAllowReplacingChildAfterIterationHasStarted() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Component label1 = new Label("label1", "Label1"); Component label2 = new Label("label1", "Label2"); wmc.add(label1); Iterator<Component> iterator = wmc.iterator(); wmc.replace(label2); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(sameInstance(label2))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void singleChildShouldAllowReplacingVisitedChildButNotRevisitReplacementChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Label label2 = new Label("label1", "Label2"); wmc.add(label1); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.hasNext(), is(true)); Assert.assertThat(iterator.next(), is(equalToObject(label1))); wmc.replace(label2); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void multipleChildIteratorRetainsOrderOfAddition() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); wmc.add(label3 = new Label("label3", "Label3")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowAddingComponentAfterIterationStarted() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); wmc.add(label3 = new Label("label3", "Label3")); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowRemovingComponentAfterIterationStarted0() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); wmc.add(label3 = new Label("label3", "Label3")); Iterator<Component> iterator = wmc.iterator(); wmc.remove(label1); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowRemovingComponentAfterIterationStarted1() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1 = new Label("label1", "Label1"); Label label2 = new Label("label2", "Label2"); Label label3 = new Label("label3", "Label3"); wmc.add(label1); wmc.add(label2); wmc.add(label3); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); wmc.remove(label1); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowRemovingComponentAfterIterationStarted2() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); wmc.add(label3 = new Label("label3", "Label3")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); wmc.remove(label1); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowRemovingComponentAfterIterationStarted3() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); wmc.add(label3 = new Label("label3", "Label3")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.next(), is(equalToObject(label3))); wmc.remove(label1); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowReplacingComponentAfterIterationStarted0() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); Iterator<Component> iterator = wmc.iterator(); wmc.replace(label3 = new Label("label1", "Label3")); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowReplacingComponentAfterIterationStarted1() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); Iterator<Component> iterator = wmc.iterator(); wmc.replace(label3 = new Label("label1", "Label3")); Assert.assertThat(iterator.next(), is(equalToObject(label3))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowReplacingComponentAfterIterationStarted() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); Assert.assertThat(iterator.next(), is(equalToObject(label2))); wmc.replace(label3 = new Label("label1", "Label3")); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void iteratorShouldAllowReplacingComponentAfterIterationStarted24() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Label label1; Label label2; Label label3; wmc.add(label1 = new Label("label1", "Label1")); wmc.add(label2 = new Label("label2", "Label2")); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); Iterator<Component> iterator = wmc.iterator(); Assert.assertThat(iterator.next(), is(equalToObject(label1))); wmc.replace(label3 = new Label("label2", "Label3")); Assert.assertThat(iterator.next(), is(equalToObject(label3))); takeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP); Assert.assertThat(iterator.hasNext(), is(false)); } @Test public void noChildLeftBehindRemoveEach() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); Iterator<Component> iterator = wmc.iterator(); while (iterator.hasNext()) { iterator.next(); iterator.remove(); } assertThat(wmc.size(), is(0)); } @Test public void noChildLeftBehindRemoveAll() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); Iterator<Component> iterator = wmc.iterator(); wmc.removeAll(); assertThat(wmc.size(), is(0)); assertThat(iterator.hasNext(), is(false)); } @Test public void noChildLeftBehindRemoveAll2() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); Iterator<Component> iterator = wmc.iterator(); iterator.next(); wmc.removeAll(); assertThat(wmc.size(), is(0)); assertThat(iterator.hasNext(), is(false)); } @Test public void ensureSerializationDeserializationWorks() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); assertThat(wmc.size(), is(NUMBER_OF_CHILDREN_FOR_A_MAP)); assertThat(WicketObjects.cloneObject(wmc), is(not(nullValue()))); removeNChildren(iterator, 1); assertThat(wmc.size(), is(NUMBER_OF_CHILDREN_FOR_A_MAP - 1)); assertThat(WicketObjects.cloneObject(wmc), is(not(nullValue()))); removeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP - 2); assertThat(WicketObjects.cloneObject(wmc), is(not(nullValue()))); assertThat(wmc.size(), is(1)); removeNChildren(iterator, 1); assertThat(wmc.size(), is(0)); assertThat(WicketObjects.cloneObject(wmc), is(not(nullValue()))); } @Test public void detachDuringIterationWorks() { int halfOfChildren = NUMBER_OF_CHILDREN_FOR_A_MAP / 2; int numberOfRemainingChildren = halfOfChildren + NUMBER_OF_CHILDREN_FOR_A_MAP % 2; WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP); takeNChildren(iterator, halfOfChildren); wmc.detach(); takeNChildren(iterator, numberOfRemainingChildren); assertThat(iterator.hasNext(), is(false)); } @Test public void detachDuringIterationWithRemovalsSucceeds() { WebMarkupContainer wmc = new WebMarkupContainer("id"); Iterator<Component> iterator = wmc.iterator(); addNChildren(wmc, 2); removeNChildren(iterator, 1); wmc.detach(); takeNChildren(iterator, 1); assertThat(iterator.hasNext(), is(false)); assertThat(wmc.size(), is(1)); } /** * Tests whether two iterators being used simultaneously keep correct score of where they are. */ @Test public void twoIteratorsWorkInTandem() { int n = NUMBER_OF_CHILDREN_FOR_A_MAP * 2; WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, n); Iterator<Component> iterator1 = wmc.iterator(); Iterator<Component> iterator2 = wmc.iterator(); Random r = new Random(); for (int i = 0; i < n; i++) { if (r.nextBoolean()) { iterator1.next(); iterator1.remove(); } else { iterator2.next(); iterator2.remove(); } } // after 2*N removals there should not be any child left assertThat(iterator1.hasNext(), is(false)); assertThat(iterator2.hasNext(), is(false)); } /** * Tests removing a child when an iterator is active, followed by a detach still has the correct * state for the iterator. */ @Test public void detachWithOneIteratorOneChild() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, 1); Iterator<Component> iterator1 = wmc.iterator(); iterator1.next(); iterator1.remove(); wmc.detach(); assertThat(iterator1.hasNext(), is(false)); } /** * Tests removing and adding a component when an iterator is active, followed by a detach still * has the correct state for the iterator. */ @Test public void detachWithOneIteratorOneChildRemovedAndAdded() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, 1); Iterator<Component> iterator1 = wmc.iterator(); iterator1.next(); iterator1.remove(); addNChildren(wmc, 1); assertThat(iterator1.hasNext(), is(true)); wmc.detach(); assertThat(iterator1.hasNext(), is(true)); assertThat(iterator1.next(), is(not(nullValue()))); } /** * Tests the case when one child is removed from a list the iterator still works after a detach. */ @Test public void detachWithOneIteratorTwoChildren() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, 2); Iterator<Component> iterator1 = wmc.iterator(); iterator1.next(); iterator1.remove(); assertThat(iterator1.hasNext(), is(true)); wmc.detach(); assertThat(iterator1.hasNext(), is(true)); assertThat(iterator1.next(), is(not(nullValue()))); } /** * Tests whether when the children is a list, removal and iteration still work after a detach. */ @Test public void detachWithOneIteratorWithListForChildren() { WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, NUMBER_OF_CHILDREN_FOR_A_MAP - 2); assertChildrenType(wmc, List.class); Iterator<Component> iterator = wmc.iterator(); takeNChildren(iterator, 1); removeNChildren(iterator, 1); wmc.detach(); takeNChildren(iterator, NUMBER_OF_CHILDREN_FOR_A_MAP - 4); assertThat(iterator.hasNext(), is(false)); } /** * Tests whether when the children is a map, removal and iteration still work after a detach. */ @Test public void detachWithOneIteratorsWithMapForChildren() { int n = NUMBER_OF_CHILDREN_FOR_A_MAP * 2; WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, n); Iterator<Component> iterator1 = wmc.iterator(); Random r = new Random(); for (int i = 0; i < NUMBER_OF_CHILDREN_FOR_A_MAP; i++) { iterator1.next(); iterator1.remove(); } wmc.detach(); for (int i = 0; i < NUMBER_OF_CHILDREN_FOR_A_MAP; i++) { iterator1.next(); iterator1.remove(); } assertThat(iterator1.hasNext(), is(false)); } /** * This tests a functional bug in the iterator implementation where you have multiple iterators * traversing the children, a detach happens and one of the iterators removes a child component * before the other iterator has a chance to update its internal state to the new world. This is * a known bug and we expect that this doesn't pose a problem in real world usage. */ @Test(expected = ConcurrentModificationException.class) public void knownBugForDetachWithTwoIteratorsAndRemovals() { int n = NUMBER_OF_CHILDREN_FOR_A_MAP * 2; WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, n); Iterator<Component> iterator1 = wmc.iterator(); Iterator<Component> iterator2 = wmc.iterator(); Random r = new Random(); for (int i = 0; i < NUMBER_OF_CHILDREN_FOR_A_MAP; i++) { if (r.nextBoolean()) { iterator1.next(); iterator1.remove(); } else { iterator2.next(); iterator2.remove(); } } wmc.detach(); iterator1.next(); iterator1.remove(); // implementation detail that gets in the way of properly solving this exotic use case: at // this moment iterator 2 doesn't know that the modification count was reset before the // iterator 1 removed the component. iterator2.next(); // code never reaches this point due to the ConcurrentModificationException } /** * This test is the working case for the above scenario where two iterators traverse the * children, the component gets detached and in this case both iterators have a chance to update * their internal state to the new world, before they continue to traverse the children. */ @Test public void detachWithTwoIteratorsAndRemovalsWork() { int n = NUMBER_OF_CHILDREN_FOR_A_MAP * 2; WebMarkupContainer wmc = new WebMarkupContainer("id"); addNChildren(wmc, n); Iterator<Component> iterator1 = wmc.iterator(); Iterator<Component> iterator2 = wmc.iterator(); Random r = new Random(); for (int i = 0; i < NUMBER_OF_CHILDREN_FOR_A_MAP; i++) { Iterator<Component> iterator = r.nextBoolean() ? iterator1 : iterator2; if (iterator.hasNext()) { iterator.next(); iterator.remove(); } } wmc.detach(); iterator1.next(); iterator2.next(); iterator1.remove(); while (iterator1.hasNext() || iterator2.hasNext()) { Iterator<Component> iterator = r.nextBoolean() ? iterator1 : iterator2; if (iterator.hasNext()) { iterator.next(); iterator.remove(); } } assertThat(iterator1.hasNext(), is(false)); assertThat(iterator2.hasNext(), is(false)); } /** * Asserts that the children property of the {@code wmc} is of a particular {@code type}. * * @param wmc * the web markup container whose children property is to be checked * @param type * the expected type */ private void assertChildrenType(WebMarkupContainer wmc, Class<?> type) { try { Field childrenField = MarkupContainer.class.getDeclaredField("children"); childrenField.setAccessible(true); Object field = childrenField.get(wmc); assertThat(field, is(instanceOf(type))); } catch (Exception e) { throw new AssertionError("Unable to read children", e); } } /** * Adds {@code numberOfChildrenToAdd} anonymous children to the {@code parent}. * * @param parent * the parent to add the children to * @param numberOfChildrenToAdd * the number of children */ private void addNChildren(WebMarkupContainer parent, int numberOfChildrenToAdd) { assertThat(numberOfChildrenToAdd, is(greaterThanOrEqualTo(0))); int start = parent.size(); for (int i = 0; i < numberOfChildrenToAdd; i++) { int index = start + i; parent.add(new Label("padding" + index, "padding" + index)); } } /** * Removes {@code numberOfChildrenToRemove} anonymous children from the parent using the * {@code iterator}. * * @param iterator * the iterator to remove the children with * @param numberOfChildrenToAdd * the number of children */ private void removeNChildren(Iterator<Component> iterator, int numberOfChildrenToRemove) { for (int i = 0; i < numberOfChildrenToRemove; i++) { iterator.next(); iterator.remove(); } } /** * Progresses the {@code iterator} with {@code numberOfChildrenToTake} anonymous children. * * @param iterator * the iterator to progress * @param numberOfChildrenToTake * the number of children */ private void takeNChildren(Iterator<Component> iterator, int numberOfChildrenToTake) { for (int i = 0; i < numberOfChildrenToTake; i++) iterator.next(); } @Test public void stream() { LoginPage loginPage = new LoginPage(); Optional<Component> first = loginPage.stream() .filter(c -> c.getId().equals("form")) .findFirst(); assertThat(first.isPresent(), is(false)); loginPage.add(new Form<>("form")); Optional<Component> second = loginPage.stream() .filter(c -> c.getId().equals("form")) .findFirst(); assertThat(second.isPresent(), is(true)); loginPage.add(new WebMarkupContainer("wmc")); Optional<Form> form = loginPage.stream() .filter(Form.class::isInstance) .map(Form.class::cast) .findFirst(); assertThat(form.isPresent(), is(true)); Optional<WebMarkupContainer> wmc = loginPage.stream() .filter(WebMarkupContainer.class::isInstance) .map(WebMarkupContainer.class::cast) .findFirst(); assertThat(wmc.isPresent(), is(true)); } @Test public void streamChildren() { LoginPage loginPage = new LoginPage(); Optional<Component> first = loginPage.stream() .filter(c -> c.getId().equals("form")) .findFirst(); assertThat(first.isPresent(), is(false)); Form<Object> form = new Form<>("form"); loginPage.add(form); form.add(new TextField<>("field")); assertThat(loginPage.streamChildren() .filter(c -> c.getId().equals("form")) .findFirst() .isPresent(), is(true)); assertThat(loginPage.streamChildren() .filter(c -> c.getId().equals("field")) .findFirst() .isPresent(), is(true)); assertThat(loginPage.streamChildren() .filter(TextField.class::isInstance) .filter(c -> c.getId().equals("field")) .findFirst() .isPresent(), is(true)); } }