com.mvp4g.client.history.PlaceService.java Source code

Java tutorial

Introduction

Here is the source code for com.mvp4g.client.history.PlaceService.java

Source

/*
 * Copyright 2010 Pierre-Laurent Coirier
 * 
 * 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.mvp4g.client.history;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.mvp4g.client.Mvp4gEventPasser;
import com.mvp4g.client.Mvp4gModule;

/**
 * Place Service defines the connection between the application and the browser history.<br/>
 * <br/>
 * When an event needs to be stored in the history, the place method of <code>PlaceService</code> is
 * called. This method will transform the event to an history token the following way:<br/>
 * <br/>
 * <b>myurl#eventName?params</b><br/>
 * <br/>
 * where params is the string returned by the handling method of the history converter for the
 * event.<br/>
 * If params is null, then the URL will be:<br/>
 * <br/>
 * <b>myurl#eventName</b><br/>
 * <br/>
 * In case an event of a child module is stored, its history name and history names of all its
 * ascendant except the Root Module will be stored in the token:<br/>
 * <br/>
 * <b>myurl#childModule/subChildModule/eventName?params</b><br/>
 * <br/>
 * By default "?" is used to seperate the event name from the parameters. You can change it thanks
 * to <code>@HistoryConfiguration</code>.<br/>
 * <br/>
 * If the token generated is supposed to be crawlable, then a "!" will be added before the token.
 * 
 * 
 * 
 * @author plcoirier
 * 
 */
public class PlaceService implements ValueChangeHandler<String> {

    public static final String MODULE_SEPARATOR = "/";

    public static final String CRAWLABLE = "!";

    private HistoryProxy history = null;
    private Mvp4gModule module = null;

    @SuppressWarnings("rawtypes")
    private Map<String, HistoryConverter> converters = new HashMap<String, HistoryConverter>();

    private boolean enabled = true;

    private NavigationConfirmationInterface navigationConfirmation;

    /**
     * Build a <code>PlaceService</code>.
     * 
     */
    public PlaceService() {
        this(DefaultHistoryProxy.INSTANCE);
    }

    /**
     * Build a <code>PlaceService</code> and inject an <code>HistoryProxy</code> instance.<br/>
     * <br/>
     * This constructor is handy when you want to test <code>PlaceService</code> without using the
     * GWT History class.<br/>
     * It shouldn't be called otherwise.
     * 
     * 
     * @param history
     *            history proxy to inject
     */
    protected PlaceService(HistoryProxy history) {
        this.history = history;
        history.addValueChangeHandler(this);
    }

    /**
     * Called when the History token has changed.<br/>
     * <br/>
     * Decode the history token and call the convertFromToken method of the history converters
     * associated with this action stored in the token.<br/>
     * <br/>
     * If token is equal to empty string, ask the event bus to dispatch an initEvent.
     * 
     * @param event
     *            event containing the new history token
     * 
     */
    public void onValueChange(final ValueChangeEvent<String> event) {

        confirmEvent(new NavigationEventCommand(module.getEventBus()) {

            protected void execute() {
                convertToken(event.getValue());
            }

        });

    }

    /**
     * Convert the token to an event
     * 
     * @param token
     *            the token to convert
     */
    protected void convertToken(String token) {
        boolean toContinue = false;
        if (token != null) {
            if (token.startsWith(CRAWLABLE)) {
                token = token.substring(1);
            }
            toContinue = (token.length() > 0);
        }

        if (toContinue) {
            String[] result = parseToken(token);
            if (!forwardToChildModuleIfNeeded(result[0], result[1])) {
                dispatchEvent(result[0], result[1], module);
            }
        } else {
            module.sendInitEvent();
        }
    }

    /**
     * Parse the token and return a string array. The first element of this array contains the event
     * name whereas the second element contains the parameters associated to the event.
     * 
     * @param token
     *            token to parse
     * @return array of string
     */
    protected String[] parseToken(String token) {
        String[] result = new String[2];
        int index = token.lastIndexOf(getParamSeparator());
        result[0] = (index == -1) ? token : token.substring(0, index);
        result[1] = (index == -1) ? null : token.substring(index + 1);
        return result;
    }

    /**
     * Check if this event is a child's module event. If it's the case, forward the token to the
     * child module and return true.
     * 
     * @param eventName
     *            name of the event that was stored in the token
     * @param param
     *            parameters stored in the token
     * @return true if this child module's event.
     */
    protected boolean forwardToChildModuleIfNeeded(final String eventName, final String param) {
        boolean forAChild = eventName.contains(MODULE_SEPARATOR);
        if (forAChild) {
            Mvp4gEventPasser passer = new Mvp4gEventPasser(true) {

                @Override
                public void pass(Mvp4gModule module) {

                    if ((Boolean) eventObjects[0]) {
                        dispatchEvent(eventName, param, module);
                    } else {
                        sendNotFoundEvent(module);
                    }
                }
            };
            module.dispatchHistoryEvent(eventName, passer);
        }
        return forAChild;
    }

    /**
     * Dispatch the event thanks to the history converter.
     * 
     * @param historyName
     *            name of the event stored in the token
     * @param param
     *            parameters stored in the token
     * @param module
     *            module to which belongs the event
     */
    @SuppressWarnings("unchecked")
    protected void dispatchEvent(String historyName, String param, Mvp4gModule module) {
        if (historyName != null) {
            @SuppressWarnings("rawtypes")
            HistoryConverter converter = converters.get(historyName);
            if (converter == null) {
                sendNotFoundEvent(module);
            } else {
                String[] tab = historyName.split(MODULE_SEPARATOR);
                String finalEventName = tab[tab.length - 1];
                converter.convertFromToken(finalEventName, param, module.getEventBus());
            }
        } else {
            sendNotFoundEvent(module);
        }
    }

    /**
     * Convert an event and its associated parameters to a token.<br/>
     * 
     * @param eventName
     *            name of the event to store
     * @param param
     *            string representation of the objects associated with the event that needs to be
     *            stored in the token
     * @param onlyToken
     *            if true, only the token will be generated and browser history won't change
     * @return the generated token
     */
    public String place(String eventName, String param, boolean onlyToken) {

        if (!enabled && !onlyToken) {
            return null;
        }

        String token = tokenize(eventName, param);

        if (converters.get(eventName).isCrawlable()) {
            token = CRAWLABLE + token;
        }
        if (!onlyToken) {
            history.newItem(token, false);
        }
        return token;
    }

    /**
     * Transform an event and its parameters to a token
     * 
     * @param eventName
     *            event's name
     * @param param
     *            event's parameters
     * @return token to store in the history
     */
    public String tokenize(String eventName, String param) {
        String token = eventName;
        if ((param != null) && (param.length() > 0)) {
            token = token + getParamSeparator() + param;
        }
        return token;
    }

    /**
     * Clear the history token stored in the browse history url by adding a new empty token
     */
    public void clearHistory() {
        history.newItem("", false);
    }

    /**
     * Add a converter for an event.
     * 
     * @param historyName
     *            name of the event to store in the token
     * @param converter
     *            converter associated with this event
     */
    @SuppressWarnings("rawtypes")
    public void addConverter(String historyName, HistoryConverter converter) {
        converters.put(historyName, converter);
    }

    /**
     * @param module
     *            the module to set
     */
    public void setModule(Mvp4gModule module) {
        this.module = module;
    }

    /**
     * @param navigationConfirmation
     *            the navigationConfirmation to set
     */
    public void setNavigationConfirmation(NavigationConfirmationInterface navigationConfirmation) {
        this.navigationConfirmation = navigationConfirmation;
    }

    /**
     * Calls not found event. By default it will called a root module 
     * event, but you can override the method if you want to call some 
     * specific event for child module
     * @param module
     *             the last module where event wasn't found. If you will have 
     *             a correct historyName at the begin of url, then this will be a
     *             child module, else rootModule.
     */
    protected void sendNotFoundEvent(Mvp4gModule module) {
        this.module.sendNotFoundEvent();
    }

    /**
     * Ask for user's confirmation before firing an event
     * 
     * @param event
     *            event to confirm
     */
    public void confirmEvent(NavigationEventCommand event) {
        if (navigationConfirmation == null) {
            //no need to remove the confirmation, there is none
            event.fireEvent(false);
        } else {
            navigationConfirmation.confirm(event);
        }
    }

    /**
     * @param enabled
     *            the enabled to set
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * @return separator used to differenciate the event's name and its parameters
     */
    protected String getParamSeparator() {
        return "?";
    }

}