Package edu.isi.karma.controller.history

Source Code of edu.isi.karma.controller.history.CommandHistory$RedoCommandObject

/*******************************************************************************
* Copyright 2012 University of Southern California
*
* 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.
*
* This code was developed by the Information Integration Group as part
* of the Karma project at the Information Sciences Institute of the
* University of Southern California.  For more information, publications,
* and related projects, please see: http://www.isi.edu/integration
******************************************************************************/
/**
*
*/
package edu.isi.karma.controller.history;

import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.isi.karma.controller.command.Command;
import edu.isi.karma.controller.command.CommandException;
import edu.isi.karma.controller.command.CommandFactory;
import edu.isi.karma.controller.command.CommandType;
import edu.isi.karma.controller.command.ICommand;
import edu.isi.karma.controller.command.ICommand.CommandTag;
import edu.isi.karma.controller.history.HistoryJsonUtil.ClientJsonKeys;
import edu.isi.karma.controller.history.HistoryJsonUtil.ParameterType;
import edu.isi.karma.controller.update.HistoryAddCommandUpdate;
import edu.isi.karma.controller.update.HistoryUpdate;
import edu.isi.karma.controller.update.UpdateContainer;
import edu.isi.karma.rep.HNode;
import edu.isi.karma.rep.Workspace;
import edu.isi.karma.util.JSONUtil;
import edu.isi.karma.view.VWorkspace;
import edu.isi.karma.webserver.ExecutionController;
import edu.isi.karma.webserver.WorkspaceRegistry;

/**
* @author szekely
*
*/
public class CommandHistory {

  private class RedoCommandObject {
    private ICommand command;
    private JSONObject historyObject;
    RedoCommandObject(ICommand command, JSONObject historyObject) {
      this.command = command;
      this.historyObject = historyObject;
    }
  }
 
  private final List<ICommand> history = new ArrayList<ICommand>();
  private Command previewCommand;
  private final List<RedoCommandObject> redoStack = new ArrayList<RedoCommandObject>();
  /**
   * If the last command was undo, and then we do a command that goes on the
   * history, then we need to send the browser the full history BEFORE we send
   * the update for the command the user just did. The reason is that the
   * history may contain undoable commands, and the browser does not know how
   * to reset the history.
   */
  private boolean lastCommandWasUndo = false;

  /**
   * Used to keep a pointer to the command which require user-interaction
   * through multiple HTTP requests.
   */

  private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

  private static HashMap<String, IHistorySaver> historySavers = new HashMap<>();

  public enum HistoryArguments {
    worksheetId, commandName, inputParameters, hNodeId, tags
  }

  public CommandHistory() {

  }

  public CommandHistory(List<ICommand> history, List<RedoCommandObject> redoStack) {
    for (ICommand c : history) {
      this.history.add(c);
    }
    for (RedoCommandObject c : redoStack) {
      this.redoStack.add(c);
    }
  }

  public boolean isUndoEnabled() {
    return !history.isEmpty();
  }

  public boolean isRedoEnabled() {
    return !redoStack.isEmpty();
  }

  public List<ICommand> _getHistory() {
    return history;
  }

  public List<ICommand> _getRedoStack() {
    List<ICommand> commands = new ArrayList<ICommand>();
    for (RedoCommandObject obj : redoStack) {
      commands.add(obj.command);
    }
    return commands;
  }

  public CommandHistory clone() {
    return new CommandHistory(history, redoStack);
  }

  /**
   * Commands go onto the history when they have all their arguments and are
   * ready to be executed. If a command needs multiple user interactions to
   * define the parameters, then the command object must be created,
   * interacted with and then executed.
   *
   * @param command
   * @param workspace
   * @return UpdateContainer with all the changes done by the command.
   * @throws CommandException
   */
  public UpdateContainer doCommand(Command command, Workspace workspace)
      throws CommandException {
    return doCommand(command, workspace, true);
  }

  public UpdateContainer doCommand(Command command, Workspace workspace, boolean saveToHistory)
      throws CommandException {
    UpdateContainer effects = new UpdateContainer();
    effects.append(command.doIt(workspace));
    command.setExecuted(true);


    if (command.getCommandType() != CommandType.notInHistory) {
      redoStack.clear();

      // Send the history before the command we just executed.
      if (lastCommandWasUndo && !(instanceOf(command, "UndoRedoCommand"))) {
        effects.append(new UpdateContainer(new HistoryUpdate(this)));
      }
      lastCommandWasUndo = false;

      history.add(command);
      effects.add(new HistoryAddCommandUpdate(command));
    }

    if(saveToHistory) {
      // Save the modeling commands
      if (!(instanceOf(command, "ResetKarmaCommand"))) {
        try {
          if(isHistoryWriteEnabled && historySavers.get(workspace.getId()) != null) {
            writeHistoryPerWorksheet(workspace, historySavers.get(workspace.getId()));
          }
        } catch (Exception e) {
          logger.error("Error occured while writing history!" , e);
          e.printStackTrace();
        }
      }
    }
    return effects;
  }

  private void writeHistoryPerWorksheet(Workspace workspace, IHistorySaver historySaver) throws Exception {
    String workspaceId = workspace.getId();
    HashMap<String, JSONArray> comMap = new HashMap<String, JSONArray>();
    for(ICommand command : history) {
      if(command.isSavedInHistory() && (command.hasTag(CommandTag.Modeling)
          || command.hasTag(CommandTag.Transformation))) {
        JSONArray json = new JSONArray(command.getInputParameterJson());
        String worksheetId = HistoryJsonUtil.getStringValue(HistoryArguments.worksheetId.name(), json);
        if(workspace.getWorksheet(worksheetId) != null)
        {
          if(comMap.get(worksheetId) == null)
            comMap.put(worksheetId, new JSONArray());
          comMap.get(worksheetId).put(getCommandJSON(workspace, command));
        }
      }
    }

    for(String worksheetId : comMap.keySet()) {
      JSONArray comms = comMap.get(worksheetId);
      historySaver.saveHistory(workspaceId, worksheetId, comms);
    }
  }


  public JSONObject getCommandJSON(Workspace workspace, ICommand comm) {
    JSONObject commObj = new JSONObject();
    commObj.put(HistoryArguments.commandName.name(), comm.getCommandName());

    // Populate the tags
    JSONArray tagsArr = new JSONArray();
    for (CommandTag tag : comm.getTags())
      tagsArr.put(tag.name());
    commObj.put(HistoryArguments.tags.name(), tagsArr);

    JSONArray inputArr = new JSONArray(comm.getInputParameterJson());
    for (int i = 0; i < inputArr.length(); i++) {
      JSONObject inpP = inputArr.getJSONObject(i);

      /*** Check the input parameter type and accordingly make changes ***/
      if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.hNodeIdList) {
        JSONArray hnodes = (JSONArray) JSONUtil.createJson(inpP.getString(ClientJsonKeys.value.name()));
        for (int j = 0; j < hnodes.length(); j++) {
          JSONObject obj = (JSONObject)hnodes.get(j);
          Object value = obj.get(ClientJsonKeys.value.name());
          if (value instanceof String) {
            String hNodeId = (String) value;
            HNode node = workspace.getFactory().getHNode(hNodeId);
            JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
            obj.put(ClientJsonKeys.value.name(), hNodeRepresentation);
          }
        }
        inpP.put(ClientJsonKeys.value.name(), hnodes);
      } else if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.orderedColumns) {
        Object tmp = inpP.get(ClientJsonKeys.value.name());
        JSONArray hnodes = (JSONArray) JSONUtil.createJson(tmp.toString());
        for (int j = 0; j < hnodes.length(); j++) {
          JSONObject obj = (JSONObject)hnodes.get(j);
          String hNodeId = obj.getString(ClientJsonKeys.id.name());
          HNode node = workspace.getFactory().getHNode(hNodeId);
          JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
          obj.put(ClientJsonKeys.id.name(), hNodeRepresentation);
          if (obj.has(ClientJsonKeys.children.name()))
            obj.put(ClientJsonKeys.children.name(), parseChildren(obj.get(ClientJsonKeys.children.name()).toString(), workspace));
        }
        inpP.put(ClientJsonKeys.value.name(), hnodes);
      } else if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.hNodeId) {
        String hNodeId = inpP.getString(ClientJsonKeys.value.name());
        HNode node = workspace.getFactory().getHNode(hNodeId);
        JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
        inpP.put(ClientJsonKeys.value.name(), hNodeRepresentation);

      } else if (HistoryJsonUtil.getParameterType(inpP) == ParameterType.worksheetId) {
        inpP.put(ClientJsonKeys.value.name(), "W");
      } else {
        // do nothing
      }
    }
    if (comm instanceof Command) {
      Command tmp = (Command)comm;
      JSONArray inputArray = new JSONArray();
      for (String hNodeId : tmp.getInputColumns()) {
        HNode node = workspace.getFactory().getHNode(hNodeId);
        JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
        JSONObject obj1 = new JSONObject();
        obj1.put(ClientJsonKeys.value.name(), hNodeRepresentation);
        inputArray.put(obj1);
      }
      JSONObject obj = HistoryJsonUtil.getJSONObjectWithName("inputColumns", inputArr);     
      if (obj == null) {
        obj = new JSONObject();
        obj.put(ClientJsonKeys.name.name(), "inputColumns");
        obj.put(ClientJsonKeys.value.name(), inputArray.toString());
        obj.put(ClientJsonKeys.type.name(), ParameterType.hNodeIdList.name());
        inputArr.put(obj);
      }
      else {
        obj.put(ClientJsonKeys.value.name(), inputArray.toString());
      }

      JSONArray outputArray = new JSONArray();
      for (String hNodeId : tmp.getOutputColumns()) {
        HNode node = workspace.getFactory().getHNode(hNodeId);
        JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
        JSONObject obj1 = new JSONObject();
        obj1.put(ClientJsonKeys.value.name(), hNodeRepresentation);
        outputArray.put(obj1);
      }
      obj = HistoryJsonUtil.getJSONObjectWithName("outputColumns", inputArr);           
      if (obj == null) {
        obj = new JSONObject();
        obj.put(ClientJsonKeys.name.name(), "outputColumns");
        obj.put(ClientJsonKeys.value.name(), outputArray.toString());
        obj.put(ClientJsonKeys.type.name(), ParameterType.hNodeIdList.name());
        inputArr.put(obj);
      }
      else {
        obj.put(ClientJsonKeys.value.name(), outputArray.toString());
      }
    }
    commObj.put(HistoryArguments.inputParameters.name(), inputArr);

    return commObj;
  }

  private boolean instanceOf(Object o, String className) { // TODO this is a hack, but instanceof doesn't really seem appropriate here
    return o.getClass().getName().toLowerCase().contains(className.toLowerCase());
  }

  private String parseChildren(String inputJSON, Workspace workspace) {
    JSONArray array = (JSONArray) JSONUtil.createJson(inputJSON);
    for (int i = 0; i < array.length(); i++) {
      JSONObject obj = array.getJSONObject(i);
      String hNodeId = obj.getString(ClientJsonKeys.id.name());
      HNode node = workspace.getFactory().getHNode(hNodeId);
      JSONArray hNodeRepresentation = node.getJSONArrayRepresentation(workspace.getFactory());
      obj.put(ClientJsonKeys.id.name(), hNodeRepresentation);
      if (obj.has(ClientJsonKeys.children.name()))
        obj.put(ClientJsonKeys.children.name(), parseChildren(obj.get(ClientJsonKeys.children.name()).toString(), workspace));
    }
    return array.toString();
  }

  /**
   * @param workspace
   * @param commandId
   *            is the id of a command that should be either in the undo or
   *            redo histories. If it is in none, then nothing will be done.
   * @return the effects of the undone or redone commands.
   * @throws CommandException
   */
  public UpdateContainer undoOrRedoCommandsUntil(Workspace workspace,
      String commandId) throws CommandException {
    List<ICommand> commandsToUndo = getCommandsUntil(history, commandId);
    if (!commandsToUndo.isEmpty()) {
      lastCommandWasUndo = true;
      return undoCommands(workspace, commandsToUndo);
    } else {
      List<RedoCommandObject> commandsToRedo = getCommandsUntil(commandId);
      if (!commandsToRedo.isEmpty()) {
        return redoCommands(workspace, commandsToRedo);
      } else {
        return new UpdateContainer();
      }
    }
  }

  /**
   * Undo all commands in the given list.
   *
   * @param workspace
   * @param commandsToUndo
   *
   * @return UpdateContainer with the effects of all undone commands.
   */
  private UpdateContainer undoCommands(Workspace workspace,
      List<ICommand> commandsToUndo) {

    UpdateContainer effects = new UpdateContainer();
    for (ICommand c : commandsToUndo) {
      history.remove(c);
      redoStack.add(new RedoCommandObject(c, getCommandJSON(workspace, c)));
      effects.append(c.undoIt(workspace));
    }
    return effects;
  }

  /**
   * Redo all the commands in the given list.
   *
   * @param workspace
   * @param commandsToRedo
   * @return
   * @throws CommandException
   */
  private UpdateContainer redoCommands(Workspace workspace,
      List<RedoCommandObject> commandsToRedo) throws CommandException {
    if (!redoStack.isEmpty()) {

      UpdateContainer effects = new UpdateContainer();
      Iterator<RedoCommandObject> itr = commandsToRedo.iterator();
      while (itr.hasNext()) {
        RedoCommandObject rco = itr.next();
        redoStack.remove(rco);
        try {
          JSONArray array = new JSONArray(rco.command.getInputParameterJson());
          String worksheetId = HistoryJsonUtil.getStringValue(HistoryArguments.worksheetId.name(), array);
          WorksheetCommandHistoryExecutor executor = new WorksheetCommandHistoryExecutor(worksheetId, workspace);
          ExecutionController ctrl = WorkspaceRegistry.getInstance().getExecutionController(workspace.getId());
          HashMap<String, CommandFactory> commandFactoryMap = ctrl.getCommandFactoryMap();
          JSONArray inputParamArr = (JSONArray)rco.historyObject.get(HistoryArguments.inputParameters.name());
          String commandName = (String)rco.historyObject.get(HistoryArguments.commandName.name());
          executor.normalizeCommandHistoryJsonInput(workspace, worksheetId, inputParamArr, commandName, true);
          CommandFactory cf = commandFactoryMap.get(rco.historyObject.get(HistoryArguments.commandName.name()));
          Command comm = cf.createCommand(inputParamArr, workspace);
          effects.append(comm.doIt(workspace));
          history.add(comm);
        }catch(Exception e) {
          history.add(rco.command);
          effects.append(rco.command.doIt(workspace));
        }
       
      }
      return effects;
    } else {
      return new UpdateContainer();
    }
  }

  /**
   * @param commands
   *            , a list of commands.
   * @param commandId
   *            , the id of a command.
   * @return the sublist of commands from the start until and including a
   *         command with the given id. If the command with the given id is
   *         not in the list, return the empty list.
   */
  private List<ICommand> getCommandsUntil(List<ICommand> commands,
      String commandId) {
    if (commands.isEmpty()) {
      return Collections.emptyList();
    }
    List<ICommand> result = new LinkedList<ICommand>();
    boolean foundCommand = false;
    for (int i = commands.size() - 1; i >= 0; i--) {
      ICommand c = commands.get(i);
      if (c.getCommandType() == CommandType.undoable) {
        result.add(c);
      }
      if (c.getId().equals(commandId)) {
        foundCommand = true;
        break;
      }
    }
    if (foundCommand) {
      return result;
    } else {
      return Collections.emptyList();
    }
  }
 
  private List<RedoCommandObject> getCommandsUntil(String commandId) {
    if (redoStack.isEmpty()) {
      return Collections.emptyList();
    }
    List<RedoCommandObject> result = new LinkedList<RedoCommandObject>();
    boolean foundCommand = false;
    for (int i = redoStack.size() - 1; i >= 0; i--) {
      RedoCommandObject c = redoStack.get(i);
      if (c.command.getCommandType() == CommandType.undoable) {
        result.add(c);
      }
      if (c.command.getId().equals(commandId)) {
        foundCommand = true;
        break;
      }
    }
    if (foundCommand) {
      return result;
    } else {
      return Collections.emptyList();
    }
  }

  public void generateFullHistoryJson(String prefix, PrintWriter pw,
      VWorkspace vWorkspace) {
    Iterator<ICommand> histIt = history.iterator();
    while (histIt.hasNext()) {
      histIt.next().generateJson(prefix, pw, vWorkspace,
          Command.HistoryType.undo);
      if (histIt.hasNext() || isRedoEnabled()) {
        pw.println(prefix + ",");
      }
    }

    for(int i = redoStack.size()-1; i>=0; i--) {
      ICommand redoComm = redoStack.get(i).command;
      redoComm.generateJson(prefix, pw, vWorkspace,
          Command.HistoryType.redo);
      if(i != 0)
        pw.println(prefix + ",");
    }
  }


  public void removeCommands(Workspace workspace, String worksheetId) {
    List<ICommand> commandsToBeRemoved = new ArrayList<ICommand>();
    ListIterator<ICommand> commandItr = history.listIterator(history.size());
    while(commandItr.hasPrevious()) {
      ICommand command = commandItr.previous();
      if(command instanceof Command && command.isSavedInHistory() && (command.hasTag(CommandTag.Modeling)
          || command.hasTag(CommandTag.Transformation))) {
        JSONArray json = new JSONArray(command.getInputParameterJson());
        if (HistoryJsonUtil.getStringValue(HistoryArguments.worksheetId.name(), json).equals(worksheetId)) {
          commandsToBeRemoved.add(command);
          Command tmp = (Command)command;
          if (tmp.getCommandType() == CommandType.undoable) {
            try {
              tmp.undoIt(workspace);
            }catch(Exception e) {

            }
          }
        }
      }
    }
    history.removeAll(commandsToBeRemoved);
  }

  public void removeCommands(String worksheetId) {
    List<ICommand> commandsFromWorksheet = new ArrayList<ICommand>();
    for(ICommand command: history) {
      try {

        Method m = command.getClass().getMethod("getWorksheetId");
        if(m != null)
        {
          String id = (String) m.invoke(command, (Object[])null);
          if (id.equals(worksheetId))
            commandsFromWorksheet.add(command);

        }
      } catch (Exception e) {
        logger.error("Unable to remove command " + command.getCommandName());
      }
    }
    history.removeAll(commandsFromWorksheet);
  }

  public List<Command> getCommandsFromWorksheetId(String worksheetId) {
    List<Command> commandsFromWorksheet = new ArrayList<Command>();
    for(ICommand command: history) {
      if(command instanceof Command && command.isSavedInHistory() && (command.hasTag(CommandTag.Modeling)
          || command.hasTag(CommandTag.Transformation))) {
        JSONArray json = new JSONArray(command.getInputParameterJson());
        if (HistoryJsonUtil.getStringValue(HistoryArguments.worksheetId.name(), json).equals(worksheetId))
          commandsFromWorksheet.add((Command)command);
      }

    }
    return commandsFromWorksheet;
  }

  public ICommand getCommand(String commandId)
  {
    for(ICommand  c: this.history)
    {
      if(c.getId().equals(commandId))
      {
        return c;
      }
    }
    return null;

  }

  public List<ICommand> getCommands(CommandTag tag) {
    List<ICommand> retCommands = new ArrayList<ICommand>();
    for(ICommand command: history) {
      if(command.hasTag(tag))
        retCommands.add(command);
    }
    return retCommands;
  }

  public void addPreviewCommand(Command c) {
    previewCommand = c;
  }

  public Command getPreviewCommand(String commandId) {
    if (previewCommand.getId().equals(commandId))
      return previewCommand;
    return null;
  }

  private static boolean isHistoryWriteEnabled = false;
  public static boolean isHistoryEnabled()
  {
    return isHistoryWriteEnabled;
  }

  public static void setIsHistoryEnabled(boolean isHistoryWriteEnabled)
  {
    CommandHistory.isHistoryWriteEnabled = isHistoryWriteEnabled;
  }

  public static void setHistorySaver(String workspaceId, IHistorySaver saver) {
    historySavers.put(workspaceId, saver);
  }

  public static IHistorySaver getHistorySaver(String workspaceId) {
    return historySavers.get(workspaceId);
  }
}
TOP

Related Classes of edu.isi.karma.controller.history.CommandHistory$RedoCommandObject

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.