Package com.tll.client.view

Source Code of com.tll.client.view.ViewManager$PanelWrapper

/**
* The Logic Lab
* @author jpk Jan 3, 2008
*/
package com.tll.client.view;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.tll.client.ui.view.ViewContainer;

/**
* Manages view life-cycles and view caching. Also
* serves as an MVC dispatcher dispatching view requests to the appropriate view
* controller. View history is also managed here.
* @author jpk
*/
public final class ViewManager implements ValueChangeHandler<String>, IHasViewChangeHandlers {

  private static final Logger log = Logger.getLogger("ViewManager");
 
  private static final class PanelWrapper extends Composite implements IHasViewChangeHandlers {

    private final ComplexPanel panel;

    public PanelWrapper(ComplexPanel panel) {
      super();
      this.panel = panel;
    }

    @Override
    public HandlerRegistration addViewChangeHandler(IViewChangeHandler handler) {
      return addHandler(handler, ViewChangeEvent.TYPE);
    }
  }

  /**
   * Creates a {@link ViewRef} given a {@link CView}.
   * @param e the view
   * @return Newly created {@link ViewRef}.
   */
  private static ViewRef ref(CView e) {
    return new ViewRef(e.init, e.vc.getView().getShortViewName(), e.vc.getView().getLongViewName());
  }

  private static ViewManager instance;

  /**
   * Must be called once by the app on startup.
   * @param parentViewPanel The Panel that is the parent of the pinned view
   *        container. Must not be <code>null</code>.
   * @param cacheCapacity The limit of the number of views to hold in memory.
   */
  public static void initialize(ComplexPanel parentViewPanel, int cacheCapacity) {
    instance = new ViewManager(parentViewPanel, cacheCapacity);
  }

  /**
   * Clears and nullifies the singleton instance.
   */
  public static void shutdown() {
    if(instance != null) {
      instance.clear();
      instance = null;
    }
  }

  /**
   * @return The singleton {@link ViewManager} instance.
   */
  public static ViewManager get() {
    if(instance == null) throw new IllegalStateException("Not initialized.");
    return instance;
  }

  /**
   * The parent view panel. This property must be set so that views can attach
   * to the DOM. The panel must support multiple children.
   */
  private final PanelWrapper parentViewPanel;

  /**
   * The view cache.
   */
  final ViewCache cache;

  /**
   * The first and currently pinned view.
   */
  private CView initial, current, pendingUnload;

  /**
   * The view request that is pending.
   * <p>
   * In order to comply with the history event system, we must be routed from
   * the browser history context.
   * <p>
   * <strong>view request procedure:</strong>
   * <ol>
   * <li>Dispatch a view request via {@link #dispatch(IViewRequest)}.
   * <li>Temporarily cache the view request in {@link #pendingViewRequest}.
   * <li>Invoke {@link History#newItem(String)}
   * <li>Re-acquire the view request held in {@link #pendingViewRequest}.
   * <li>Dispatch normally
   * </ol>
   */
  private IViewRequest pendingViewRequest;

  /**
   * Constructor
   * @param parentPanel The panel that will contained pinned views.
   * @param cacheCapacity
   * @see ViewManager#initialize(ComplexPanel, int)
   */
  private ViewManager(ComplexPanel parentPanel, int cacheCapacity) {
    parentViewPanel = new PanelWrapper(parentPanel);
    cache = new ViewCache(cacheCapacity);
    History.addValueChangeHandler(this);
  }

  @Override
  public HandlerRegistration addViewChangeHandler(IViewChangeHandler handler) {
    return parentViewPanel.addViewChangeHandler(handler);
  }

  @Override
  public void fireEvent(GwtEvent<?> event) {
    parentViewPanel.fireEvent(event);
  }

  /**
   * @return The assigned panel to which views are shown.
   */
  public Panel getParentViewPanel() {
    return parentViewPanel.panel;
  }

  /**
   * @return The current view's runtime identifier.
   */
  public ViewKey getCurrentViewKey() {
    return current == null ? null : current.vc.getViewKey();
  }

  /**
   * @return the current view or <code>null</code> if not set.
   */
  public IView<?> getCurrentView() {
    ViewKey cvk = getCurrentViewKey();
    return cvk == null ? null : resolveView(cvk);
  }

  /**
   * Loads the view into cache but does't add it to the dom.
   * @param init
   */
  @SuppressWarnings("unchecked")
  public void loadView(IViewInitializer init) {
    // first check if not already cached
    CView e = cache.peekQueue(init.getViewKey());
    if(e != null) return;

    log.fine("Loading view: " + init + " ..");

    // non-cached view
    final IView<IViewInitializer> view = (IView<IViewInitializer>) init.getViewKey().getViewClass().newView();

    // initialize the view
    view.initialize(init);

    e = new CView(new ViewContainer(view, init.getViewKey().getViewClass().getViewOptions(), init.getViewKey()), init);

    cacheView(e);

    view.apply(e.vc, e.vc.getToolbar());

    // load the view
    view.refresh();
  }

  /**
   * Searches the currently cached views for the view matching the given view
   * key.
   * @param key the view key
   * @return The matching cached view or <code>null</code> if not found.
   */
  public IView<?> resolveView(ViewKey key) {
    CView cv = cache.peekQueue(key);
    return cv == null ? null : cv.vc.getView();
  }

  /**
   * Sets the current view. The current view is defined as the visible pinned
   * view.
   * @param init The view initializer employed only when the view is not present
   *        in the view cache
   */
  @SuppressWarnings("unchecked")
  void setCurrentView(IViewInitializer init) {
    final ViewKey key = init.getViewKey();
    log.fine("Setting current view: '" + key + "' ..");

    CView e;
    final ViewOptions options = init.getViewKey().getViewClass().getViewOptions();
    final int cacheIndex = cache.searchQueue(key);

    if(cacheIndex != -1) {
      // existing cached view
      e = cache.removeAt(cacheIndex);
      assert e != null;
      setCurrentView(e);
    }
    else {
      log.fine("Creating and initializing view: " + key + " ..");
      // non-cached view
      final IView<IViewInitializer> view = (IView<IViewInitializer>) key.getViewClass().newView();

      // initialize the view
      view.initialize(init);

      e = new CView(new ViewContainer(view, options, key), init);
      setCurrentView(e);

      view.apply(e.vc, e.vc.getToolbar());

      // load the view
      view.refresh();
    }
  }

  /**
   * Sets the current view given a presently cached view container.
   * @param e primary cache element
   */
  private void setCurrentView(CView e) {
    final ViewContainer vc = e.vc;
    final ViewOptions options = e.init.getViewKey().getViewClass().getViewOptions();

    final boolean rmvCrnt = current != null;
    final boolean pinPndg = rmvCrnt || (!e.vc.isAttached() || !e.vc.isVisible());

    if(rmvCrnt) {
      // remove or hide the current pinned view
      if(current.init.getViewKey().getViewClass().getViewOptions().isKeepInDom()) {
        current.vc.setVisible(false);
      }
      else {
        current.vc.removeFromParent();
      }
      RootPanel.get().removeStyleName(current.vc.getView().getViewClass().getName());
    }
    if(pinPndg) {
      // add the current view's view class name to the root panel
      RootPanel.get().addStyleName(e.vc.getView().getViewClass().getName());

      // ** pin the view (the current view in the dom) **
      // vc.makePinReady();

      if(options.isKeepInDom() && parentViewPanel.panel.getWidgetIndex(vc) >= 0) {
        vc.setVisible(true);
      }
      else {
        parentViewPanel.panel.add(vc);
      }
    }
    // set as current
    current = e;

    // fire view load event
    Scheduler.get().scheduleDeferred(new Command() {

      @SuppressWarnings("synthetic-access")
      @Override
      public void execute() {
        fireEvent(ViewChangeEvent.viewLoadedEvent(current.getViewKey()));
      }
    });

    // add the view to the cache
    cacheView(e);

    // unload pending cview
    if(pendingUnload != null) {
      log.fine("Destroying pending unload view- " + pendingUnload.vc.getView().toString() + "..");
      pendingUnload.vc.getView().onDestroy();

      // fire view un-load event
      final ViewKey vk = pendingUnload.getViewKey();
      Scheduler.get().scheduleDeferred(new Command() {

        @Override
        public void execute() {
          fireEvent(ViewChangeEvent.viewUnloadedEvent(vk));
        }
      });

      pendingUnload = null;
    }

    // set the initial view if not set
    if(initial == null) initial = e;
  }

  private void cacheView(CView e) {
    // add the view to the cache
    final CView old = cache.cache(e);
    if(old != null) {
      assert old != e && !old.getViewKey().equals(e.getViewKey());
      log.fine("Destroying view - " + old.vc.getView().toString() + "..");
      // view life-cycle destroy
      old.vc.getView().onDestroy();

      // fire view un-load event
      Scheduler.get().scheduleDeferred(new Command() {

        @Override
        public void execute() {
          fireEvent(ViewChangeEvent.viewUnloadedEvent(old.getViewKey()));
        }
      });
    }

  }

  /**
   * Unloads the given view optionally removing it from the view cache then
   * updates the current pinned view routing through the history system.
   * @param key The view key of the view to unload
   * @param destroy Remove the view from the queue of current views?
   * @param erradicate Physically remove the view from cache entirely?
   */
  void unloadView(ViewKey key, boolean destroy, boolean erradicate) {
    final CView e = findView(key);
    if(e == null) return;

    // unload the given view
    e.vc.close();

    if(pendingUnload != null) throw new IllegalStateException();
    if(destroy) {
      pendingUnload = e;
      // remove the view from cache
      cache.remove(key);
    }
    if(erradicate) {
      cache.removeFromStack(key);
    }

    // find the newest pinned view excluding the one to be unloaded
    CView pendingCurrent = findFirstView(e);
    if(pendingCurrent == null && e != initial) {
      pendingCurrent = initial;
    }
    if(pendingCurrent != null && pendingCurrent != current) {
      History.newItem(pendingCurrent.getViewKey().getToken());
    }
    else {
      // we're unloading a view that isn't current but we still need to notify
      // our view listeners so they remain in sync
      if(pendingUnload != null) {
        pendingUnload.vc.getView().onDestroy();
        // fire view un-load event
        Scheduler.get().scheduleDeferred(new Command() {

          @SuppressWarnings("synthetic-access")
          @Override
          public void execute() {
            fireEvent(ViewChangeEvent.viewUnloadedEvent(pendingUnload.getViewKey()));
            pendingUnload = null;
          }
        });
      }
    }
  }

  /**
   * Removes all view artifacts from the DOM and clears the cache.
   */
  public void clear() {
    log.fine("Clearing view cache..");
    if(cache.size() > 0) {
      for(final Iterator<CView> itr = cache.queueIterator(); itr.hasNext();) {
        final CView e = itr.next();
        e.vc.close();
        e.vc.getView().onDestroy();
      }
    }
    cache.clear();
    log.fine("View cache cleared");
  }

  /**
   * Generic find view method returning the first found match in the view cache.
   * @param exclude The view to exclude from the search. May be
   *        <code>null</code>.
   * @return The first found view or <code>null</code> if no match found.
   */
  private CView findFirstView(CView exclude) {
    final Iterator<CView> itr = cache.queueIterator();
    if(itr != null) {
      while(itr.hasNext()) {
        final CView e = itr.next();
        if(exclude == null || exclude != e) {
          return e;
        }
      }
    }
    return null;
  }

  /**
   * Locates a cached view given a view key.
   * @param key The view key
   * @return The found {@link IView} or <code>null</code> if not present in the
   *         view cache.
   */
  private CView findView(ViewKey key) {
    final Iterator<CView> itr = cache.queueIterator();
    if(itr != null) {
      while(itr.hasNext()) {
        final CView e = itr.next();
        if(e.getViewKey().equals(key)) {
          return e;
        }
      }
    }
    return null;
  }

  /**
   * Locates a cached view given a view key token.
   * @param viewKeyToken
   * @return The found {@link IView} or <code>null</code> if not present in the
   *         view cache.
   */
  private CView findView(String viewKeyToken) {
    final Iterator<CView> itr = cache.queueIterator();
    if(itr != null) {
      while(itr.hasNext()) {
        final CView e = itr.next();
        final String vhtoken = e.getViewKey().getToken();
        if(vhtoken.equals(viewKeyToken)) {
          return e;
        }
      }
    }
    return null;
  }

  /**
   * Resolves a view key hash to the associated view ref held in the view cache.
   * <p>
   * <b>NOTE:</b> we could throw an exception here since we are retaining view
   * refs for *all* visited views and, therefore, we expect to be able to
   * resolve any view key hash to a view ref but we account for possibility the
   * user may have manually changed the view key hash in the query string or
   * equivalent.
   * @param viewKeyToken
   * @return The found view ref or <code>null</code> if not found.
   */
  private ViewRef findViewRef(String viewKeyToken) {
    // try visited view ref cache
    final Iterator<ViewRef> vitr = cache.visitedRefIterator();
    if(vitr != null) {
      while(vitr.hasNext()) {
        final ViewRef r = vitr.next();
        final String hc = r.getViewKey().getToken();
        if(hc.equals(viewKeyToken)) {
          return r;
        }
      }
    }
    return null;
  }

  /**
   * @return Never <code>null</code> array of the currently cached views from
   *         most recently visited (head) to oldest (tail) which may be empty
   *         indicating there are currently no cached views.
   */
  public IView<?>[] getCachedViews() {
    if(cache.size() == 0) return new IView[0];
    final ArrayList<IView<?>> list = new ArrayList<IView<?>>(cache.size());
    for(final Iterator<CView> itr = cache.queueIterator(); itr.hasNext();) {
      list.add(itr.next().vc.getView());
    }
    return list.toArray(new IView[0]);
  }

  /**
   * Provides an array of the cached views as stand-alone references in "cache"
   * order (head is newest).
   * <p>
   * <b>IMPT:</b> The current view is <em>not</em> included.
   * @param capacity the maximum number of view refs to provide in the returned
   *        array. <code>-1</code> indicates un-bounded in which case the
   *        capacity is that of the number of distinct views visited.
   * @param includeFirst Include the first cached view? <br>
   *        NOTE: This view is <em>always</em> retained.
   * @return A newly created never <code>null</code> array of view references.
   */
  public ViewRef[] getViewRefs(int capacity, boolean includeFirst) {
    if(initial == null) return new ViewRef[0];
    assert current != null;

    if(capacity == -1) capacity = cache.numVisited();

    int count = 0;

    final ArrayList<ViewRef> plist = new ArrayList<ViewRef>();

    final Iterator<ViewRef> ritr = cache.visitedRefIterator();
    if(ritr != null) {
      while(ritr.hasNext() && count < capacity) {
        ViewRef r = ritr.next();
        if(current.compareTo(r) == 0) {
          // don't include the current view
          r = null;
        }
        if(r != null) {
          plist.add(r);
          count++;
        }
      }
    }
    assert count <= capacity;

    // include the initial view if called for and not already in list and not
    // the current view
    if(includeFirst && !initial.equals(current)) {
      // verify not already present
      int initialIndex = -1;
      for(int i = 0; i < plist.size(); i++) {
        if(initial.compareTo(plist.get(i)) == 0) {
          initialIndex = i;
          break;
        }
      }
      if(initialIndex == -1) {
        if(count == capacity) {
          plist.remove(plist.size() - 1);
        }
        plist.add(ref(initial));
      }
    }

    return plist.toArray(new ViewRef[plist.size()]);
  }

  /**
   * Dispatches the view request event to the appropriate controller.
   * @param request The view request
   */
  public void dispatch(IViewRequest request) {
    if(request == null) throw new IllegalArgumentException("No view request specified.");

    if(pendingViewRequest == null) {
      String htoken;
      if(request.addHistory()) {
        // history routing required
        assert request.getViewKey() != null : "Unable to add history: No view key specified.";
        htoken = History.getToken();
        final String vtoken = request.getViewKey().getToken();
        if(vtoken != null && vtoken.equals(htoken)) {
          doDispatch(request);
        }
        else {
          // need to route through history first
          this.pendingViewRequest = request;
          htoken = request.getViewKey().getToken();
          log.fine("Routing view '" + request.getViewKey() + "' through history with hash: " + htoken);
          History.newItem(htoken);
        }
      }
      else {
        // no history routing required
        doDispatch(request);
      }
    }
    else {
      assert request == pendingViewRequest;
      pendingViewRequest = null; // reset
      doDispatch(request);
    }
  }

  private void doDispatch(IViewRequest request) {
    // do actual disptach
    log.fine("Dispatching view request: " + request + " ..");
    if(request instanceof ShowViewRequest) {
      setCurrentView(((ShowViewRequest) request).getViewInitializer());
    }
    else if(request instanceof UnloadViewRequest) {
      UnloadViewRequest u = (UnloadViewRequest) request;
      unloadView(u.getViewKey(), u.isDestroy(), u.isErradicate());
    }
    else
      throw new IllegalStateException("Unhandled view request: " + request);
   
    // fire on complete command
    Command occ = request.onCompleteCommand();
    if(occ != null) {
      occ.execute();
    }
  }

  @Override
  public void onValueChange(ValueChangeEvent<String> event) {
    final String htoken = event.getValue();
    if(htoken != null) {
      log.fine("Considering history token: " + htoken + "..");
      if(pendingViewRequest != null) {
        // dispatch the view request
        dispatch(pendingViewRequest);
      }
      else {
        // user pressed the back button or a non-show type view request was
        // invoked or equivalant
        CView e = findView(htoken);
        if(e == null) {
          // probably the user is clicking the back button a number of times
          // beyond the cache capacity
          // resort to the visited view ref cache
          final ViewRef r = findViewRef(htoken);
          if(r != null) {
            setCurrentView(r.getViewInitializer());
            return;
          }
          // this should only happen when the user mucks with the view key hash
          // in the query string
          // resort to the initial view
          //log.fine("Un-resolved : " + htoken);
          //e = initial;
        }
        if(e != null) {
          setCurrentView(e);
        }
        //else {
          //log.fine("Un-resolvable view hash: " + htoken + ". No action performed.");
        //}
      }
    }
  }
}
TOP

Related Classes of com.tll.client.view.ViewManager$PanelWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.