/*
 * 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.myfaces.orchestra.viewController;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * Map view-ids to bean names, using a dirSubdirPage style format.
 * <p>
 * The strategy of this mapper is as follows:
 * <ul>
 * <li>The first character after every slash (except the first one) is converted to uppercase;</li>
 * <li>All slashes ('/') are removed;</li>
 * <li>All characters following the last dot ('.') are removed;</li>
 * <li>Reserved words {@link #RESERVED_WORDS} are prefixed with an underscore ('_');</li>
 * <li>Resulting bean-names starting with a digit are also prefixed with an underscore.</li>
 * </ul>
 * <p>
 * Examples:
 * <ul>
 * <li>"/userInfo.jsp" becomes "userInfo"
 * <li>"/SecureArea/userPassword.xhtml" becomes "secureAreaUserPassword"
 * </ul>
 * <p>
 * Using a bean naming scheme provides the following benefits:
 * <ul>
 * <li>The backing bean code does not need to be aware of the view path;
 * <li>The backing bean class can be in any package;
 * <li>Moving the view does not require alteration to the java code;
 * <li>There is no separate "view mapping" configuration file required with
 * its own unique format, just normal managed-bean configuration.
 * <li>It is easy to see from the managed-bean declarations which view a bean is
 * associated with. This information can be extremely useful when developing
 * a large application, so a bean naming convention of this type is useful
 * even when not being used for the purposes of view lifecycle events.
 * </ul> 
 * <p>
 * In particular, the separation between "UI designer" and "coder" remains; the
 * UI designer can move and rearrange views without touching java code. They do
 * need to change the bean mapping files, but that is not so significant.
 * <p>
 * The following limitations apply to this approach:
 * <ul>
 * <li>Only one bean can be mapped to a view. However this can be worked around
 * by providing a "relay" bean that delegates calls to all of the beans that 
 * have been injected into it.
 * <li>When a view is moved, lifecycle events will silently stop occurring if
 * the bean-name is not been updated.
 * <li>When a view is moved, the bean-name has to change. If the bean handling
 * viewcontroller events is also referenced by other expressions in the page,
 * then all those expressions must also be changed. Dependency-injection
 * configuration that uses that bean-name must also be updated. However see
 * below for information on "bean aliasing".
 * <li>When a view is deeply nested within a directory tree, the bean name
 * generated from the view name can become long and inconvenient to use within
 * expressions. This is only an issue if the bean handling lifecycle events
 * is also the target of expressions in the page, but that is often the case.
 * </ul>
 * <p>
 * Some dependency-injection frameworks allow bean-name "aliases" to be defined,
 * ie for a single managed-bean to have multiple names. This can be used to define
 * one name that expressions reference the bean through, and a separate name
 * that is used only in order to link that bean with the corresponding view. With
 * this configuration, moving a view simply requires changing the name of the
 * alias. If appropriate these aliases can be defined in a different configuration
 * file from the "real" bean definitions (eg for the use of UI Designers). This
 * approach does increase the number of managed-bean declarations required, so
 * should only be applied where useful.
 * <p>
 * It is possible to define a very simple request-scoped bean for viewcontroller
 * event handling that just delegates to another bean that is injected into it.
 * This has the same effect as "bean aliasing" although it is implementable without
 * "aliasing" support (and particularly, in plain JSF 1.1). This simple bean can be
 * named after the view it controls, while the "real" backing bean can be named
 * however it wishes. The same benefits and drawbacks apply as for the "aliases"
 * approach described above.
 * <p>
 * It may be possible to also define aliases within the page definitions. In
 * particular, for JSF the Apache Myfaces Tomahawk AliasBean can be used to define
 * a local alias for a bean. If this is done at the top of a file, then when the
 * view is moved, only that alias entry in the page needs to be altered rather
 * than all expressions in the page. 
 */
public class DefaultViewControllerNameMapper implements ViewControllerNameMapper
{
    /**
     * An unmodifiable set of strings which are not permitted as bean-names.
     * <p>
     * If the mapping of any viewId to a bean-name results in one of these values,
     * then the name-mapper must modify the result.  
     * <p>
     * TODO: move this list to some shared class. Other ViewControllerNameMapper
     * implementations could find this list useful. Note, however, that it is
     * servlet-specific. This class is supposed to not assume any particular
     * request/response technology.
     */
    private static final Set RESERVED_WORDS = new HashSet(Arrays.asList(
            new String[]
            {
                "applicationScope",
                "cookie",
                "facesContext",
                "header",
                "headerValues",
                "initParam",
                "param",
                "paramValues",
                "requestScope",
                "sessionScope",
                "view"
            }
    ));


    public String mapViewId(String viewId)
    {
        if (viewId == null)
        {
            return null;
        }

        boolean nextUpper = false;

        StringBuffer sb = new StringBuffer(viewId);
        for (int i = 0; i < sb.length(); i++)
        {
            char c = sb.charAt(i);
            if (c == '/')
            {
                if (i > 0)
                {
                    nextUpper = true;
                }
                sb.deleteCharAt(i);
                i--;
            }
            else if (c == '.')
            {
                sb.delete(i, sb.length());
                break;
            }
            else if (nextUpper)
            {
                sb.setCharAt(i, Character.toUpperCase(c));
                nextUpper = false;
            }
        }

        if (sb.length() > 0)
        {
            sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
        }

        String beanName = sb.toString();
        if (RESERVED_WORDS.contains(beanName))
        {
            return "_" + beanName;
        }

        if (beanName.length() > 0 && Character.isDigit(beanName.charAt(0)))
        {
            return "_" + beanName;
        }

        return beanName;
    }
}
