All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.goodow.realtime.Document Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012 Goodow.com
 * 
 * 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.goodow.realtime;

import com.goodow.realtime.channel.constant.Constants.Params;
import com.goodow.realtime.channel.util.ChannelNative;
import com.goodow.realtime.model.util.ModelFactory;
import com.goodow.realtime.operation.util.Pair;

import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import elemental.json.JsonObject;

/**
 * A Realtime document. A document consists of a Realtime model and a set of collaborators. Listen
 * on the document for the following events:
 * 
    *
  • *

    * {@link com.goodow.realtime.EventType#COLLABORATOR_LEFT} *

  • *

    * {@link com.goodow.realtime.EventType#COLLABORATOR_JOINED} *

  • *

    * {@link com.goodow.realtime.EventType#DOCUMENT_SAVE_STATE_CHANGED} *

*

* This class should not be instantiated directly. The document object is generated during the * document load process. */ @ExportPackage(ModelFactory.PACKAGE_PREFIX_REALTIME) @Export(all = true) public class Document implements EventTarget { static final String EVENT_HANDLER_KEY = "document"; private static final Logger log = Logger.getLogger(Document.class.getName()); private final List collaborators = new ArrayList(); private Model model; private Map, List>> handlers; private boolean isEventsScheduled = false; private List, Disposable>> events; private Map> eventsById; private final Runnable eventsTask = new Runnable() { private List, Disposable>> evts; private Map> evtsById; @Override public void run() { evts = events; evtsById = eventsById; events = null; eventsById = null; isEventsScheduled = false; for (int i = 0, len = evts.size(); i < len; i++) { Pair, Disposable> evt = evts.get(i); produceObjectChangedEvent(evt.first.first, evt.second); } for (Pair, Disposable> evt : evts) { fireEvent(evt.first, evt.second); } assert evtsById.isEmpty(); evts = null; evtsById = null; } private void bubblingToAncestors(String id, ObjectChangedEvent objectChangedEvent, Set seen) { if (seen.contains(id)) { return; } seen.add(id); evts.add(Pair.of(Pair.of(id, objectChangedEvent.type), (Disposable) objectChangedEvent)); String[] parents = model.getParents(id); if (parents != null) { for (String parent : parents) { bubblingToAncestors(parent, objectChangedEvent, seen); } } } @SuppressWarnings({"unchecked", "cast"}) private void fireEvent(Pair key, Disposable event) { List> handlers = getEventHandlers(key, false); if (handlers == null) { return; } for (int i = 0, len = handlers.size(); i < len; i++) { EventHandler handler = handlers.get(i); if (handler instanceof EventHandler) { ((EventHandler) handler).handleEvent(event); } else { __ocniFireEvent__(handler, event); } } } private void produceObjectChangedEvent(String id, Disposable event) { if (!evtsById.containsKey(id)) { return; } BaseModelEvent evt = (BaseModelEvent) event; assert !evt.bubbles; List eventsPerId = evtsById.get(id); evtsById.remove(id); ObjectChangedEvent objectChangedEvent = new ObjectChangedEvent(evt.target, evt.sessionId, evt.userId, eventsPerId .toArray(new BaseModelEvent[0])); Set seen = new HashSet(); bubblingToAncestors(id, objectChangedEvent, seen); } }; private DocumentBridge bridge; /** * @param bridge The driver for the GWT collaborative libraries. * @param commService The communication service to dispose when this document is disposed. * @param errorHandlerFn The third-party error handling function. */ Document(DocumentBridge bridge, Disposable commService, ErrorHandler errorHandlerFn) { this.bridge = bridge; model = new Model(bridge, this); } public void addCollaboratorJoinedListener(EventHandler handler) { addEventListener(EventType.COLLABORATOR_JOINED, handler, false); } public void addCollaboratorLeftListener(EventHandler handler) { addEventListener(EventType.COLLABORATOR_LEFT, handler, false); } public void addDocumentSaveStateListener(EventHandler handler) { addEventListener(EventType.DOCUMENT_SAVE_STATE_CHANGED, handler, false); } @Override public void addEventListener(EventType type, EventHandler handler, boolean opt_capture) { addEventListener(EVENT_HANDLER_KEY, type, handler, opt_capture); } /** * Closes the document and disconnects from the server. After this function is called, event * listeners will no longer fire and attempts to access the document, model, or model objects will * throw a {@link com.goodow.realtime.DocumentClosedError}. Calling this function after the * document has been closed will have no effect. * * @throws DocumentClosedError */ public void close() { bridge.outputSink.close(); bridge = null; model = null; } /** * Gets an array of collaborators active in this session. Each collaborator is a jsMap with these * fields: sessionId, userId, displayName, color, isMe, isAnonymous. * * @return An array of collaborators. */ public Collaborator[] getCollaborators() { return collaborators.toArray(new Collaborator[0]); } /** * Gets the collaborative model associated with this document. * * @return The collaborative model for this document. */ public Model getModel() { return model; } public void removeCollaboratorJoinedListener(EventHandler handler) { removeEventListener(EventType.COLLABORATOR_JOINED, handler, false); } public void removeCollaboratorLeftListener(EventHandler handler) { removeEventListener(EventType.COLLABORATOR_LEFT, handler, false); } @Override public void removeEventListener(EventType type, EventHandler handler, boolean opt_capture) { removeEventListener(EVENT_HANDLER_KEY, type, handler, opt_capture); } void addEventListener(String id, EventType type, EventHandler handler, boolean opt_capture) { if (id == null || type == null || handler == null) { throw new NullPointerException((id == null ? "id" : type == null ? "type" : "handler") + " was null."); } List> handlersPerType = getEventHandlers(Pair.of(id, type), true); if (handlersPerType.contains(handler)) { log.warning("The same handler can only be added once per the type."); } else { handlersPerType.add(handler); } } void checkStatus() throws DocumentClosedError { if (bridge == null) { throw new DocumentClosedError(); } } void onCollaboratorChanged(boolean isJoined, JsonObject json) { Collaborator collaborator = new Collaborator(json.getString(Params.USER_ID), json.getString(Params.SESSION_ID), json .getString(Params.DISPLAY_NAME), json.getString(Params.COLOR), json .getBoolean(Params.IS_ME), json.getBoolean(Params.IS_ANONYMOUS), json .getString(Params.PHOTO_URL)); if (isJoined) { collaborators.add(collaborator); CollaboratorJoinedEvent event = new CollaboratorJoinedEvent(this, collaborator); scheduleEvent(Document.EVENT_HANDLER_KEY, EventType.COLLABORATOR_JOINED, event); } else { collaborators.remove(collaborator); CollaboratorLeftEvent event = new CollaboratorLeftEvent(this, collaborator); scheduleEvent(Document.EVENT_HANDLER_KEY, EventType.COLLABORATOR_LEFT, event); } } void removeEventListener(String id, EventType type, EventHandler handler, boolean opt_capture) { if (handlers == null || handler == null) { return; } List> handlersPerType = handlers.get(Pair.of(id, type)); if (handlersPerType == null) { return; } handlersPerType.remove(handler); if (handlersPerType.isEmpty()) { handlers.remove(handlersPerType); if (handlers.isEmpty()) { handlers = null; } } } void scheduleEvent(String id, EventType type, Disposable event) { if (events == null) { initializeEvents(); } events.add(Pair.of(Pair.of(id, type), event)); if (event instanceof BaseModelEvent) { BaseModelEvent evt = (BaseModelEvent) event; assert !evt.bubbles; List eventsPerId = eventsById.get(id); if (eventsPerId == null) { eventsPerId = new ArrayList(); eventsById.put(id, eventsPerId); } eventsPerId.add(evt); } if (!isEventsScheduled) { isEventsScheduled = true; ChannelNative.get().scheduleDeferred(eventsTask); } } // @formatter:off private native void __ocniFireEvent__(Object handler, Object event) /*-[ GDREventBlock block = (GDREventBlock)handler; block(event); ]-*/ /*-{ }-*/; // @formatter:on private List> getEventHandlers(Pair key, boolean createIfNotExist) { if (handlers == null) { if (!createIfNotExist) { return null; } handlers = new HashMap, List>>(); } List> handlersPerType = handlers.get(key); if (handlersPerType == null) { if (!createIfNotExist) { return null; } handlersPerType = new ArrayList>(); handlers.put(key, handlersPerType); } return handlersPerType; } private void initializeEvents() { events = new ArrayList, Disposable>>(); eventsById = new HashMap>(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy