com.google.gwt.activity.shared.ActivityManager.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.activity.shared.ActivityManager.java

Source

/*
 * Copyright 2010 Google Inc.
 * 
 * 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.google.gwt.activity.shared;

import com.google.gwt.event.shared.ResettableEventBus;
import com.google.gwt.event.shared.UmbrellaException;
import com.google.gwt.place.shared.PlaceChangeEvent;
import com.google.gwt.place.shared.PlaceChangeRequestEvent;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Manages {@link Activity} objects that should be kicked off in response to
 * {@link PlaceChangeEvent} events. Each activity can start itself
 * asynchronously, and provides a widget to be shown when it's ready to run.
 */
public class ActivityManager implements PlaceChangeEvent.Handler, PlaceChangeRequestEvent.Handler {

    /**
     * Wraps our real display to prevent an Activity from taking it over if it is
     * not the currentActivity.
     */
    private class ProtectedDisplay implements AcceptsOneWidget {
        private final Activity activity;

        ProtectedDisplay(Activity activity) {
            this.activity = activity;
        }

        public void setWidget(IsWidget view) {
            if (this.activity == ActivityManager.this.currentActivity) {
                startingNext = false;
                showWidget(view);
            }
        }
    }

    private static final Activity NULL_ACTIVITY = new AbstractActivity() {
        public void start(AcceptsOneWidget panel, com.google.gwt.event.shared.EventBus eventBus) {
        }
    };

    private final ActivityMapper mapper;

    private final EventBus eventBus;

    /*
     * Note that we use the legacy class from com.google.gwt.event.shared, because
     * we can't change the Activity interface.
     */
    private final ResettableEventBus stopperedEventBus;

    private Activity currentActivity = NULL_ACTIVITY;

    private AcceptsOneWidget display;

    private boolean startingNext = false;

    private HandlerRegistration handlerRegistration;

    /**
     * Create an ActivityManager. Next call {@link #setDisplay}.
     * 
     * @param mapper finds the {@link Activity} for a given
     *          {@link com.google.gwt.place.shared.Place}
     * @param eventBus source of {@link PlaceChangeEvent} and
     *          {@link PlaceChangeRequestEvent} events.
     */
    public ActivityManager(ActivityMapper mapper, EventBus eventBus) {
        this.mapper = mapper;
        this.eventBus = eventBus;
        this.stopperedEventBus = new ResettableEventBus(eventBus);
    }

    /**
     * Deactivate the current activity, find the next one from our ActivityMapper,
     * and start it.
     * <p>
     * The current activity's widget will be hidden immediately, which can cause
     * flicker if the next activity provides its widget asynchronously. That can
     * be minimized by decent caching. Perenially slow activities might mitigate
     * this by providing a widget immediately, with some kind of "loading"
     * treatment.
     */
    public void onPlaceChange(PlaceChangeEvent event) {
        Activity nextActivity = getNextActivity(event);

        Throwable caughtOnStop = null;
        Throwable caughtOnCancel = null;
        Throwable caughtOnStart = null;

        if (nextActivity == null) {
            nextActivity = NULL_ACTIVITY;
        }

        if (currentActivity.equals(nextActivity)) {
            return;
        }

        if (startingNext) {
            // The place changed again before the new current activity showed its
            // widget
            caughtOnCancel = tryStopOrCancel(false);
            currentActivity = NULL_ACTIVITY;
            startingNext = false;
        } else if (!currentActivity.equals(NULL_ACTIVITY)) {
            showWidget(null);

            /*
             * Kill off the activity's handlers, so it doesn't have to worry about
             * them accidentally firing as a side effect of its tear down
             */
            stopperedEventBus.removeHandlers();
            caughtOnStop = tryStopOrCancel(true);
        }

        currentActivity = nextActivity;

        if (currentActivity.equals(NULL_ACTIVITY)) {
            showWidget(null);
        } else {
            startingNext = true;
            caughtOnStart = tryStart();
        }

        if (caughtOnStart != null || caughtOnCancel != null || caughtOnStop != null) {
            Set<Throwable> causes = new LinkedHashSet<Throwable>();
            if (caughtOnStop != null) {
                causes.add(caughtOnStop);
            }
            if (caughtOnCancel != null) {
                causes.add(caughtOnCancel);
            }
            if (caughtOnStart != null) {
                causes.add(caughtOnStart);
            }

            throw new UmbrellaException(causes);
        }
    }

    /**
     * Reject the place change if the current activity is not willing to stop.
     * 
     * @see com.google.gwt.place.shared.PlaceChangeRequestEvent.Handler#onPlaceChangeRequest(PlaceChangeRequestEvent)
     */
    public void onPlaceChangeRequest(PlaceChangeRequestEvent event) {
        event.setWarning(currentActivity.mayStop());
    }

    /**
     * Sets the display for the receiver, and has the side effect of starting or
     * stopping its monitoring the event bus for place change events.
     * <p>
     * If you are disposing of an ActivityManager, it is important to call
     * setDisplay(null) to get it to deregister from the event bus, so that it can
     * be garbage collected.
     * 
     * @param display an instance of AcceptsOneWidget
     */
    public void setDisplay(AcceptsOneWidget display) {
        boolean wasActive = (null != this.display);
        boolean willBeActive = (null != display);
        this.display = display;
        if (wasActive != willBeActive) {
            updateHandlers(willBeActive);
        }
    }

    private Activity getNextActivity(PlaceChangeEvent event) {
        if (display == null) {
            /*
             * Display may have been nulled during PlaceChangeEvent dispatch. Don't
             * bother the mapper, just return a null to ensure we shut down the
             * current activity
             */
            return null;
        }
        return mapper.getActivity(event.getNewPlace());
    }

    private void showWidget(IsWidget view) {
        if (display != null) {
            display.setWidget(view);
        }
    }

    private Throwable tryStart() {
        Throwable caughtOnStart = null;
        try {
            /*
             * Wrap the actual display with a per-call instance that protects the
             * display from canceled or stopped activities, and which maintains our
             * startingNext state.
             */
            currentActivity.start(new ProtectedDisplay(currentActivity), stopperedEventBus);
        } catch (Throwable t) {
            caughtOnStart = t;
        }
        return caughtOnStart;
    }

    private Throwable tryStopOrCancel(boolean stop) {
        Throwable caughtOnStop = null;
        try {
            if (stop) {
                currentActivity.onStop();
            } else {
                currentActivity.onCancel();
            }
        } catch (Throwable t) {
            caughtOnStop = t;
        } finally {
            /*
             * Kill off the handlers again in case it was naughty and added new ones
             * during onstop or oncancel
             */
            stopperedEventBus.removeHandlers();
        }
        return caughtOnStop;
    }

    private void updateHandlers(boolean activate) {
        if (activate) {
            final HandlerRegistration placeReg = eventBus.addHandler(PlaceChangeEvent.TYPE, this);
            final HandlerRegistration placeRequestReg = eventBus.addHandler(PlaceChangeRequestEvent.TYPE, this);

            this.handlerRegistration = new HandlerRegistration() {
                public void removeHandler() {
                    placeReg.removeHandler();
                    placeRequestReg.removeHandler();
                }
            };
        } else {
            if (handlerRegistration != null) {
                handlerRegistration.removeHandler();
                handlerRegistration = null;
            }
        }
    }
}