Package com.google.mobwrite

Source Code of com.google.mobwrite.ShareObj

/**
* MobWrite - Real-time Synchronization and Collaboration Service
*
* Copyright 2009 Google Inc.
* http://code.google.com/p/google-mobwrite/
*
* 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.mobwrite;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.regex.Pattern;
import name.fraser.neil.plaintext.diff_match_patch;
import name.fraser.neil.plaintext.diff_match_patch.*;

public abstract class ShareObj {
  /**
   * Instantiation of the Diff Match Patch library.
   * http://code.google.com/p/google-diff-match-patch/
   */
  protected diff_match_patch dmp;

  /**
   * The filename or ID for this shared object.
   */
  protected String file;

  public String getFile() {
    return file;
  }

  /**
   * The hosting MobWriteClient.
   */
  protected MobWriteClient mobwrite;

  public MobWriteClient getMobwrite() {
    return mobwrite;
  }

  /**
   * List of currently unacknowledged edits sent to the server.
   */
  protected LinkedList<Object[]> editStack;

  /**
   * Client's understanding of what the server's text looks like.
   */
  protected String shadowText = "";

  /**
   * The client's version for the shadow (n).
   */
  protected int clientVersion = 0;

  /**
   * The server's version for the shadow (m).
   */
  protected int serverVersion = 0;

  /**
   * Did the client understand the server's delta in the previous heartbeat?
   * Initialize false because the server and client are out of sync initially.
   */
  protected boolean deltaOk = false;

  /**
   * Synchronization mode.
   * True: Used for text, attempts to gently merge differences together.
   * False: Used for numbers, overwrites conflicts, last save wins.
   */
  protected boolean mergeChanges = true;

  /**
   * A file ID must start with a letter and continue with letters, numbers,
   * dashes, periods, colons or underscores.  From the W3 spec for HTML IDs.
   */
   private static Pattern idPattern = Pattern.compile("^[A-Za-z][-.:\\w]*$");

  /**
   * Constructor.  Create a ShareObj with a file ID.
   * @param file Filename to share as.
   * @throws IllegalArgumentException If filename is illegal.
   */
  public ShareObj(String file) {
    if (!idPattern.matcher(file).matches()) {
      throw new IllegalArgumentException("Illegal id " + file);
    }
    this.file = file;
    this.dmp = new diff_match_patch();
    this.dmp.Diff_Timeout = 0.5f;
    // List of unacknowledged edits sent to the server.
    this.editStack = new LinkedList<Object[]>();
  }

  /**
   * Fetch or compute a plaintext representation of the user's text.
   * @return Plaintext content.
   */
  public abstract String getClientText();

  /**
   * Set the user's text based on the provided plaintext.
   * @param text New text.
   */
  public abstract void setClientText(String text);

  /**
   * Modify the user's plaintext by applying a series of patches against it.
   * @param patches Array of Patch objects.
   */
  public void patchClientText(LinkedList<Patch> patches) {
    String oldClientText = this.getClientText();
    Object[] result = this.dmp.patch_apply(patches, oldClientText);
    // Set the new text only if there is a change to be made.
    if (!oldClientText.equals(result[0])) {
      // The following will probably destroy any cursor or selection.
      // Widgets with cursors should override and patch more delicately.
      this.setClientText((String) result[0]);
    }
  }

  /**
   * Notification of when a diff was sent to the server.
   * @param diffs Array of diff objects.
   */
  private void onSentDiff(LinkedList<Diff> diffs) {
    // Potential hook for subclass
  }

  /**
   * Does the text look like unmergable content?
   * Currently we look for numbers.
   * @param text Plaintext content.
   * @return True iff unmergable.
   */
  protected boolean isEnum(String text) {
    Pattern p = Pattern.compile("^\\s*-?[\\d.,]+\\s*$");
    return !p.matcher(text).matches();
  }

  /**
   * Return the command to nullify this field.  Also unshares this field.
   * @return Command to be sent to the server.
   */
  protected String nullify() {
    mobwrite.unshare(this);
    // Create the output starting with the file statement, followed by the edits.
    String data = mobwrite.idPrefix + this.file;
    return "N:" + data + '\n';
  }

  /**
   * Asks the ShareObj to synchronize.  Computes client-made changes since
   * previous postback.  Return '' to skip this synchronization.
   * @return Commands to be sent to the server.
   */
  protected String syncText() {
    String clientText;
    try {
      clientText = this.getClientText();
      if (clientText == null) {
        // Null is not an acceptable result.
        throw new NullPointerException();
      }
    } catch (Exception e) {
      // Potential call to untrusted 3rd party code.
      this.mobwrite.logger.log(Level.SEVERE, "Error calling getClientText on '"
                                             + this.file + "'", e);
      return "";
    }
    if (this.deltaOk) {
      // The last delta postback from the server to this shareObj was successful.
      // Send a compressed delta.
      LinkedList<Diff> diffs = this.dmp.diff_main(this.shadowText, clientText, true);
      if (diffs.size() > 2) {
        this.dmp.diff_cleanupSemantic(diffs);
        this.dmp.diff_cleanupEfficiency(diffs);
      }
      boolean changed = diffs.size() != 1
          || diffs.getFirst().operation != Operation.EQUAL;
      if (changed) {
        this.mobwrite.clientChange_ = true;
        this.shadowText = clientText;
      }
      // Don't bother appending a no-change diff onto the stack if the stack
      // already contains something.
      if (changed || this.editStack.isEmpty()) {
        String action = (this.mergeChanges ? "d:" : "D:") + this.clientVersion
            + ':' + this.dmp.diff_toDelta(diffs);
        this.editStack.push(new Object[]{this.clientVersion, action});
        this.clientVersion++;
        try {
          this.onSentDiff(diffs);
        } catch (Exception e) {
          // Potential call to untrusted 3rd party code.
          this.mobwrite.logger.log(Level.SEVERE, "Error calling onSentDiff on '"
                                                 + this.file + "'", e);
        }
      }
    } else {
      // The last delta postback from the server to this shareObj didn't match.
      // Send a full text dump to get back in sync. This will result in any
      // changes since the last postback being wiped out. :(
      String data = clientText;
      try {
        data = URLEncoder.encode(data, "UTF-8").replace('+', ' ');
      } catch (UnsupportedEncodingException e) {
        // Not likely on modern system.
        throw new Error("This system does not support UTF-8.", e);
      }
      data = MobWriteClient.unescapeForEncodeUriCompatability(data);
      this.shadowText = clientText;
      this.clientVersion++;
      String action = "r:" + this.clientVersion + ':' + data;
      // Append the action to the edit stack.
      this.editStack.push(new Object[]{this.clientVersion, action});
      // Sending a raw dump will put us back in sync.
      // Set deltaOk to true in case this sync fails to connect, in which case
      // the following sync(s) should be a delta, not more raw dumps.
      this.deltaOk = true;
    }

    // Create the output starting with the file statement, followed by the edits.
    String data = mobwrite.idPrefix + this.file;
    data = "F:" + this.serverVersion + ':' + data + '\n';
    for (Object[] pair : this.editStack) {
      data += (String) pair[1] + '\n';
    }
    return data;
  }

  /**
   * Stop sharing this object.
   */
  public void unshare() {
    if (this.mobwrite != null) {
      this.mobwrite.unshare(this);
    }
  }
}
TOP

Related Classes of com.google.mobwrite.ShareObj

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.