Allows adding/removing view change listeners from listeners. Change-Id: Idb2227e1423c0297887f01f6df03b74e633ad917tags/7.4.5
@@ -32,6 +32,7 @@ package com.vaadin.navigator; | |||
*/ | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
@@ -597,7 +598,10 @@ public class Navigator implements Serializable { | |||
* block the navigation operation | |||
*/ | |||
protected boolean fireBeforeViewChange(ViewChangeEvent event) { | |||
for (ViewChangeListener l : listeners) { | |||
// a copy of the listener list is needed to avoid | |||
// ConcurrentModificationException as a listener can add/remove | |||
// listeners | |||
for (ViewChangeListener l : new ArrayList<ViewChangeListener>(listeners)) { | |||
if (!l.beforeViewChange(event)) { | |||
return false; | |||
} | |||
@@ -647,7 +651,10 @@ public class Navigator implements Serializable { | |||
* view change event (not null) | |||
*/ | |||
protected void fireAfterViewChange(ViewChangeEvent event) { | |||
for (ViewChangeListener l : listeners) { | |||
// a copy of the listener list is needed to avoid | |||
// ConcurrentModificationException as a listener can add/remove | |||
// listeners | |||
for (ViewChangeListener l : new ArrayList<ViewChangeListener>(listeners)) { | |||
l.afterViewChange(event); | |||
} | |||
} |
@@ -0,0 +1,100 @@ | |||
package com.vaadin.tests.navigator; | |||
import com.vaadin.navigator.Navigator; | |||
import com.vaadin.navigator.View; | |||
import com.vaadin.navigator.ViewChangeListener; | |||
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.VerticalLayout; | |||
public class NavigatorListenerModifiesListeners extends AbstractTestUI { | |||
private Navigator navigator; | |||
protected static final String LABEL_MAINVIEW_ID = "LABEL_MAINVIEW_ID"; | |||
protected static final String LABEL_ANOTHERVIEW_ID = "LABEL_ANOTHERVIEW_ID"; | |||
// NOP view change listener | |||
private class MyViewChangeListener implements ViewChangeListener { | |||
@Override | |||
public boolean beforeViewChange(ViewChangeEvent event) { | |||
navigator.removeViewChangeListener(listener1); | |||
navigator.addViewChangeListener(listener2); | |||
return true; | |||
} | |||
@Override | |||
public void afterViewChange(ViewChangeEvent event) { | |||
navigator.removeViewChangeListener(listener2); | |||
navigator.addViewChangeListener(listener1); | |||
} | |||
} | |||
private MyViewChangeListener listener1 = new MyViewChangeListener(); | |||
private MyViewChangeListener listener2 = new MyViewChangeListener(); | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
navigator = new Navigator(this, this); | |||
navigator.addView(MainView.NAME, new MainView()); | |||
navigator.addView(AnotherView.NAME, new AnotherView()); | |||
navigator.addViewChangeListener(listener1); | |||
navigator.navigateTo(MainView.NAME); | |||
} | |||
class MainView extends VerticalLayout implements View { | |||
public static final String NAME = "mainview"; | |||
public MainView() { | |||
Label label = new Label("MainView content"); | |||
label.setId(LABEL_MAINVIEW_ID); | |||
addComponent(label); | |||
Button buttonNavToAnotherView = new Button( | |||
"Navigate to another view", new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
navigator.navigateTo(AnotherView.NAME); | |||
} | |||
}); | |||
addComponent(buttonNavToAnotherView); | |||
} | |||
@Override | |||
public void enter(ViewChangeEvent event) { | |||
} | |||
} | |||
class AnotherView extends VerticalLayout implements View { | |||
public static final String NAME = "another"; | |||
public AnotherView() { | |||
Label label = new Label("AnotherView content"); | |||
label.setId(LABEL_ANOTHERVIEW_ID); | |||
addComponent(label); | |||
} | |||
@Override | |||
public void enter(ViewChangeEvent event) { | |||
} | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Adding and removing view change listeners from view change listeners should not cause a ConcurrentModificationException"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 17477; | |||
} | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* Licensed 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 com.vaadin.tests.navigator; | |||
import static org.junit.Assert.assertEquals; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||
public class NavigatorListenerModifiesListenersTest extends SingleBrowserTest { | |||
@Test | |||
public void testIfConfirmBack() { | |||
openTestURL(); | |||
// keep URL of main view | |||
final String initialUrl = driver.getCurrentUrl(); | |||
// do it 2 times to verify that this is not broken after first time | |||
for (int i = 0; i < 2; i++) { | |||
// go to prompted view | |||
WebElement button = $(ButtonElement.class).first(); | |||
button.click(); | |||
// verify we are in another view and url is correct | |||
waitForElementPresent(By | |||
.id(NavigatorListenerModifiesListeners.LABEL_ANOTHERVIEW_ID)); | |||
String currentUrl = driver.getCurrentUrl(); | |||
assertEquals( | |||
"Current URL should be equal to another view URL", | |||
initialUrl | |||
.replace( | |||
NavigatorListenerModifiesListeners.MainView.NAME, | |||
NavigatorListenerModifiesListeners.AnotherView.NAME), | |||
currentUrl); | |||
// click back button | |||
driver.navigate().back(); | |||
// verify we are in main view and url is correct | |||
// without the fix for #17477, we get | |||
// ConcurrentModificationException | |||
waitForElementPresent(By | |||
.id(NavigatorListenerModifiesListeners.LABEL_MAINVIEW_ID)); | |||
currentUrl = driver.getCurrentUrl(); | |||
assertEquals("Current URL should be equal to the initial view URL", | |||
initialUrl, currentUrl); | |||
} | |||
} | |||
} |