Package mindnotes.client.presentation

Source Code of mindnotes.client.presentation.MindMapEditor

package mindnotes.client.presentation;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import mindnotes.client.storage.CloudStorage;
import mindnotes.client.storage.LocalMapStorage;
import mindnotes.client.storage.Storage;
import mindnotes.shared.model.EmbeddedObject;
import mindnotes.shared.model.MindMap;
import mindnotes.shared.model.MindMapInfo;
import mindnotes.shared.model.Node;
import mindnotes.shared.model.NodeLocation;
import mindnotes.shared.services.UserInfo;
import mindnotes.shared.services.UserInfoService;
import mindnotes.shared.services.UserInfoServiceAsync;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;

public class MindMapEditor {

  public class DragDropAction implements UndoableAction {

    private Node _parent;

    private Node _child;
    private int _index;
    private NodeLocation _location;

    private Node _oldParent;
    private int _oldIndex;
    private NodeLocation _oldLocation;

    public DragDropAction(Node node, int index, NodeLocation location) {
      _child = node;
      _index = index;
      _location = location;

    }

    public void setParent(Node parent) {
      _parent = parent;
    }

    @Override
    public boolean doAction() {
      _oldParent = _child.getParent();

      _oldIndex = _oldParent.getChildren().indexOf(_child);
      _oldLocation = _child.getNodeLocation();

      _oldParent.removeChildNode(_child);

      _child.setNodeLocation(_location, true);

      _parent.addChildNode(_child);

      _mindMapView.holdLayout();
      NodeView childView = _nodeViews.get(_child);
      _nodeViews.get(_oldParent).moveChild(childView,
          _nodeViews.get(_parent), _index, _location);

      _mindMapView.resumeLayout();
      return _oldParent != _parent;
    }

    @Override
    public void undoAction() {

      _parent.removeChildNode(_child);

      _child.setNodeLocation(_oldLocation);

      _oldParent.addChildNode(_child);

      _mindMapView.holdLayout();

      NodeView childView = _nodeViews.get(_child);
      _nodeViews.get(_parent).moveChild(childView,
          _nodeViews.get(_oldParent), _oldIndex, _oldLocation);
      _mindMapView.resumeLayout();

    }

  }

  private class AddAction implements UndoableAction {

    private Node _createdNode;
    private NodeLocation _location;

    public AddAction(NodeLocation location) {
      _location = location;
    }

    @Override
    public boolean doAction() {
      _createdNode = addChild(_selection.getCurrentNode(), _location);
      setCurrentNode(_createdNode);
      return true;
    }

    @Override
    public void undoAction() {
      deleteNode(_createdNode);
    }

  }

  private class AddSiblingAction implements UndoableAction {

    private Node _createdNode;
    private boolean _above;

    public AddSiblingAction(boolean above) {
      _above = above;
    }

    @Override
    public boolean doAction() {
      Node current = _selection.getCurrentNode();
      _createdNode = insertChild(current.getParent(), current, _above);

      setCurrentNode(_createdNode);
      return true;
    }

    @Override
    public void undoAction() {
      deleteNode(_createdNode);
    }

  }

  private class CutAction implements UndoableAction {

    private Node _cutNode;
    private Node _parentNode;

    public CutAction() {
    }

    @Override
    public boolean doAction() {
      // TODO make this action work on the whole selection
      _cutNode = _selection.getCurrentNode();
      if (_cutNode == null)
        return false;
      _parentNode = _cutNode.getParent();
      _clipboard.setCurrentNode(_cutNode);
      deleteNode(_cutNode);

      return true;
    }

    @Override
    public void undoAction() {

      // TODO a mechanism for reporting that the action did nothing and
      // shouldn't be on the
      // undo stack
      if (_cutNode == null)
        return;
      insertChild(_parentNode, _cutNode);
      setCurrentNode(_cutNode);
    }

  }

  private class DeleteAction implements UndoableAction {

    private Node _parentNode;
    private Node _removedNode;

    public DeleteAction(Node removedNode) {
      _removedNode = removedNode;
    }

    @Override
    public boolean doAction() {
      _parentNode = _removedNode.getParent();
      deleteNode(_removedNode);
      return true;
    }

    @Override
    public void undoAction() {
      insertChild(_parentNode, _removedNode);
      setCurrentNode(_removedNode);
    }

  }

  private class ExpandAction implements UndoableAction {

    private boolean _expandOnUndo;
    private Node _subject;

    public ExpandAction(Node subject) {
      _subject = subject;
      if (_subject == null)
        throw new NullPointerException();
    }

    @Override
    public boolean doAction() {
      _subject.setExpanded(!_subject.isExpanded());

      _nodeViews.get(_subject).setExpanded(_subject.isExpanded());
      _expandOnUndo = !_subject.isExpanded();
      return true;
    }

    @Override
    public void undoAction() {
      _subject.setExpanded(_expandOnUndo);
      _nodeViews.get(_subject).setExpanded(_expandOnUndo);
    }

  }

  private class PasteAction implements UndoableAction {

    private Node _pastedNode;

    public PasteAction() {

    }

    @Override
    public boolean doAction() {
      if (_clipboard.getCurrentNode() == null)
        return false;
      _pastedNode = _clipboard.getCurrentNode();
      insertChild(_selection.getCurrentNode(), _pastedNode);
      setCurrentNode(_pastedNode);
      return true;
    }

    @Override
    public void undoAction() {
      deleteNode(_pastedNode);
    }

  }

  private Selection _clipboard;
  private MindMap _mindMap;
  private MindMapView _mindMapView;

  private Map<Node, NodeView> _nodeViews;

  private Selection _selection;
  private UndoStack _undoStack;

  private CloudStorage _cloudStorage;
  private Storage _localStorage;

  private String _currentMapKey;

  /**
   * The editor is dirty if there are any unsaved changes in the map.
   */
  private boolean _isDirty;

  UserInfoServiceAsync _userInfoService = GWT.create(UserInfoService.class);
  private DragDropAction _dragDropAction;

  public MindMapEditor(MindMapView mindMapView) {
    _nodeViews = new HashMap<Node, NodeView>();
    _undoStack = new UndoStack();

    _mindMapView = mindMapView;
    _mindMapView.setListener(new Gestures(this));
    _selection = new Selection();
    _clipboard = new Selection();
    _cloudStorage = new CloudStorage();
    if (com.google.code.gwt.storage.client.Storage.isSupported()) {
      _localStorage = new LocalMapStorage();
    }

  }

  public void updateUserInfo() {
    updateUserInfo(false);
  }

  /**
   *
   */
  public void updateUserInfo(final boolean reconnecting) {
    // ask asynchronously for user info
    _userInfoService.getUserInfo(new AsyncCallback<UserInfo>() {

      @Override
      public void onFailure(Throwable caught) {
        // most likely a connection error;
        _mindMapView.getCloudActionsView().setOffline();
        if (reconnecting)
          _mindMapView
              .showMessage("Cannot reach MindNotes server. Are you online?");

      }

      @Override
      public void onSuccess(UserInfo result) {
        if (result.isLoggedIn()) {
          _mindMapView.getCloudActionsView().setOnline(
              result.getEmail(), result.getLogoutURL());
        } else {
          // the cloud is available, but user not logged in;
          _mindMapView.getCloudActionsView().setNotLoggedIn(
              result.getLoginURL());
        }

      }
    });
  }

  /**
   *
   * @param node
   * @param loc
   *            Suggested node location. In current layout, if <c>node's
   *            parent</c> is not a root node, <c>loc</c> is ignored and
   *            parent node location is used.
   */
  private Node addChild(Node node, NodeLocation loc) {

    Node child = new Node();
    child.setText("New node");
    if (node.getNodeLocation() == NodeLocation.ROOT) {
      child.setNodeLocation(loc);
    } else {
      child.setNodeLocation(node.getNodeLocation());
    }

    node.addChildNode(child);

    _mindMapView.holdLayout();
    NodeView childView = _nodeViews.get(node).createChild();
    setUpNodeView(childView, child);
    _mindMapView.resumeLayout();
    return child;

  }

  private Node insertChild(Node parent, Node current, boolean above) {
    Node child = new Node();
    child.setText("New node");
    child.setNodeLocation(current.getNodeLocation());

    if (above) {
      parent.insertBefore(child, current);
    } else {
      parent.insertAfter(child, current);
    }
    _mindMapView.holdLayout();
    NodeView parentView = _nodeViews.get(parent);
    NodeView currentView = _nodeViews.get(current);
    NodeView childView;
    if (above) {
      childView = parentView.createChildBefore(currentView);
    } else {
      childView = parentView.createChildAfter(currentView);
    }

    setUpNodeView(childView, child);
    _mindMapView.resumeLayout();
    return child;
  }

  private void deleteNode(Node node) {
    if (node.getParent() == null)
      return;

    deleteNodeView(node);

    node.getParent().removeChildNode(node);

    setCurrentNode(node.getParent());
  }

  /**
   * Delete NodeView for the specified Node and all children NodeViews as
   * well.
   *
   * @param node
   */
  private void deleteNodeView(Node node) {
    for (Node child : node.getChildren()) {
      deleteNodeView(child);
    }
    NodeView nodeView = _nodeViews.get(node);

    if (nodeView != null) {
      _nodeViews.remove(nodeView);
      nodeView.delete();
    }
  }

  private void doUndoableAction(UndoableAction a) {
    if (a.doAction()) {
      _isDirty = true;
      _undoStack.push(a);
    }
  }

  private void generateView() {
    _mindMapView.setTitle(_mindMap.getTitle());
    _mindMapView.holdLayout();
    NodeView rootNodeView = _mindMapView.getRootNodeView();
    rootNodeView.removeAll();

    setUpNodeView(rootNodeView, _mindMap.getRootNode());

    _mindMapView.resumeLayout();

  }

  private void setUpNodeView(NodeView nodeView, Node node) {

    _nodeViews.put(node, nodeView);

    nodeView.setListener(new Gestures.NodeGestures(node, this));
    nodeView.setText(node.getText());
    nodeView.setLocation(node.getNodeLocation());
    nodeView.setExpanded(node.isExpanded());

    for (Node child : node.getChildren()) {
      setUpNodeView(nodeView.createChild(), child);
    }

    for (EmbeddedObject object : node.getObjects()) {
      addEmbeddedObjectView(node, object);
    }

  }

  /**
   * @param object
   */
  private void addEmbeddedObject(Node node, EmbeddedObject object) {
    node.addObject(object);
    addEmbeddedObjectView(node, object);
  }

  /**
   * @param node
   * @param object
   */
  private void addEmbeddedObjectView(final Node node,
      final EmbeddedObject object) {
    final NodeView nodeView = _nodeViews.get(node);
    final EmbeddedObjectView videoView = nodeView.createEmbeddedObject(
        object.getType(), object.getData());

    // who said Java doesn't have closures?
    videoView.setListener(new EmbeddedObjectView.Listener() {

      @Override
      public void onEmbeddedObjectViewRemove() {
        node.removeObject(object);
        nodeView.removeEmbeddedObject(videoView);
      }

      @Override
      public void onDataChanged(String newData) {
        object.setData(newData);
      }
    });
  }

  public void add() {
    doUndoableAction(new AddAction(null));
  }

  public void addLeft() {
    doUndoableAction(new AddAction(NodeLocation.LEFT));
  }

  public void addRight() {
    doUndoableAction(new AddAction(NodeLocation.RIGHT));
  }

  public void addUp() {
    doUndoableAction(new AddSiblingAction(true));
  }

  public void addDown() {
    doUndoableAction(new AddSiblingAction(false));
  }

  public void cut() {
    doUndoableAction(new CutAction());
  }

  public void deleteSelection() {
    doUndoableAction(new DeleteAction(_selection.getCurrentNode()));
  }

  public void deselect() {
    // user clicked on the working area and not on any particular widget;
    // deselect
    setCurrentNode(null);
  }

  public void insertChild(Node parentNode, Node newNode) {
    if (parentNode.getNodeLocation() != NodeLocation.ROOT) {
      newNode.setNodeLocation(parentNode.getNodeLocation());
    }

    parentNode.addChildNode(newNode);

    NodeView nodeView = _nodeViews.get(parentNode);

    NodeView childView = nodeView.createChild();
    setUpNodeView(childView, newNode);
  }

  public void load(final MindMapInfo map, boolean local) {
    Storage s = local ? getLocalStorage() : getCloudStorage();
    s.loadMindMap(map, new AsyncCallback<MindMap>() {

      @Override
      public void onFailure(Throwable caught) {

        if (caught instanceof InvocationException) {
          // connection to the server failed for some reason
          updateUserInfo(true);
        }

      }

      @Override
      public void onSuccess(MindMap result) {
        if (result != null) {

          setMindMap(result);
          _currentMapKey = map.getKey();
          _isDirty = false;
        } else
          throw new NullPointerException("result is null");
      }
    });
  }

  private Storage getLocalStorage() {
    if (_localStorage == null) {
      _localStorage = new LocalMapStorage();
    }
    return _localStorage;
  }

  private Storage getCloudStorage() {
    return _cloudStorage;
  }

  public void loadFromCloudWithDialog() {

    final MindMapSelectionView selectionView = _mindMapView
        .getMindMapSelectionView();

    selectionView.setListener(new MindMapSelectionView.Listener() {

      @Override
      public void mindMapChosen(MindMapInfo map, boolean local) {
        load(map, local);
      }

      @Override
      public void mindMapRemove(MindMapInfo map, boolean local) {
        remove(map, local);
      }
    });

    // show the selection (most probably a dialog) to the
    // user
    selectionView.askForCloudDocumentSelection();

    _cloudStorage.getStoredMaps(new AsyncCallback<List<MindMapInfo>>() {

      @Override
      public void onFailure(Throwable caught) {
        selectionView.setMindMaps(null);

        if (caught instanceof InvocationException) {
          // connection to the server failed for some reason
          updateUserInfo(true);
        }
      }

      @Override
      public void onSuccess(List<MindMapInfo> result) {
        selectionView.setMindMaps(result);
      }
    });
    _localStorage.getStoredMaps(new AsyncCallback<List<MindMapInfo>>() {

      @Override
      public void onFailure(Throwable caught) {
        selectionView.setLocalMindMaps(null);

      }

      @Override
      public void onSuccess(List<MindMapInfo> result) {
        selectionView.setLocalMindMaps(result);
      }
    });
  }

  protected void remove(MindMapInfo map, boolean local) {
    Storage s = local ? getLocalStorage() : getCloudStorage();
    s.remove(map, new AsyncCallback<Void>() {

      @Override
      public void onFailure(Throwable caught) {
        _mindMapView
            .showMessage("Oops! There was an error during deleting. Try again later.");
        if (caught instanceof InvocationException) {
          // connection to the server failed for some reason
          updateUserInfo();
        }
      }

      @Override
      public void onSuccess(Void result) {
        /* successfully is a funny word. */
        _mindMapView.showMessage("Removed successfully.");
      }
    });
  }

  public void paste() {
    doUndoableAction(new PasteAction());
  }

  public void saveToCloud() {
    if (_mindMap.getTitle() == null) {
      String title = _mindMapView.askForDocumentTitle();
      if (title == null)
        return;
      _mindMap.setTitle(title);
    }

    _cloudStorage.saveMindMap(_currentMapKey, _mindMap,
        new AsyncCallback<MindMapInfo>() {

          @Override
          public void onFailure(Throwable caught) {
            _mindMapView
                .showMessage("Oops! There was an error during saving. Try saving locally for now."
                    + caught);
            if (caught instanceof InvocationException) {
              // connection to the server failed for some reason
              updateUserInfo(true);
            }
          }

          @Override
          public void onSuccess(MindMapInfo result) {
            _currentMapKey = result.getKey();
            _mindMapView.showMessage("Succesfully saved.");
            _isDirty = false;
          }
        });
  }

  public void setCurrentNode(Node node) {
    setCurrentNode(node, false);
  }

  private void setCurrentNode(Node node, boolean enterTextMode) {
    Node oldCurrentNode = _selection.getCurrentNode();
    _mindMapView.holdLayout();
    // check if the new current node is different from the last one
    if (oldCurrentNode != node) {
      // update old current node ui
      if (oldCurrentNode != null) {
        _nodeViews.get(oldCurrentNode).setSelectionState(
            SelectionState.DESELECTED);
      }

      // update selection state
      _selection.setCurrentNode(node);
    }
    // update new current node ui
    if (node != null) {
      NodeView nodeView = _nodeViews.get(node);

      if (enterTextMode) {
        nodeView.setSelectionState(SelectionState.TEXT_EDITING);
      } else {
        nodeView.setSelectionState(SelectionState.CURRENT);
      }

      // TODO save the copy of actionoptionsimpl for future use
      _mindMapView.showActions(nodeView, new ActionOptionsImpl(node));
    } else {
      _mindMapView.hideActions();
    }
    _mindMapView.resumeLayout();
  }

  public void setMindMap(MindMap mindMap) {
    _currentMapKey = null;
    _mindMap = mindMap;
    generateView();
    updateUserInfo();
  }

  public void setNodeText(Node node, String newText) {
    node.setText(newText);
    _nodeViews.get(node).setText(newText);
  }

  public void toggleExpand() {
    doUndoableAction(new ExpandAction(_selection.getCurrentNode()));
  }

  public void undo() {
    _undoStack.undo();
  }

  /**
   * Opens the text editor for the given node. If node is null, opens text
   * editor for the current node.
   *
   * @param node
   */
  public void enterTextMode(Node node) {
    if (node == null)
      node = _selection.getCurrentNode();
    setCurrentNode(node, true);
  }

  public void exitTextMode() {
    setCurrentNode(_selection.getCurrentNode(), false);
  }

  public void saveLocal() {

    getLocalStorage().saveMindMap(null, _mindMap,
        new AsyncCallback<MindMapInfo>() {

          @Override
          public void onFailure(Throwable caught) {
            _mindMapView
                .showMessage("Oops! Local save was not successful. Check if local saving is supported!");
          }

          @Override
          public void onSuccess(MindMapInfo result) {
            _mindMapView
                .showMessage("Successfully saved on this device.");
            _isDirty = false;
          }
        });

  }

  public void newMindMap() {

    DeferredCommand.addCommand(new Command() {

      @Override
      public void execute() {
        final MindMap mm = new MindMap();
        _currentMapKey = null;
        mm.setTitle("New Untitled Mind Map");
        Node n = new Node();
        n.setText("New Mind Map");
        Node n1 = new Node();
        n1.setText("A bubble");
        n1.setNodeLocation(NodeLocation.LEFT);
        Node n2 = new Node();
        n2.setText("Another bubble");
        n2.setNodeLocation(NodeLocation.RIGHT);
        n.addChildNode(n1);
        n.addChildNode(n2);
        mm.setRootNode(n);
        setMindMap(mm);
        _isDirty = false;

      }
    });
  }

  public void setTitle(String title) {
    _mindMap.setTitle(title);
    _mindMapView.setTitle(title);
  }

  public void copy() {
    /* take note that copy (contrary to paste or cut) is not undoable*/
    Node n = new Node();
    _selection.getCurrentNode().copyTo(n);
    _clipboard.setCurrentNode(n);
  }

  public void navigateLeft() {
    navigateToSide(NodeLocation.LEFT);
  }

  public void navigateRight() {
    navigateToSide(NodeLocation.RIGHT);
  }

  /**
   * Change current node from previous one to one of the nodes on the left or
   * right. A node which is left or right to the current node is either one of
   * its children or its parent.
   *
   * @param where
   */
  private void navigateToSide(NodeLocation where) {
    Node n = _selection.getCurrentNode();
    if (n.getNodeLocation() == where) {
      // we are going deeper into children, f.e. moving left from a node
      // that is on the left
      if (n.getChildCount() <= 0)
        return;
      setCurrentNode(n.getChildren().iterator().next());

    } else if (n.getNodeLocation() == NodeLocation.ROOT) {
      // moving from the root, which has no parent and children in either
      // direction
      if (n.getChildCount() <= 0)
        return;
      for (Node child : n.getChildren()) {
        if (child.getNodeLocation() == where) {
          setCurrentNode(child);
          return;
        }
      }
    } else {
      // moving towards the parent (moving right on left-sided node)
      setCurrentNode(n.getParent());
    }

  }

  public void navigateUp() {
    Node n = _selection.getCurrentNode();
    if (n.getNodeLocation() == NodeLocation.ROOT)
      return;
    Node parent = n.getParent();
    int index = parent.getChildren().indexOf(n);
    if (index > 0) {

      // search siblings for the first sibling after current node that is
      // on the same side
      for (int i = index - 1; i >= 0; i--) {
        Node candidate = parent.getChildren().get(i);
        if (candidate.getNodeLocation() == n.getNodeLocation()) {
          setCurrentNode(candidate);
          return;
        }

      }
    }
  }

  public void navigateDown() {
    Node n = _selection.getCurrentNode();
    if (n.getNodeLocation() == NodeLocation.ROOT)
      return;
    Node parent = n.getParent();
    int index = parent.getChildren().indexOf(n);
    if (index < parent.getChildCount() - 1) {

      // search siblings for the first sibling after current node that is
      // on the same side
      for (int i = index + 1; i < parent.getChildCount(); i++) {
        Node candidate = parent.getChildren().get(i);
        if (candidate.getNodeLocation() == n.getNodeLocation()) {
          setCurrentNode(candidate);
          return;
        }

      }
    }
  }

  public void insertYouTubeVideo(String id) {
    EmbeddedObject video = new EmbeddedObject("youtube", id);
    addEmbeddedObject(_selection.getCurrentNode(), video);

  }

  public void setDragDropChild(Node node, int index, NodeLocation location) {
    _dragDropAction = new DragDropAction(node, index, location);
  }

  public void setDragDropParent(Node node) {
    _dragDropAction.setParent(node);
  }

  public void doDragDropAction() {
    doUndoableAction(_dragDropAction);
    _dragDropAction = null;
  }

  public void insertImage(String url) {
    EmbeddedObject image = new EmbeddedObject("image", url);
    addEmbeddedObject(_selection.getCurrentNode(), image);

  }

  public void insertMap() {
    EmbeddedObject map = new EmbeddedObject("map", "");
    addEmbeddedObject(_selection.getCurrentNode(), map);
  }

  public void insertRichText() {
    EmbeddedObject text = new EmbeddedObject("richtext", "type text here");
    addEmbeddedObject(_selection.getCurrentNode(), text);
  }

  public void showInsertMenu(int x, int y) {
    _mindMapView
        .showInsertMenu(x, y, _selection.getCurrentNode().getText());
  }

  public void showShareDialog() {
    final ShareOptionsView view = _mindMapView.showShareDialog();
    view.setListener(new ShareOptionsView.Listener() {

      @Override
      public void makePublicGesture() {
        setMapPublic(true);
        view.setLink(getMindNotesViewerURL());

      }

      @Override
      public void makeNotPublicGesture() {
        setMapPublic(false);

      }
    });
    if (_currentMapKey == null) {
      view.setMapNotUploaded();
      return;
    }
    _cloudStorage.getService().getMapPublic(_currentMapKey,
        new AsyncCallback<Boolean>() {

          @Override
          public void onFailure(Throwable caught) {
            _mindMapView
                .showMessage("Oops! There was an error during retrieving sharing preferences.");
            if (caught instanceof InvocationException) {
              // connection to the server failed for some reason
              updateUserInfo(true);
            }
          }

          @Override
          public void onSuccess(Boolean result) {
            view.setMapPublished(result);
            if (result) {
              view.setLink(getMindNotesViewerURL());

            }
          }
        });

  }

  public void setMapPublic(boolean b) {
    _cloudStorage.getService().setMapPublic(_currentMapKey, b,
        new AsyncCallback<Void>() {

          @Override
          public void onFailure(Throwable caught) {
            _mindMapView
                .showMessage("Oops! There was an error during setting sharing preferences.");
            if (caught instanceof InvocationException) {
              // connection to the server failed for some reason
              updateUserInfo(true);
            }
          }

          @Override
          public void onSuccess(Void result) {
            _mindMapView
                .showMessage("Sharing preferences changed.");
          }
        });
  }

  /**
   * @return
   */
  private String getMindNotesViewerURL() {
    if (GWT.isScript()) {
      return "http://mind-notes.appspot.com/MindNotesViewer.html?map="
          + _currentMapKey;
    } else {
      return "http://127.0.0.1:8888/MindNotesViewer.html?gwt.codesvr=127.0.0.1:9997&map="
          + _currentMapKey;
    }
  }

  public boolean isDirty() {
    return _isDirty;
  }

}
TOP

Related Classes of mindnotes.client.presentation.MindMapEditor

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.