package org.tessell.place;
import java.util.HashMap;
import org.tessell.place.events.PlaceChangedEvent;
import org.tessell.place.events.PlaceChangedHandler;
import org.tessell.place.events.PlaceRequestEvent;
import org.tessell.place.events.PlaceRequestHandler;
import org.tessell.place.history.IsHistory;
import org.tessell.place.tokenizer.Tokenizer;
import org.tessell.place.tokenizer.TokenizerException;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.web.bindery.event.shared.EventBus;
public class DefaultPlaceManager implements PlaceManager {
private final EventBus eventBus;
private final Tokenizer tokenizer;
private final IsHistory history;
private boolean alreadyHandling = false;
protected final HashMap<String, Place> places = new HashMap<String, Place>();
public DefaultPlaceManager(final EventBus eventBus, final Tokenizer tokenizer, final IsHistory history) {
this.eventBus = eventBus;
this.tokenizer = tokenizer;
this.history = history;
this.history.addValueChangeHandler(new OnHistoryValueChanged());
eventBus.addHandler(PlaceRequestEvent.getType(), new OnPlaceRequest());
eventBus.addHandler(PlaceChangedEvent.getType(), new OnPlaceChanged());
}
public void registerPlace(final Place place) {
if (places.containsKey(place.getName())) {
throw new IllegalStateException(place.getName() + " is already taken");
}
places.put(place.getName(), place);
place.bind();
}
public void deregisterPlace(final Place place) {
place.unbind();
places.remove(place.getName());
}
/** Called when we can't parse the history token. */
protected void handleInvalidToken(final String invalidToken) {
}
/** Updates History if it has changed, without firing another PlaceRequestEvent. */
protected void setTokenWithoutEvent(final PlaceRequest request) {
final String requestToken = tokenizer.toHistoryToken(request);
final String currentToken = history.getToken();
if (currentToken == null || !currentToken.equals(requestToken)) {
try {
// We call newItem(..., true) to notify other history listeners of the
// change but but use alreadyHandling to note to ourselves that, when
// this comes back via OnHistoryValueChanged, we are the originators
alreadyHandling = true;
history.newItem(requestToken, true);
} finally {
alreadyHandling = false;
}
}
}
/** Finds the place for {@code event} and calls {@link handleRequest}. */
protected void handleRequest(final PlaceRequest request) {
final Place place = places.get(request.getName());
if (place != null) {
setTokenWithoutEvent(request);
place.handleRequest(request);
eventBus.fireEvent(new PlaceChangedEvent(place, request));
}
}
/** Fires a {@link PlaceRequestEvent} with the current history token, if present, otherwise return <code>false</code>. */
public boolean fireCurrentPlace() {
final String currentToken = history.getToken();
if (currentToken != null && currentToken.trim().length() > 0) {
history.fireCurrentHistoryState();
return true;
}
return false;
}
private class OnPlaceChanged implements PlaceChangedHandler {
public void onPlaceChanged(final PlaceChangedEvent event) {
setTokenWithoutEvent(event.getRequest());
}
}
/** Handles change events from {@link IsHistory}. */
private class OnHistoryValueChanged implements ValueChangeHandler<String> {
public void onValueChange(final ValueChangeEvent<String> event) {
if (alreadyHandling) {
return;
}
try {
eventBus.fireEvent(new PlaceRequestEvent(tokenizer.toPlaceRequest(event.getValue())));
} catch (final TokenizerException e) {
handleInvalidToken(event.getValue());
}
}
}
/** Listens for {@link PlaceRequestEvent}s and calls {@link #handleRequest} if its for this instance. */
private final class OnPlaceRequest implements PlaceRequestHandler {
public void onPlaceRequest(final PlaceRequestEvent event) {
handleRequest(event.getRequest());
}
}
@Override
public void fireCurrentOr(PlaceRequest defaultPlace) {
if (!fireCurrentPlace()) {
eventBus.fireEvent(defaultPlace.asEvent());
}
}
}