package com.gwt.mvp.client; import java.util.Set; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.user.client.History; import com.gwt.mvp.client.event.PlaceChangedEvent; import com.gwt.mvp.client.event.PlaceChangedHandler; import com.gwt.mvp.client.event.PlaceRequestEvent; import com.gwt.mvp.client.event.PlaceRequestHandler; import com.gwt.mvp.client.event.SessionAttributeChangedEvent; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * <code>EventBusManager</code> class provide default implementation of <code>EventBus</code>. * * @author jguibert * @author ibouakl */ public class EventBusManager extends HandlerManager implements EventBus { private LocalSession localSession; /** * Build a new instance of <code>EventBusManager</code>. * Place Handler will be registered. */ public EventBusManager() { this(true); } /** * Build a new instance of <code>EventBusManager</code>. * * @param addPlaceHandler if true, register place handler */ public EventBusManager(final boolean addPlaceHandler) { this(null, addPlaceHandler); } /** * Build a new instance of <code>EventBusManager</code>. * * @param source the event source * @param addPlaceHandler if true, register place handler */ public EventBusManager(final Object source, final boolean addPlaceHandler) { super(source); if (addPlaceHandler) { registerPlaceHandler(); } localSession = null; } @Override public Session getSession() { return getSession(false); } @Override public Session getSession(boolean create) { if (create && (localSession == null)) { localSession = new LocalSession(); } return localSession; } /** * Resister Place Handler. */ private void registerPlaceHandler() { PlaceManagerHandler placeManagerHandler = new PlaceManagerHandler(this); // Register placeManagerHandler with the History API. History.addValueChangeHandler(placeManagerHandler); // Listen for manual place change events. addHandler(PlaceChangedEvent.TYPE, placeManagerHandler); addHandler(PlaceRequestEvent.TYPE, placeManagerHandler); } @Override public boolean fireCurrentPlace() { boolean result = false; if (History.getToken() != null && History.getToken().length() != 0) { History.fireCurrentHistoryState(); result = true; } return result; } /** * <code>PlaceManagerHandler</code> internal handler. * * @author Jerome Guibert */ private static class PlaceManagerHandler implements ValueChangeHandler<String>, PlaceChangedHandler, PlaceRequestHandler { private final EventBus eventBus; private final TokenFormatter tokenFormatter; /** * Build a new instance of <code>PlaceManagerHandler</code>. * * @param eventBus event bus instance. */ public PlaceManagerHandler(final EventBus eventBus) { super(); this.eventBus = eventBus; this.tokenFormatter = new TokenFormatter(); } @Override public void onValueChange(final ValueChangeEvent<String> event) { try { eventBus.fireEvent(new PlaceRequestEvent(new Place(tokenFormatter.toPlace(event.getValue()), true))); } catch (IllegalArgumentException e) { // log something } } @Override public void onPlaceChange(final Place place) { if (!place.isFromHistory()) { History.newItem(tokenFormatter.toHistoryToken(place), false); } } @Override public void onPlaceRequest(final Place place) { if (!place.isFromHistory()) { History.newItem(tokenFormatter.toHistoryToken(place), false); } } } /** * Formats tokens from String values into <code>Place</code> values and back again. This implementation * parses the token format like so: * * <pre> * [name](;param=value)* * </pre> */ private static class TokenFormatter { private static final String PARAM_SEPARATOR = ";"; private static final String PARAM_PATTERN = PARAM_SEPARATOR + "(?!" + PARAM_SEPARATOR + ")"; private static final String PARAM_ESCAPE = PARAM_SEPARATOR + PARAM_SEPARATOR; private static final String VALUE_SEPARATOR = "="; private static final String VALUE_PATTERN = VALUE_SEPARATOR + "(?!" + VALUE_SEPARATOR + ")"; private static final String VALUE_ESCAPE = VALUE_SEPARATOR + VALUE_SEPARATOR; /** * Build a new instance of <code>TokenFormatter</code>. */ public TokenFormatter() { super(); } /** * Converts a {@link Place} into a {@link com.google.gwt.user.client.History} token. * * @param place The place request * @return The history token */ public String toHistoryToken(final Place place) { StringBuilder out = new StringBuilder(); out.append(place.getPlaceId()); Set<String> params = place.getParameterNames(); if (params != null && params.size() > 0) { for (String name : params) { out.append(PARAM_SEPARATOR); out.append(escape(name)).append(VALUE_SEPARATOR).append(escape(place.getParameter(name, null))); } } return out.toString(); } /** * Converts a {@link com.google.gwt.user.client.History} token into a {@link Place}. * * @param token The token. * @return The place request * @throws IllegalArgumentException if there is an error converting. */ public Place toPlace(final String token) throws IllegalArgumentException { Place req = null; int split = token.indexOf(PARAM_SEPARATOR); if (split == 0) { throw new IllegalArgumentException("Place name is missing."); } else if (split == -1) { req = new Place(token); } else if (split >= 0) { req = new Place(token.substring(0, split)); String paramsChunk = token.substring(split + 1); String[] paramTokens = paramsChunk.split(PARAM_PATTERN); for (String paramToken : paramTokens) { String[] param = paramToken.split(VALUE_PATTERN); if (param.length != 2) { throw new IllegalArgumentException("Bad parameter: Parameters require a single '" + VALUE_SEPARATOR + "' between the key and value."); } req = req.with(unescape(param[0]), unescape(param[1])); } } return req; } private String escape(final String value) { return value.replaceAll(PARAM_SEPARATOR, PARAM_ESCAPE).replaceAll(VALUE_SEPARATOR, VALUE_ESCAPE); } private String unescape(final String value) { return value.replaceAll(PARAM_ESCAPE, PARAM_SEPARATOR).replaceAll(VALUE_ESCAPE, VALUE_SEPARATOR); } } /** * Local Session is a container of String key/value. This object by pass loozly coupled mechanism. * * @author Jerome Guibert */ private class LocalSession implements Session { private final Map<String, Object> attributes; private final long created; public LocalSession() { super(); attributes = new HashMap<String, Object>(); created = new Date().getTime(); } /** * Returns the time when this session was created, measured in milliseconds since midnight January 1, 1970 GMT. * @return */ @Override public long getCreationTime() { return created; } /** * Returns the object bound with the specified name in this session, or null if no object is bound under the name. * @param name * @return */ @Override public Object getAttribute(String name) { return attributes.get(name); } /** * Binds an object to this session, using the name specified. * @param name * @param value */ @Override public void setAttribute(String name, Object value) { attributes.put(name, value); fireEvent(new SessionAttributeChangedEvent(name, value)); } /** * Returns an Enumeration of String objects containing the names of all the objects bou * @return */ @Override public Enumeration<String> getAttributeNames() { return Collections.enumeration(attributes.keySet()); } /** * Invalidates this session and unbinds any objects bound to it. */ @Override public void invalidate() { attributes.clear(); } } }