--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+/**
+ * Fragment manager that handles interaction between Navigator and URI fragments
+ * or other similar view identification and bookmarking system.
+ *
+ * Alternative implementations can be created for HTML5 pushState, for portlet
+ * URL navigation and other similar systems.
+ *
+ * This interface is mostly for internal use by {@link Navigator}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public interface FragmentManager extends Serializable {
+ /**
+ * Return the current fragment (location string) including view name and any
+ * optional parameters.
+ *
+ * @return current view and parameter string, not null
+ */
+ public String getFragment();
+
+ /**
+ * Set the current fragment (location string) in the application URL or
+ * similar location, including view name and any optional parameters.
+ *
+ * @param fragment
+ * new view and parameter string, not null
+ */
+ public void setFragment(String fragment);
+}
\ No newline at end of file
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.CustomComponent;
+import com.vaadin.ui.Root;
+import com.vaadin.ui.Root.FragmentChangedEvent;
+import com.vaadin.ui.Root.FragmentChangedListener;
+
+/**
+ * Navigator utility that allows switching of views in a part of an application.
+ *
+ * The view switching can be based e.g. on URI fragments containing the view
+ * name and parameters to the view. There are two types of parameters for views:
+ * an optional parameter string that is included in the fragment (may be
+ * bookmarkable) and optional internal parameters that are not.
+ *
+ * Views can be explicitly registered or dynamically generated and listening to
+ * view changes is possible.
+ *
+ * Note that {@link Navigator} is not a component itself but comes with
+ * {@link SimpleViewDisplay} which is a component that displays the selected
+ * view as its contents.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public class Navigator implements Serializable {
+
+ // TODO divert navigation e.g. if no permissions? Or just show another view
+ // but keep URL? how best to intercept
+ // TODO investigate relationship to TouchKit navigation support
+
+ /**
+ * Empty view component.
+ */
+ public static class EmptyView extends CssLayout implements View {
+ public void init() {
+ setWidth("0px");
+ setHeight("0px");
+ }
+
+ public void navigateTo(String fragmentParameters,
+ Object... internalParameters) {
+ // nothing to do
+ }
+ }
+
+ /**
+ * Fragment manager using URI fragments of a Root to track views and enable
+ * listening to view changes.
+ *
+ * This class is mostly for internal use by Navigator, and is only public
+ * and static to enable testing.
+ */
+ public static class UriFragmentManager implements FragmentManager,
+ FragmentChangedListener {
+ private final Root root;
+ private final Navigator navigator;
+
+ /**
+ * Create a new URIFragmentManager and attach it to listen to URI
+ * fragment changes of a {@link Root}.
+ *
+ * @param root
+ * root whose URI fragment to get and modify
+ * @param navigator
+ * {@link Navigator} to notify of fragment changes (using
+ * {@link Navigator#navigateTo(String, Object...)}
+ */
+ public UriFragmentManager(Root root, Navigator navigator) {
+ this.root = root;
+ this.navigator = navigator;
+
+ root.addListener(this);
+ }
+
+ public String getFragment() {
+ return root.getFragment();
+ }
+
+ public void setFragment(String fragment) {
+ // TODO ", false" ???
+ root.setFragment(fragment);
+ }
+
+ public void fragmentChanged(FragmentChangedEvent event) {
+ UriFragmentManager.this.navigator.navigateTo(getFragment());
+ }
+ }
+
+ /**
+ * View display that is a component itself and replaces its contents with
+ * the view.
+ *
+ * This display only supports views that are {@link Component}s themselves.
+ * Attempting to display a view that is not a component causes an exception
+ * to be thrown.
+ *
+ * By default, the view display has full size.
+ */
+ public static class SimpleViewDisplay extends CustomComponent implements
+ ViewDisplay {
+
+ /**
+ * Create new {@link ViewDisplay} that is itself a component displaying
+ * the view.
+ */
+ public SimpleViewDisplay() {
+ setSizeFull();
+ }
+
+ public void showView(View view) {
+ if (view instanceof Component) {
+ setCompositionRoot((Component) view);
+ } else {
+ throw new IllegalArgumentException("View is not a component: "
+ + view);
+ }
+ }
+ }
+
+ /**
+ * View provider which uses a map from view name to pre-created and
+ * registered view instances.
+ */
+ public static class RegisteredViewProvider implements ViewProvider {
+
+ private HashMap<String, View> viewNameToView = new HashMap<String, View>();
+
+ public String getViewName(String viewAndParameters) {
+ if (null == viewAndParameters) {
+ return null;
+ }
+ for (String viewName : viewNameToView.keySet()) {
+ if (viewAndParameters.equals(viewName)
+ || viewAndParameters.startsWith(viewName + "/")) {
+ return viewName;
+ }
+ }
+ return null;
+ }
+
+ public View getView(String viewName) {
+ return viewNameToView.get(viewName);
+ }
+
+ /**
+ * Register a view for a view name.
+ *
+ * Registering another view with a name that is already registered
+ * overwrites the old registration.
+ *
+ * @param viewName
+ * String that identifies a view (not null nor empty string)
+ * @param view
+ * {@link View} instance (not null)
+ */
+ public void addView(String viewName, View view) {
+
+ // Check parameters
+ if (viewName == null || view == null || viewName.length() == 0) {
+ throw new IllegalArgumentException(
+ "view and viewName must be non-null and not empty");
+ }
+
+ viewNameToView.put(viewName, view);
+ }
+
+ /**
+ * Remove view from navigator.
+ *
+ * @param viewName
+ * name of the view to remove
+ */
+ public void removeView(String viewName) {
+ viewNameToView.remove(viewName);
+ }
+ }
+
+ /**
+ * View provider which uses a map from view name to the class to instantiate
+ * for the view.
+ *
+ * Views that have been created are cached and reused when the same view
+ * name is requested again.
+ *
+ * Note that the view class must be accessible by the class loader used by
+ * the provider. This may require its visibility to be public.
+ */
+ public static class ClassBasedViewProvider implements ViewProvider {
+
+ private HashMap<String, Class<? extends View>> viewNameToClass = new HashMap<String, Class<? extends View>>();
+ private HashMap<Class<? extends View>, String> classToViewName = new HashMap<Class<? extends View>, String>();
+ /**
+ * Already opened (cached) views that can be reopened or reused with new
+ * parameters.
+ */
+ private HashMap<Class<? extends View>, View> classToView = new HashMap<Class<? extends View>, View>();
+
+ public String getViewName(String viewAndParameters) {
+ if (null == viewAndParameters) {
+ return null;
+ }
+ for (String viewName : viewNameToClass.keySet()) {
+ if (viewAndParameters.equals(viewName)
+ || viewAndParameters.startsWith(viewName + "/")) {
+ return viewName;
+ }
+ }
+ return null;
+ }
+
+ public View getView(String viewName) {
+ Class<? extends View> newViewClass = viewNameToClass.get(viewName);
+ if (null == newViewClass) {
+ return null;
+ }
+ if (!classToView.containsKey(newViewClass)) {
+ try {
+ View view = newViewClass.newInstance();
+ view.init();
+ classToView.put(newViewClass, view);
+ } catch (InstantiationException e) {
+ // TODO error handling
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ // TODO error handling
+ throw new RuntimeException(e);
+ }
+ }
+ // return already cached view
+ final View v = classToView.get(newViewClass);
+ return v;
+ }
+
+ /**
+ * Register a view class for a view name.
+ *
+ * @param viewName
+ * String that identifies a view (not null nor empty string)
+ * @param viewClass
+ * Component class that implements Navigator.View interface
+ * (not null)
+ */
+ public void addView(String viewName, Class<? extends View> viewClass) {
+
+ // Check parameters
+ if (viewName == null || viewClass == null || viewName.length() == 0) {
+ throw new IllegalArgumentException(
+ "viewClass and viewName must be non-null and not empty");
+ }
+
+ if (!View.class.isAssignableFrom(viewClass)) {
+ throw new IllegalArgumentException(
+ "viewClass must implement Navigator.View");
+ }
+
+ if (viewNameToClass.containsKey(viewName)) {
+ if (viewNameToClass.get(viewName) == viewClass) {
+ return;
+ }
+
+ throw new IllegalArgumentException(viewNameToClass
+ .get(viewName).getName()
+ + " is already mapped to '"
+ + viewName + "'");
+ }
+
+ if (classToViewName.containsKey(viewClass)) {
+ throw new IllegalArgumentException(
+ "Each view class can only be added to Navigator with one view name");
+ }
+
+ viewNameToClass.put(viewName, viewClass);
+ classToViewName.put(viewClass, viewName);
+ }
+
+ /**
+ * Remove view from navigator.
+ *
+ * @param viewName
+ * name of the view to remove
+ */
+ public void removeView(String viewName) {
+ Class<? extends View> c = viewNameToClass.get(viewName);
+ if (c != null) {
+ viewNameToClass.remove(viewName);
+ classToViewName.remove(c);
+ classToView.remove(c);
+ }
+ }
+
+ /**
+ * Get the view name for given view implementation class.
+ *
+ * @param viewClass
+ * Class that implements the view.
+ * @return view name for which the view class is registered, null if
+ * none
+ */
+ public String getViewName(Class<? extends View> viewClass) {
+ return classToViewName.get(viewClass);
+ }
+
+ /**
+ * Get the view class for given view name.
+ *
+ * @param viewName
+ * view name to get view for
+ * @return View that corresponds to the name
+ */
+ public Class<? extends View> getViewClass(String viewName) {
+ return viewNameToClass.get(viewName);
+ }
+ }
+
+ private final FragmentManager fragmentManager;
+ private final ViewDisplay display;
+ private String mainViewName = null;
+ private View currentView = null;
+ private List<ViewChangeListener> listeners = new LinkedList<ViewChangeListener>();
+ private List<ViewProvider> providers = new LinkedList<ViewProvider>();
+
+ /**
+ * Create a navigator that is tracking the active view using URI fragments.
+ *
+ * @param root
+ * whose URI fragments are used
+ * @param display
+ * where to display the views
+ */
+ public Navigator(Root root, ViewDisplay display) {
+ this.display = display;
+ fragmentManager = new UriFragmentManager(root, this);
+ }
+
+ /**
+ * Create a navigator.
+ *
+ * When a custom fragment manager is not needed, use the constructor
+ * {@link #Navigator(Root, ViewDisplay)} which uses a URI fragment based
+ * fragment manager.
+ *
+ * @param fragmentManager
+ * fragment manager keeping track of the active view and enabling
+ * bookmarking and direct navigation
+ * @param display
+ * where to display the views
+ */
+ public Navigator(FragmentManager fragmentManager, ViewDisplay display) {
+ this.display = display;
+ this.fragmentManager = fragmentManager;
+ }
+
+ /**
+ * Navigate to a view and initialize the view with given parameters.
+ *
+ * The view string consists of a view name optionally followed by a slash
+ * and (fragment) parameters. ViewProviders are used to find and create the
+ * correct type of view.
+ *
+ * If the view being left indicates it wants a confirmation for the
+ * navigation operation, the user is asked for the confirmation.
+ *
+ * @param viewAndParameters
+ * view name and parameters
+ * @param internalParameters
+ * parameters that are only passed when switching views
+ * explicitly on the server side, not bookmarkable
+ */
+ public void navigateTo(String viewAndParameters,
+ Object... internalParameters) {
+ if ("".equals(viewAndParameters)) {
+ viewAndParameters = mainViewName;
+ }
+ for (ViewProvider provider : providers) {
+ String viewName = provider.getViewName(viewAndParameters);
+ if (null != viewName) {
+ String parameters = null;
+ if (viewAndParameters.length() > viewName.length() + 1) {
+ parameters = viewAndParameters
+ .substring(viewName.length() + 1);
+ }
+ View view = provider.getView(viewName);
+ if (null != view) {
+ navigateTo(view, viewName, parameters, internalParameters);
+ // stop after a view is found
+ return;
+ }
+ }
+ }
+ // TODO if no view is found, use main view or do nothing?
+ }
+
+ /**
+ * Internal method activating a view, setting its parameters and calling
+ * listeners.
+ *
+ * This method also verifies that the user is allowed to perform the
+ * navigation operation.
+ *
+ * @param view
+ * view to activate
+ * @param viewName
+ * (optional) name of the view or null not to set the fragment
+ * @param fragmentParameters
+ * parameters passed in the fragment for the view
+ * @param internalParameters
+ * parameters that are only passed when switching views
+ * explicitly on the server side, not bookmarkable
+ */
+ protected void navigateTo(View view, String viewName,
+ String fragmentParameters, Object... internalParameters) {
+ if (!isViewChangeAllowed(currentView, view, viewName,
+ fragmentParameters, internalParameters)) {
+ return;
+ }
+
+ if (null != viewName && getFragmentManager() != null) {
+ String currentFragment = viewName;
+ if (fragmentParameters != null) {
+ currentFragment += "/" + fragmentParameters;
+ }
+ if (!currentFragment.equals(getFragmentManager().getFragment())) {
+ getFragmentManager().setFragment(currentFragment);
+ }
+ }
+
+ view.navigateTo(fragmentParameters, internalParameters);
+ View previousView = currentView;
+ currentView = view;
+
+ if (display != null) {
+ display.showView(view);
+ }
+
+ fireViewChange(previousView);
+ }
+
+ /**
+ * Check whether view change is allowed.
+ *
+ * All related listeners are called. The view change is blocked if any of
+ * them wants to block the navigation operation.
+ *
+ * The view change listeners may also e.g. open a warning or question dialog
+ * and save the parameters to re-initiate the navigation operation upon user
+ * action.
+ *
+ * @param previous
+ * the view being deactivated
+ * @param next
+ * the view being activated
+ * @param viewName
+ * name of the view being activated
+ * @param fragmentParameters
+ * parameters passed in the fragment for the view
+ * @param internalParameters
+ * parameters that are only passed on the server side, not
+ * bookmarkable
+ * @return true if the view change should be allowed, false to silently
+ * block the navigation operation
+ */
+ protected boolean isViewChangeAllowed(View previous, View next,
+ String viewName, String fragmentParameters,
+ Object[] internalParameters) {
+ for (ViewChangeListener l : listeners) {
+ if (!l.isViewChangeAllowed(previous, next, viewName,
+ fragmentParameters, internalParameters)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Return the fragment manager that is used to get, listen to and manipulate
+ * the URI fragment or other source of navigation information.
+ *
+ * @return fragment manager in use
+ */
+ protected FragmentManager getFragmentManager() {
+ return fragmentManager;
+ }
+
+ /**
+ * Get the main view.
+ *
+ * Main view is the default view shown to user when he opens application
+ * without specifying view name.
+ *
+ * @return name of the main view.
+ */
+ public String getMainView() {
+ return mainViewName;
+ }
+
+ /**
+ * Set the main view.
+ *
+ * Main view is the default view shown to user when he opens application
+ * without specifying view name. If the {@link Navigator} does not have a
+ * current view, the main view is automatically selected when set.
+ *
+ * @param mainViewName
+ * name of the main view.
+ */
+ public void setMainView(String mainViewName) {
+ this.mainViewName = mainViewName;
+ // TODO should this be done?
+ if (currentView == null) {
+ navigateTo(mainViewName);
+ }
+ }
+
+ /**
+ * Fire an event when the current view has changed.
+ *
+ * @param previousView
+ */
+ protected void fireViewChange(View previousView) {
+ for (ViewChangeListener l : listeners) {
+ l.navigatorViewChanged(previousView, currentView);
+ }
+ }
+
+ /**
+ * Register a view provider (factory).
+ *
+ * Providers are called in order of registration until one that can handle
+ * the requested view name is found.
+ *
+ * @param provider
+ * provider to register
+ */
+ public void registerProvider(ViewProvider provider) {
+ providers.add(provider);
+ }
+
+ /**
+ * Unregister a view provider (factory).
+ *
+ * @param provider
+ * provider to unregister
+ */
+ public void unregisterProvider(ViewProvider provider) {
+ providers.remove(provider);
+ }
+
+ /**
+ * Listen to changes of the active view.
+ *
+ * The listener will get notified after the view has changed.
+ *
+ * @param listener
+ * Listener to invoke after view changes.
+ */
+ public void addListener(ViewChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a view change listener.
+ *
+ * @param listener
+ * Listener to remove.
+ */
+ public void removeListener(ViewChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+}
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+import com.vaadin.ui.Component;
+
+/**
+ * Interface for all views controlled by the navigator.
+ *
+ * Each view added to the navigator must implement this interface. Typically, a
+ * view is a {@link Component}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public interface View extends Serializable {
+
+ /**
+ * Init view.
+ *
+ * Convenience method which navigator calls just before the view is rendered
+ * for the first time. This is called only once in the lifetime of each view
+ * instance.
+ */
+ public void init();
+
+ /**
+ * This view is navigated to.
+ *
+ * This method is always called before the view is shown on screen. If there
+ * is any additional id to data what should be shown in the view, it is also
+ * optionally passed as parameter.
+ *
+ * TODO fragmentParameters null if no parameters or empty string?
+ *
+ * @param fragmentParameters
+ * parameters to the view or null if none given. This is the
+ * string that appears e.g. in URI after "viewname/"
+ * @param internalParameters
+ * parameters given directly to
+ * {@link Navigator#navigateTo(String, Object...)}, not passed
+ * via the fragment and not preserved in bookmarks
+ */
+ public void navigateTo(String fragmentParameters,
+ Object... internalParameters);
+}
\ No newline at end of file
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+/**
+ * Interface for listening to View changes before and after they occur.
+ *
+ * Implementations of this interface can also block navigation between views
+ * before it is performed.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public interface ViewChangeListener extends Serializable {
+
+ /**
+ * Check whether changing the view is permissible.
+ *
+ * This method may also e.g. open a "save" dialog or question about the
+ * change, which may re-initiate the navigation operation after user action.
+ *
+ * If this listener does not want to block the view change (e.g. does not
+ * know the view in question), it should return true. If any listener
+ * returns false, the view change is not allowed.
+ *
+ * TODO move to separate interface?
+ *
+ * @param previous
+ * view that is being deactivated
+ * @param next
+ * view that is being activated
+ * @param viewName
+ * name of the new view that is being activated
+ * @param fragmentParameters
+ * fragment parameters (potentially bookmarkable) for the new
+ * view
+ * @param internalParameters
+ * internal parameters for the new view, not visible in the
+ * browser
+ * @return true if the view change should be allowed or this listener does
+ * not care about the view change, false to block the change
+ */
+ public boolean isViewChangeAllowed(View previous, View next,
+ String viewName, String fragmentParameters,
+ Object... internalParameters);
+
+ /**
+ * Invoked after the view has changed. Be careful for deadlocks if you
+ * decide to change the view again in the listener.
+ *
+ * @param previous
+ * Preview view before the change.
+ * @param current
+ * New view after the change.
+ */
+ public void navigatorViewChanged(View previous, View current);
+
+}
\ No newline at end of file
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+/**
+ * Interface for displaying a view in an appropriate location.
+ *
+ * The view display can be a component/layout itself or can modify a separate
+ * layout.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public interface ViewDisplay extends Serializable {
+ /**
+ * Remove previously shown view and show the newly selected view in its
+ * place.
+ *
+ * The parameters for the view have been set before this method is called.
+ *
+ * @param view
+ * new view to show
+ */
+ public void showView(View view);
+}
\ No newline at end of file
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+/**
+ * A provider for view instances that can return pre-registered views or
+ * dynamically create new views.
+ *
+ * If multiple providers are used, {@link #getViewName(String)} of each is
+ * called (in registration order) until one of them returns a non-null value.
+ * The {@link #getView(String)} method of that provider is then used.
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+public interface ViewProvider extends Serializable {
+ /**
+ * Extract the view name from a combined view name and parameter string.
+ * This method should return a view name if and only if this provider
+ * handles creation of such views.
+ *
+ * @param viewAndParameters
+ * string with view name and its fragment parameters (if given),
+ * not null
+ * @return view name if the view is handled by this provider, null otherwise
+ */
+ public String getViewName(String viewAndParameters);
+
+ /**
+ * Create or return a pre-created instance of a view.
+ *
+ * The parameters for the view are set separately by the navigator when the
+ * view is activated.
+ *
+ * @param viewName
+ * name of the view, not null
+ * @return newly created view (null if none available for the view name)
+ */
+ public View getView(String viewName);
+}
\ No newline at end of file
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.tests.server.navigator;
+
+import junit.framework.TestCase;
+
+import com.vaadin.navigator.Navigator.ClassBasedViewProvider;
+import com.vaadin.navigator.View;
+import com.vaadin.ui.Label;
+
+public class ClassBasedViewProviderTest extends TestCase {
+
+ private ClassBasedViewProvider provider;
+
+ public static class TestView extends Label implements View {
+ public boolean initialized = false;
+ public String parameters = null;
+
+ public void init() {
+ initialized = true;
+ }
+
+ public void navigateTo(String parameters, Object... internalParameters) {
+ this.parameters = parameters;
+ }
+
+ }
+
+ public static class TestView2 extends TestView {
+
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ provider = new ClassBasedViewProvider();
+ }
+
+ public void testAddViewWithNullName() throws Exception {
+ try {
+ provider.addView(null, TestView.class);
+ fail("Should not be able to add view with null name");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testAddViewWithEmptyStringName() throws Exception {
+ try {
+ provider.addView("", TestView.class);
+ fail("Should not be able to add view with empty name");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testAddViewNull() throws Exception {
+ try {
+ provider.addView("test", null);
+ fail("Should not be able to add null view");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testAddViewSameName() throws Exception {
+ try {
+ provider.addView("test", TestView.class);
+ provider.addView("test", TestView2.class);
+ fail("Should not be able to add two views with same name");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testAddViewSameClass() throws Exception {
+ try {
+ provider.addView("test", TestView.class);
+ provider.addView("test2", TestView.class);
+ fail("Should not be able to add same view class with two different names");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testGetViewNameForNullString() throws Exception {
+ assertNull(
+ "Found view name for null view string in an empty view provider",
+ provider.getViewName((String) null));
+
+ provider.addView("test", TestView.class);
+ assertNull("Found view name for null view string",
+ provider.getViewName((String) null));
+ }
+
+ public void testGetViewNameForNullClass() throws Exception {
+ assertNull("Found view name for null class",
+ provider.getViewName((Class<View>) null));
+ }
+
+ public void testGetViewNameForEmptyString() throws Exception {
+ assertNull(
+ "Found view name for empty view string in an empty provider",
+ provider.getViewName(""));
+ provider.addView("test", TestView.class);
+ assertNull("Found view name for empty view string",
+ provider.getViewName(""));
+ }
+
+ public void testGetViewNameForClass() throws Exception {
+ provider.addView("test", TestView.class);
+ assertEquals("No view name found for view class", "test",
+ provider.getViewName(TestView.class));
+ }
+
+ public void testGetViewNameWithParameters() throws Exception {
+ provider.addView("test", TestView.class);
+ assertEquals("Incorrect view name found for view string", "test",
+ provider.getViewName("test"));
+ assertEquals(
+ "Incorrect view name found for view string ending with slash",
+ "test", provider.getViewName("test/"));
+ assertEquals(
+ "Incorrect view name found for view string with parameters",
+ "test", provider.getViewName("test/params/are/here"));
+ }
+
+ public void testGetViewNameMultipleRegisteredWithParameters()
+ throws Exception {
+ provider.addView("test", TestView.class);
+ provider.addView("test2", TestView2.class);
+ assertEquals("Incorrect view name found for view string", "test",
+ provider.getViewName("test/test2/params"));
+ }
+
+ public void testGetViewNameNestedNames() throws Exception {
+ provider.addView("test/subview", TestView2.class);
+ provider.addView("test", TestView.class);
+ assertEquals("Incorrect view name found for subview string",
+ "test/subview", provider.getViewName("test/subview"));
+ assertEquals(
+ "Incorrect view name found for subview string with empty parameters",
+ "test/subview", provider.getViewName("test/subview/"));
+ assertEquals(
+ "Incorrect view name found for subview string with parameters",
+ "test/subview", provider.getViewName("test/subview/parameters"));
+ assertEquals("Incorrect view name found for top level view string",
+ "test", provider.getViewName("test"));
+ assertEquals(
+ "Incorrect view name found for top level view string with empty parameters",
+ "test", provider.getViewName("test/"));
+ assertEquals(
+ "Incorrect view name found for top level view string with parameters starting like subview name",
+ "test", provider.getViewName("test/subviewnothere"));
+ }
+
+ public void testGetViewClass() throws Exception {
+ assertNull("View class found for empty view provider",
+ provider.getViewClass("test"));
+ provider.addView("test", TestView.class);
+ assertEquals("View class not found", TestView.class,
+ provider.getViewClass("test"));
+ assertNull("View class found for unregistered view name",
+ provider.getViewClass("test2"));
+ }
+
+ public void testGetViewSimple() throws Exception {
+ assertNull("Found view in an empty view provider",
+ provider.getViewName("test"));
+
+ provider.addView("test", TestView.class);
+ View view = provider.getView("test");
+ assertNotNull("Did not get view from a provider", view);
+ assertEquals("Incorrect view type", TestView.class, view.getClass());
+ assertTrue("View not initialized", ((TestView) view).initialized);
+ }
+
+ public void testGetViewMultipleRegistered() throws Exception {
+ provider.addView("test", TestView.class);
+ provider.addView("test2", TestView2.class);
+ assertEquals("Incorrect view type", TestView.class,
+ provider.getView("test").getClass());
+ assertEquals("Incorrect view type", TestView2.class,
+ provider.getView("test2").getClass());
+ assertEquals("Incorrect view type", TestView.class,
+ provider.getView("test").getClass());
+ }
+
+ public void testRemoveView() throws Exception {
+ provider.addView("test", TestView.class);
+ assertNotNull("Did not get view from a provider",
+ provider.getView("test"));
+ provider.removeView("test");
+ assertNull("View class found for removed view name",
+ provider.getViewClass("test"));
+ assertNull("View name found for removed view",
+ provider.getViewName(TestView.class));
+ // cached view?
+ assertNull(
+ "Received view instance from a provider after removing view type",
+ provider.getView("test"));
+ }
+
+ public void testGetViewCached() throws Exception {
+ provider.addView("test", TestView.class);
+ View view1 = provider.getView("test");
+ View view2 = provider.getView("test");
+ assertSame("View instance not cached", view1, view2);
+ }
+
+}
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.tests.server.navigator;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+
+import com.vaadin.navigator.FragmentManager;
+import com.vaadin.navigator.Navigator;
+import com.vaadin.navigator.View;
+import com.vaadin.navigator.ViewChangeListener;
+import com.vaadin.navigator.ViewDisplay;
+import com.vaadin.navigator.ViewProvider;
+
+public class NavigatorTest extends TestCase {
+
+ // TODO test internal parameters (and absence of them)
+ // TODO test listeners blocking navigation, multiple listeners
+
+ public void testBasicNavigation() {
+ IMocksControl control = EasyMock.createControl();
+ FragmentManager manager = control.createMock(FragmentManager.class);
+ ViewDisplay display = control.createMock(ViewDisplay.class);
+ ViewProvider provider = control.createMock(ViewProvider.class);
+ View view1 = control.createMock(View.class);
+ View view2 = control.createMock(View.class);
+
+ // prepare mocks: what to expect
+ EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ view1.navigateTo(null);
+ display.showView(view1);
+ manager.setFragment("test1");
+
+ EasyMock.expect(provider.getViewName("test2/")).andReturn("test2");
+ EasyMock.expect(provider.getView("test2")).andReturn(view2);
+ view2.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view1");
+ view2.navigateTo(null);
+ display.showView(view2);
+ manager.setFragment("test2");
+
+ EasyMock.expect(provider.getViewName("test1/params"))
+ .andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view2");
+ view1.navigateTo("params");
+ display.showView(view1);
+ manager.setFragment("test1/params");
+
+ control.replay();
+
+ // create and test navigator
+ Navigator navigator = new Navigator(manager, display);
+ navigator.registerProvider(provider);
+
+ navigator.navigateTo("test1");
+ navigator.navigateTo("test2/");
+ navigator.navigateTo("test1/params");
+ }
+
+ public void testMainView() {
+ IMocksControl control = EasyMock.createControl();
+ FragmentManager manager = control.createMock(FragmentManager.class);
+ ViewDisplay display = control.createMock(ViewDisplay.class);
+ ViewProvider provider = control.createMock(ViewProvider.class);
+ View view1 = control.createMock(View.class);
+ View view2 = control.createMock(View.class);
+
+ // prepare mocks: what to expect
+ EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ view1.navigateTo(null);
+ display.showView(view1);
+ manager.setFragment("test1");
+
+ EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
+ EasyMock.expect(provider.getView("test2")).andReturn(view2);
+ view2.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view1");
+ view2.navigateTo(null);
+ display.showView(view2);
+ manager.setFragment("test2");
+
+ EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ view1.navigateTo(null);
+ display.showView(view1);
+ manager.setFragment("test1");
+
+ EasyMock.expect(provider.getViewName("test1/params"))
+ .andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view2");
+ view1.navigateTo("params");
+ display.showView(view1);
+ manager.setFragment("test1/params");
+
+ control.replay();
+
+ // create and test navigator
+ Navigator navigator = new Navigator(manager, display);
+ navigator.registerProvider(provider);
+ // this also triggers navigation
+ navigator.setMainView("test1");
+
+ navigator.navigateTo("test2");
+ navigator.navigateTo("");
+ navigator.navigateTo("test1/params");
+ }
+
+ public void testListeners() {
+ IMocksControl control = EasyMock.createControl();
+ FragmentManager manager = control.createMock(FragmentManager.class);
+ ViewDisplay display = control.createMock(ViewDisplay.class);
+ ViewProvider provider = control.createMock(ViewProvider.class);
+ View view1 = control.createMock(View.class);
+ View view2 = control.createMock(View.class);
+ ViewChangeListener listener = control
+ .createMock(ViewChangeListener.class);
+
+ // prepare mocks: what to expect
+ EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ EasyMock.expect(
+ listener.isViewChangeAllowed(null, view1, "test1", null,
+ new Object[0])).andReturn(true);
+ view1.navigateTo(null);
+ display.showView(view1);
+ manager.setFragment("test1");
+ listener.navigatorViewChanged(null, view1);
+
+ EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
+ EasyMock.expect(provider.getView("test2")).andReturn(view2);
+ view2.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view1");
+ EasyMock.expect(
+ listener.isViewChangeAllowed(view1, view2, "test2", null,
+ new Object[0])).andReturn(true);
+ view2.navigateTo(null);
+ display.showView(view2);
+ manager.setFragment("test2");
+ listener.navigatorViewChanged(view1, view2);
+
+ control.replay();
+
+ // create and test navigator
+ Navigator navigator = new Navigator(manager, display);
+ navigator.registerProvider(provider);
+ navigator.addListener(listener);
+
+ navigator.navigateTo("test1");
+ navigator.navigateTo("test2");
+ }
+
+ public void testBlockNavigation() {
+ IMocksControl control = EasyMock.createControl();
+ FragmentManager manager = control.createMock(FragmentManager.class);
+ ViewDisplay display = control.createMock(ViewDisplay.class);
+ ViewProvider provider = control.createMock(ViewProvider.class);
+ View view1 = control.createMock(View.class);
+ View view2 = control.createMock(View.class);
+ ViewChangeListener listener1 = control
+ .createMock(ViewChangeListener.class);
+ ViewChangeListener listener2 = control
+ .createMock(ViewChangeListener.class);
+
+ // prepare mocks: what to expect
+ // first listener blocks first view change
+ EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ EasyMock.expect(
+ listener1.isViewChangeAllowed(null, view1, "test1", null,
+ new Object[0])).andReturn(false);
+
+ // second listener blocks second view change
+ EasyMock.expect(provider.getViewName("test1/test")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ EasyMock.expect(
+ listener1.isViewChangeAllowed(null, view1, "test1", "test",
+ new Object[0])).andReturn(true);
+ EasyMock.expect(
+ listener2.isViewChangeAllowed(null, view1, "test1", "test",
+ new Object[0])).andReturn(false);
+
+ // both listeners allow view change
+ EasyMock.expect(provider.getViewName("test1/bar")).andReturn("test1");
+ EasyMock.expect(provider.getView("test1")).andReturn(view1);
+ view1.init();
+ EasyMock.expect(manager.getFragment()).andReturn("");
+ EasyMock.expect(
+ listener1.isViewChangeAllowed(null, view1, "test1", "bar",
+ new Object[0])).andReturn(true);
+ EasyMock.expect(
+ listener2.isViewChangeAllowed(null, view1, "test1", "bar",
+ new Object[0])).andReturn(true);
+ view1.navigateTo("bar");
+ display.showView(view1);
+ manager.setFragment("test1/bar");
+ listener1.navigatorViewChanged(null, view1);
+ listener2.navigatorViewChanged(null, view1);
+
+ // both listeners allow view change from non-null view
+ EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
+ EasyMock.expect(provider.getView("test2")).andReturn(view2);
+ view2.init();
+ EasyMock.expect(manager.getFragment()).andReturn("view1");
+ EasyMock.expect(
+ listener1.isViewChangeAllowed(view1, view2, "test2", null,
+ new Object[0])).andReturn(true);
+ EasyMock.expect(
+ listener2.isViewChangeAllowed(view1, view2, "test2", null,
+ new Object[0])).andReturn(true);
+ view2.navigateTo(null);
+ display.showView(view2);
+ manager.setFragment("test2");
+ listener1.navigatorViewChanged(view1, view2);
+ listener2.navigatorViewChanged(view1, view2);
+
+ control.replay();
+
+ // create and test navigator
+ Navigator navigator = new Navigator(manager, display);
+ navigator.registerProvider(provider);
+ navigator.addListener(listener1);
+ navigator.addListener(listener2);
+
+ navigator.navigateTo("test1");
+ navigator.navigateTo("test1/test");
+ navigator.navigateTo("test1/bar");
+ navigator.navigateTo("test2");
+ }
+
+}
--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.tests.server.navigator;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+
+import com.vaadin.navigator.Navigator;
+import com.vaadin.navigator.Navigator.UriFragmentManager;
+import com.vaadin.ui.Root;
+import com.vaadin.ui.Root.FragmentChangedEvent;
+
+public class UriFragmentManagerTest extends TestCase {
+
+ public void testGetSetFragment() {
+ Root root = EasyMock.createMock(Root.class);
+ UriFragmentManager manager = new UriFragmentManager(root, null);
+
+ // prepare mock
+ EasyMock.expect(root.getFragment()).andReturn("");
+ root.setFragment("test");
+ EasyMock.expect(root.getFragment()).andReturn("test");
+ EasyMock.replay(root);
+
+ // test manager using the mock
+ assertEquals("Incorrect fragment value", "", manager.getFragment());
+ manager.setFragment("test");
+ assertEquals("Incorrect fragment value", "test", manager.getFragment());
+ }
+
+ public void testListener() {
+ // create mocks
+ IMocksControl control = EasyMock.createControl();
+ Navigator navigator = control.createMock(Navigator.class);
+ Root root = control.createMock(Root.class);
+
+ UriFragmentManager manager = new UriFragmentManager(root, navigator);
+
+ EasyMock.expect(root.getFragment()).andReturn("test");
+ navigator.navigateTo("test");
+ control.replay();
+
+ FragmentChangedEvent event = root.new FragmentChangedEvent(root,
+ "oldtest");
+ manager.fragmentChanged(event);
+ }
+}