components/visual-panels/core/src/java/vpanels/panel/com/oracle/solaris/vp/panel/common/control/Control.java
changeset 3553 f1d133b09a8c
parent 3552 077ebe3d0d24
child 3554 ef58713bafc4
equal deleted inserted replaced
3552:077ebe3d0d24 3553:f1d133b09a8c
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.panel.common.control;
       
    27 
       
    28 import java.io.UnsupportedEncodingException;
       
    29 import java.net.*;
       
    30 import java.util.*;
       
    31 import java.util.Map;
       
    32 import java.util.logging.*;
       
    33 import javax.help.*;
       
    34 import com.oracle.solaris.vp.panel.common.action.*;
       
    35 
       
    36 /**
       
    37  * The {@code Control} class encapsulates the control over all aspects of a
       
    38  * single point in a navigation hierarchy.
       
    39  */
       
    40 public abstract class Control implements Navigable, HasControl {
       
    41     //
       
    42     // Enums
       
    43     //
       
    44 
       
    45     public enum UnsavedChangesAction {
       
    46 	CANCEL, DISCARD, SAVE
       
    47     }
       
    48 
       
    49     //
       
    50     // Static data
       
    51     //
       
    52 
       
    53     /**
       
    54      * The encoding used to encode/decode Control IDs and parameters in a path
       
    55      * string.
       
    56      */
       
    57     public static final String ENCODING = "UTF-8";
       
    58 
       
    59     //
       
    60     // Instance data
       
    61     //
       
    62 
       
    63     private String id;
       
    64     private String name;
       
    65     private Navigator navigator;
       
    66     private Map<String, String> parameters;
       
    67     private Control child;
       
    68 
       
    69     private StructuredAction<?, ?, ?> resetAction =
       
    70 	new StructuredAction<Object, Object, Object>(null) {
       
    71 	    @Override
       
    72 	    public Object work(Object pInput, Object rtInput)
       
    73 		throws ActionAbortedException, ActionFailedException {
       
    74 
       
    75 		resetAll();
       
    76 		return null;
       
    77 	    }
       
    78 	};
       
    79 
       
    80     private StructuredAction<?, ?, ?> saveAction =
       
    81 	new StructuredAction<Object, Object, Object>(null) {
       
    82 	    @Override
       
    83 	    public Object work(Object pInput, Object rtInput)
       
    84 		throws ActionAbortedException, ActionFailedException {
       
    85 
       
    86 		try {
       
    87 		    saveAll();
       
    88 		} catch (ActionUnauthorizedException e) {
       
    89 		    throw new ActionFailedException(e);
       
    90 		}
       
    91 		return null;
       
    92 	    }
       
    93 	};
       
    94 
       
    95     //
       
    96     // Constructors
       
    97     //
       
    98 
       
    99     /**
       
   100      * Constructs a {@code Control} with the given identifier and name.
       
   101      */
       
   102     public Control(String id, String name) {
       
   103 	setId(id);
       
   104 	setName(name);
       
   105     }
       
   106 
       
   107     /**
       
   108      * Constructs a {@code Control} with a {@code null} identifier and name.
       
   109      */
       
   110     public Control() {
       
   111 	this(null, null);
       
   112     }
       
   113 
       
   114     //
       
   115     // HasId methods
       
   116     //
       
   117 
       
   118     /**
       
   119      * Gets an identifier for this {@code Control}, sufficiently unique as to
       
   120      * distinguish itself from its siblings.
       
   121      */
       
   122     @Override
       
   123     public String getId() {
       
   124 	return id;
       
   125     }
       
   126 
       
   127     //
       
   128     // Navigable methods
       
   129     //
       
   130 
       
   131     /**
       
   132      * Gets the localized name of this {@code Control}.
       
   133      */
       
   134     @Override
       
   135     public String getName() {
       
   136 	return name;
       
   137     }
       
   138 
       
   139     /**
       
   140      * Gets the initialization parameters passed to the {@link #start} method,
       
   141      * if this {@code Control} is started, or {@code null} if this {@code
       
   142      * Control} is stopped.
       
   143      */
       
   144     @Override
       
   145     public Map<String, String> getParameters() {
       
   146 	return parameters;
       
   147     }
       
   148 
       
   149     //
       
   150     // HasControl methods
       
   151     //
       
   152 
       
   153     @Override
       
   154     public Control getControl() {
       
   155 	return this;
       
   156     }
       
   157 
       
   158     //
       
   159     // Object methods
       
   160     //
       
   161 
       
   162     @Override
       
   163     public String toString() {
       
   164 	return getName();
       
   165     }
       
   166 
       
   167     //
       
   168     // Control methods
       
   169     //
       
   170 
       
   171     /**
       
   172      * Saves the given child as the {@link #getRunningChild running child}.
       
   173      * Called by {@link #descendantStarted} when a child of this {@code Control}
       
   174      * is started.
       
   175      *
       
   176      * @exception   IllegalStateException
       
   177      *		    if the running child has already been set
       
   178      */
       
   179     public void childStarted(Control child) {
       
   180 	if (this.child != null) {
       
   181 	    throw new IllegalStateException("child already started");
       
   182 	}
       
   183 
       
   184 	this.child = child;
       
   185     }
       
   186 
       
   187     /**
       
   188      * Removes the given child as the {@link #getRunningChild running child}.
       
   189      * Called by {@link #descendantStopped} when a child of this {@code Control}
       
   190      * is stopped.
       
   191      *
       
   192      * @exception   IllegalStateException
       
   193      *		    if the given control is not the running child
       
   194      */
       
   195     public void childStopped(Control child) {
       
   196 	if (this.child != child) {
       
   197 	    throw new IllegalStateException("not running child");
       
   198 	}
       
   199 
       
   200 	this.child = null;
       
   201     }
       
   202 
       
   203     /**
       
   204      * Calls {@link #childStarted} iff the given path refers to an immediate
       
   205      * child of this {@code Control}.
       
   206      * <p/>
       
   207      * Called by the {@link Navigator} just after a descendant {@code Control}
       
   208      * of this {@code Control} has been started and pushed onto the {@link
       
   209      * Navigator}'s {@code Control} stack.
       
   210      *
       
   211      * @param	    path
       
   212      *		    the path to the descendant {@code Control}, relative to this
       
   213      *		    {@code Control} (with the just-started {@code Control} as
       
   214      *		    the last element)
       
   215      */
       
   216     public void descendantStarted(Control[] path) {
       
   217 	if (path.length == 1) {
       
   218 	    childStarted(path[0]);
       
   219 	}
       
   220     }
       
   221 
       
   222     /**
       
   223      * Calls {@link #childStopped} iff the given path refers to an immediate
       
   224      * child of this {@code Control}.
       
   225      * <p/>
       
   226      * Called by the {@link Navigator} just after a descendant {@code Control}
       
   227      * of this {@code Control} has been stopped and popped off the {@link
       
   228      * Navigator}'s {@code Control} stack.
       
   229      *
       
   230      * @param	    path
       
   231      *		    the path to the descendant {@code Control}, relative to this
       
   232      *		    {@code Control} (with the just-stopped {@code Control} as
       
   233      *		    the last element)
       
   234      */
       
   235     public void descendantStopped(Control[] path) {
       
   236 	if (path.length == 1) {
       
   237 	    childStopped(path[0]);
       
   238 	}
       
   239     }
       
   240 
       
   241     /**
       
   242      * Asynchronously navigates up one level above this {@code Control} in the
       
   243      * navigation stack.  The {@link #stop stop methods} of all affected {@code
       
   244      * Control}s are called with a {@code true} argument.
       
   245      */
       
   246     public void doCancel() {
       
   247 	getNavigator().goToAsync(true, this, Navigator.PARENT_NAVIGABLE);
       
   248     }
       
   249 
       
   250     /**
       
   251      * Asynchronously invokes this {@link Control}'s save action, then navigates
       
   252      * up one level in the navigation stack.
       
   253      */
       
   254     public void doOkay() {
       
   255 	final StructuredAction<?, ?, ?> saveAction = getSaveAction();
       
   256 	saveAction.asyncExec(
       
   257 	    new Runnable() {
       
   258 		@Override
       
   259 		public void run() {
       
   260 		    try {
       
   261 			saveAction.invoke();
       
   262 
       
   263 			getNavigator().goToAsync(false, Control.this,
       
   264 			    Navigator.PARENT_NAVIGABLE);
       
   265 		    } catch (ActionException ignore) {
       
   266 		    }
       
   267 		}
       
   268 	    });
       
   269     }
       
   270 
       
   271     /**
       
   272      * Gets a list of {@code Navigable}s that resolve to a child {@code Control}
       
   273      * of this {@code Control}.
       
   274      *
       
   275      * @return	    a non-{@code null} (but possibly empty) {@code Collection}
       
   276      */
       
   277     public abstract List<Navigable> getBrowsable();
       
   278 
       
   279     /**
       
   280      * Gets the child {@code Control} with the given identifier, creating it if
       
   281      * necessary.
       
   282      *
       
   283      * @param	    id
       
   284      *		    a unique identifier, as reported by the child {@code
       
   285      *		    Control}'s {@link #getId} method.
       
   286      *
       
   287      * @return	    a {@code Control} object, or {@code null} if no such child
       
   288      *		    is known
       
   289      */
       
   290     public abstract Control getChildControl(String id);
       
   291 
       
   292     /**
       
   293      * Gets a {@link Navigable} path to navigate to automatically when this
       
   294      * {@code Control} is the final destination of a navigation (not an
       
   295      * intermediate stop to another {@code Control}).  This method is called by
       
   296      * the {@link Navigator} <strong>after</strong> this {@code Control} has
       
   297      * been started.
       
   298      * <p/>
       
   299      * If the first element is {@code null}, the returned path is considered
       
   300      * absolute.  Otherwise, it is relative to this {@code Control}.
       
   301      * <p/>
       
   302      * This default implementation returns {@code null}.
       
   303      *
       
   304      * @param	    childStopped
       
   305      *		    {@code true} if navigation stopped here because a child
       
   306      *		    {@code Control} of this {@code Control} stopped, {@code
       
   307      *		    false} if this {@code Control} was started as part of this
       
   308      *		    specific navigation
       
   309      *
       
   310      * @return	    a {@link Navigable} array, or {@code null} if no automatic
       
   311      *		    forwarding should occur
       
   312      */
       
   313     public Navigable[] getForwardingPath(boolean childStopped) {
       
   314 	return null;
       
   315     }
       
   316 
       
   317     /**
       
   318      * Used by {@link #getHelpURL}, returns a {@code HelpSet} map identifier
       
   319      * that corresponds to a help URL for this {@code Control}.
       
   320      * <p/>
       
   321      * This default implementation returns {@code null}.
       
   322      *
       
   323      * @return	    an ID, or {@code null} if no specific topic in the {@code
       
   324      *		    HelpSet} applies to this {@code Control}
       
   325      */
       
   326     public String getHelpMapID() {
       
   327 	return null;
       
   328     }
       
   329 
       
   330     /**
       
   331      * Gets the help URL for this {@code Control}.
       
   332      * <p/>
       
   333      * This default implementation attempts to retrieve a URL corresponding to
       
   334      * the {@link #getHelpMapID help ID}, if any, in the value map of the given
       
   335      * localized {@code HelpSet}.
       
   336      *
       
   337      * @return	    a URL, or {@code null} if no URL applies
       
   338      *
       
   339      * @param	    helpSet
       
   340      *		    a localized {@code HelpSet}
       
   341      *
       
   342      * @return	    a URL, or {@code null} if no URL is appropriate for this
       
   343      *        	    {@code Control}
       
   344      */
       
   345     public URL getHelpURL(HelpSet helpSet) {
       
   346 	String id = getHelpMapID();
       
   347 	if (id != null) {
       
   348 	    // JavaHelp provides no hash into Map values, sigh...
       
   349 	    @SuppressWarnings({"unchecked"})
       
   350 	    Enumeration<javax.help.Map.ID> ids =
       
   351 		helpSet.getCombinedMap().getAllIDs();
       
   352 
       
   353 	    for (; ids.hasMoreElements();) {
       
   354 		javax.help.Map.ID mid = ids.nextElement();
       
   355 		if (mid.getIDString().equals(id)) {
       
   356 		    try {
       
   357 			return mid.getURL();
       
   358 		    } catch (MalformedURLException ignore) {
       
   359 			getLog().log(Level.SEVERE, String.format(
       
   360 			    "id \"%s\" invalid for help set: %s", id,
       
   361 			    helpSet.getHelpSetURL()));
       
   362 		    }
       
   363 		    break;
       
   364 		}
       
   365 	    }
       
   366 	}
       
   367 
       
   368 	return null;
       
   369     }
       
   370 
       
   371     /**
       
   372      * Gets a {@code Logger} for this class.  The {@code Logger}'s name is
       
   373      * derived from the class package name.
       
   374      */
       
   375     protected Logger getLog() {
       
   376 	return Logger.getLogger(getClass().getPackage().getName());
       
   377     }
       
   378 
       
   379     /**
       
   380      * Gets the {@link Navigator} passed to the {@link #start} method, if
       
   381      * this {@code Control} is started, or {@code null} if this {@code Control}
       
   382      * is stopped.
       
   383      */
       
   384     public Navigator getNavigator() {
       
   385 	return navigator;
       
   386     }
       
   387 
       
   388     /**
       
   389      * Gets a {@link StructuredAction} that invokes {@link #resetAll}.
       
   390      */
       
   391     public StructuredAction<?, ?, ?> getResetAction() {
       
   392 	return resetAction;
       
   393     }
       
   394 
       
   395     /**
       
   396      * Gets the child {@code Control} currently running, or {@code null} if
       
   397      * there is none.
       
   398      */
       
   399     public Control getRunningChild() {
       
   400 	return child;
       
   401     }
       
   402 
       
   403     /**
       
   404      * Gets a {@link StructuredAction} that invokes {@link #saveAll}.
       
   405      */
       
   406     public StructuredAction<?, ?, ?> getSaveAction() {
       
   407 	return saveAction;
       
   408     }
       
   409 
       
   410     /**
       
   411      * Called by {@link #stop} when there are unsaved changes, gets the action
       
   412      * that should be taken to handle them.
       
   413      * <p/>
       
   414      * This default implementation returns {@link UnsavedChangesAction#DISCARD}.
       
   415      * Subclasses may wish to prompt the user to determine the appropriate
       
   416      * action to take.
       
   417      */
       
   418     protected UnsavedChangesAction getUnsavedChangesAction() {
       
   419 	return UnsavedChangesAction.DISCARD;
       
   420     }
       
   421 
       
   422     /**
       
   423      * Gets a hint as to whether this {@code Control} should be returned by a
       
   424      * parent {@code Control}'s {@link #getBrowsable} method.  The parent may
       
   425      * choose to ignore this hint.
       
   426      * <p/>
       
   427      * This default implementation returns {@code true}.
       
   428      */
       
   429     public boolean isBrowsable() {
       
   430 	return true;
       
   431     }
       
   432 
       
   433     /**
       
   434      * Indicates whether there are any unsaved changes in this {@code Control}.
       
   435      * <p/>
       
   436      * This default implementation returns {@code false}.
       
   437      */
       
   438     protected boolean isChanged() {
       
   439 	return false;
       
   440     }
       
   441 
       
   442     public boolean isStarted() {
       
   443 	return navigator != null;
       
   444     }
       
   445 
       
   446     /**
       
   447      * If appropriate, resets this {@code Control}, discarding any pending
       
   448      * changes.
       
   449      * <p/>
       
   450      * This default implementation does nothing.
       
   451      *
       
   452      * @exception   ActionAbortedException
       
   453      *		    if this operation is cancelled
       
   454      *
       
   455      * @exception   ActionFailedException
       
   456      *		    if this operation fails
       
   457      */
       
   458     protected void reset() throws ActionAbortedException, ActionFailedException
       
   459     {
       
   460     }
       
   461 
       
   462     /**
       
   463      * {@link #reset Reset}s all {@code Control}s from the top of the navigation
       
   464      * stack to this {@link Control}, discarding any pending changes.
       
   465      *
       
   466      * @exception   ActionAbortedException
       
   467      *		    see {@link #reset}
       
   468      *
       
   469      * @exception   ActionFailedException
       
   470      *		    see {@link #reset}
       
   471      *
       
   472      * @exception   IllegalStateException
       
   473      *		    if this {@link Control} is not started
       
   474      */
       
   475     protected void resetAll() throws ActionAbortedException,
       
   476 	ActionFailedException {
       
   477 
       
   478 	assertStartState(true);
       
   479 
       
   480 	List<Control> path = navigator.getPath();
       
   481 	if (!path.contains(this)) {
       
   482 	    throw new IllegalStateException();
       
   483 	}
       
   484 
       
   485 	for (int i = path.size() - 1; i >= 0; i--) {
       
   486 	    Control control = path.get(i);
       
   487 	    control.reset();
       
   488 	    if (control == this) {
       
   489 		break;
       
   490 	    }
       
   491 	}
       
   492     }
       
   493 
       
   494     /**
       
   495      * If appropriate, saves any changes made while this {@code Control} is
       
   496      * running.
       
   497      * <p/>
       
   498      * This default implementation does nothing.
       
   499      *
       
   500      * @exception   ActionAbortedException
       
   501      *		    if this operation is cancelled
       
   502      *
       
   503      * @exception   ActionFailedException
       
   504      *		    if this operation fails
       
   505      *
       
   506      * @exception   ActionUnauthorizedException
       
   507      *		    if the current user has insufficient privileges for this
       
   508      *		    operation
       
   509      */
       
   510     protected void save() throws ActionAbortedException, ActionFailedException,
       
   511 	ActionUnauthorizedException {
       
   512     }
       
   513 
       
   514     /**
       
   515      * {@link #save Save}s all {@code Control}s from the top of the navigation
       
   516      * stack to this {@link Control}.
       
   517      *
       
   518      * @exception   ActionAbortedException
       
   519      *		    see {@link #save}
       
   520      *
       
   521      * @exception   ActionFailedException
       
   522      *		    see {@link #save}
       
   523      *
       
   524      * @exception   ActionUnauthorizedException
       
   525      *		    see {@link #save}
       
   526      *
       
   527      * @exception   IllegalStateException
       
   528      *		    if this {@link Control} is not started
       
   529      */
       
   530     protected void saveAll() throws ActionAbortedException,
       
   531 	ActionFailedException, ActionUnauthorizedException {
       
   532 
       
   533 	assertStartState(true);
       
   534 
       
   535 	List<Control> path = navigator.getPath();
       
   536 	if (!path.contains(this)) {
       
   537 	    throw new IllegalStateException();
       
   538 	}
       
   539 
       
   540 	for (int i = path.size() - 1; i >= 0; i--) {
       
   541 	    Control control = path.get(i);
       
   542 	    if (control.isChanged()) {
       
   543 		control.save();
       
   544 	    }
       
   545 	    if (control == this) {
       
   546 		break;
       
   547 	    }
       
   548 	}
       
   549     }
       
   550 
       
   551     /**
       
   552      * Sets the identifier for this {@code Control}.
       
   553      */
       
   554     protected void setId(String id) {
       
   555 	this.id = id;
       
   556     }
       
   557 
       
   558     /**
       
   559      * Sets the name for this {@code Control}.
       
   560      */
       
   561     protected void setName(String name) {
       
   562 	this.name = name;
       
   563     }
       
   564 
       
   565     /**
       
   566      * Saves references to the given {@link #getNavigator Navigator} and {@link
       
   567      * #getParameters initialization parameters}.
       
   568      * <p/>
       
   569      * Called by the {@link Navigator} when this {@code Control} is pushed onto
       
   570      * the {@code Control} stack.
       
   571      *
       
   572      * @param	    navigator
       
   573      *		    the {@link Navigator} that handles navigation to/from this
       
   574      *		    {@code Controls}
       
   575      *
       
   576      * @param	    parameters
       
   577      *		    non-{@code null}, but optional (may be empty) initialization
       
   578      *		    parameters
       
   579      *
       
   580      * @exception   NavigationAbortedException
       
   581      *		    if this action is cancelled or vetoed
       
   582      *
       
   583      * @exception   InvalidParameterException
       
   584      *		    if this action fails due to invalid initialization
       
   585      *		    parameters
       
   586      *
       
   587      * @exception   NavigationFailedException
       
   588      *		    if this action fails for some other reason
       
   589      *
       
   590      * @exception   IllegalStateException
       
   591      *		    if this {@link Control} is already started
       
   592      */
       
   593     public void start(Navigator navigator, Map<String, String> parameters)
       
   594 	throws NavigationAbortedException, InvalidParameterException,
       
   595 	NavigationFailedException {
       
   596 
       
   597 	assertStartState(false);
       
   598 	this.navigator = navigator;
       
   599 	this.parameters = parameters;
       
   600     }
       
   601 
       
   602     /**
       
   603      * If {@code isCancel} is {@code false}, saves, resets, or cancels changes
       
   604      * {@link #isChanged if necessary}, based on the return value of {@link
       
   605      * #getUnsavedChangesAction}.  Then resets the references to the {@link
       
   606      * #getNavigator Navigator} and {@link #getParameters initialization
       
   607      * parameters}.
       
   608      * <p/>
       
   609      * Called by the {@link Navigator} prior to this {@code Control} being
       
   610      * removed as the current {@code Control}.
       
   611      *
       
   612      * @param	    isCancel
       
   613      *		    {@code true} if this {@code Control} is being stopped as
       
   614      *		    part of a {@code cancel} operation, {@code false} otherwise
       
   615      *
       
   616      * @exception   NavigationAbortedException
       
   617      *		    if this {@code Control} should remain the current {@code
       
   618      *		    Control}
       
   619      *
       
   620      * @exception   IllegalStateException
       
   621      *		    if this {@link Control} is not started
       
   622      */
       
   623     public void stop(boolean isCancel) throws NavigationAbortedException {
       
   624 	assertStartState(true);
       
   625 
       
   626 	if (!isCancel && isChanged()) {
       
   627 	    try {
       
   628 		switch (getUnsavedChangesAction()) {
       
   629 		    case SAVE:
       
   630 			getSaveAction().invoke();
       
   631 			break;
       
   632 
       
   633 		    case DISCARD:
       
   634 			getResetAction().invoke();
       
   635 			break;
       
   636 
       
   637 		    default:
       
   638 		    case CANCEL:
       
   639 			throw new NavigationAbortedException();
       
   640 		}
       
   641 
       
   642 	    // Thrown by invoke()
       
   643 	    } catch (ActionException e) {
       
   644 		throw new NavigationAbortedException(e);
       
   645 	    }
       
   646 	}
       
   647 
       
   648 	this.navigator = null;
       
   649 	this.parameters = null;
       
   650     }
       
   651 
       
   652     //
       
   653     // Private methods
       
   654     //
       
   655 
       
   656     private void assertStartState(boolean started) {
       
   657 	if (isStarted() != started) {
       
   658 	    throw new IllegalStateException(started ? "control started" :
       
   659 		"control not started");
       
   660 	}
       
   661     }
       
   662 
       
   663     //
       
   664     // Static methods
       
   665     //
       
   666 
       
   667     /**
       
   668      * Decodes the given encoded {@code String} into an identifier and
       
   669      * parameters, encapsulated by a {@link SimpleNavigable}.
       
   670      *
       
   671      * @param	    encoded
       
   672      *		    an {@link #encode encode}d {@code String}
       
   673      *
       
   674      * @return	    a {@link SimpleNavigable}
       
   675      */
       
   676     public static SimpleNavigable decode(String encoded) {
       
   677 	String[] elements = encoded.split("\\?", 2);
       
   678 	String id = elements[0];
       
   679 	Map<String, String> parameters = new HashMap<String, String>();
       
   680 
       
   681 	try {
       
   682 	    id = URLDecoder.decode(elements[0], ENCODING);
       
   683 	} catch (UnsupportedEncodingException ignore) {
       
   684 	}
       
   685 
       
   686 	if (elements.length >= 2 && !elements[1].isEmpty()) {
       
   687 	    String[] keyEqVals = elements[1].split("&");
       
   688 
       
   689 	    for (String keyEqVal : keyEqVals) {
       
   690 		String[] nvPair = keyEqVal.split("=", 2);
       
   691 		String key = nvPair[0];
       
   692 		String value = nvPair.length < 2 ? "" : nvPair[1];
       
   693 
       
   694 		try {
       
   695 		    key = URLDecoder.decode(key, ENCODING);
       
   696 		    value = URLDecoder.decode(value, ENCODING);
       
   697 		} catch (UnsupportedEncodingException ignore) {
       
   698 		}
       
   699 
       
   700 		parameters.put(key, value);
       
   701 	    }
       
   702 	}
       
   703 
       
   704 	return new SimpleNavigable(id, null, parameters);
       
   705     }
       
   706 
       
   707     /**
       
   708      * Encodes the given identifier and parameters.
       
   709      *
       
   710      * @param	    id
       
   711      *		    a {@link Control#getId Control identifier}
       
   712      *
       
   713      * @param	    parameters
       
   714      *		    initialization parameters, or {@code null} if no parameters
       
   715      *		    apply
       
   716      *
       
   717      * @return	    an encoded String
       
   718      */
       
   719     public static String encode(String id, Map<String, String> parameters) {
       
   720 	StringBuilder buffer = new StringBuilder();
       
   721 
       
   722 	try {
       
   723 	    buffer.append(URLEncoder.encode(id, ENCODING));
       
   724 
       
   725 	    if (parameters != null && !parameters.isEmpty()) {
       
   726 		buffer.append("?");
       
   727 		boolean first = true;
       
   728 
       
   729 		for (String key : parameters.keySet()) {
       
   730 		    if (first) {
       
   731 			first = false;
       
   732 		    } else {
       
   733 			buffer.append("&");
       
   734 		    }
       
   735 
       
   736 		    String value = parameters.get(key);
       
   737 		    if (value == null) {
       
   738 			value = "";
       
   739 		    } else {
       
   740 			value = URLEncoder.encode(value, ENCODING);
       
   741 		    }
       
   742 
       
   743 		    key = URLEncoder.encode(key, ENCODING);
       
   744 
       
   745 		    buffer.append(key).append("=").append(value);
       
   746 		}
       
   747 	    }
       
   748 	} catch (UnsupportedEncodingException ignore) {
       
   749 	}
       
   750 
       
   751 	return buffer.toString();
       
   752     }
       
   753 }