/* * Copyright (C) 2014 たんらる */ package fourthline.mabiicco.ui.editor; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.StringReader; import java.util.Arrays; import java.util.Base64; import java.util.Stack; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import fourthline.mabiicco.IFileState; import fourthline.mabiicco.IFileStateObserver; import fourthline.mabiicco.ui.IMMLManager; import fourthline.mmlTools.MMLScore; public final class MMLScoreUndoEdit extends AbstractUndoableEdit implements IFileState { private static final long serialVersionUID = 4093930608712571204L; private IFileStateObserver fileStateObserver = null; private static final int MAX_UNDO = 20; private final Stack<byte[]> undoState = new Stack<>(); private final Stack<byte[]> redoState = new Stack<>(); private final IMMLManager mmlManager; private int originalIndex = 0; /** オリジナル位置. undo/redo範囲外になった場合は 負値. 0~size-1 */ private String backupString = null; public MMLScoreUndoEdit(IMMLManager mmlManager) { this.mmlManager = mmlManager; } public void initState() { undoState.clear(); redoState.clear(); originalIndex = 0; saveState(); } @Override public void saveState() { MMLScore score = mmlManager.getMMLScore(); byte state[] = score.getObjectState(); if ( !undoState.empty() && Arrays.equals(state, undoState.lastElement()) ) { return; } undoState.push(state); redoState.clear(); if (undoState.size() > MAX_UNDO) { undoState.remove(0); originalIndex = -1; } if (fileStateObserver != null) fileStateObserver.notifyUpdateFileState(); System.out.println("saveState() "+undoState.size()); makeBackup(); } @Override public void revertState() { MMLScore score = mmlManager.getMMLScore(); score.putObjectState(undoState.lastElement()); } @Override public void undo() throws CannotUndoException { super.undo(); MMLScore score = mmlManager.getMMLScore(); if (canUndo()) { byte nextState[] = undoState.pop(); score.putObjectState(undoState.lastElement()); redoState.push(nextState); makeBackup(); if (fileStateObserver != null) fileStateObserver.notifyUpdateFileState(); } } @Override public void redo() throws CannotRedoException { super.redo(); MMLScore score = mmlManager.getMMLScore(); if (canRedo()) { byte state[] = redoState.pop(); score.putObjectState(state); undoState.push(state); makeBackup(); if (fileStateObserver != null) fileStateObserver.notifyUpdateFileState(); } } @Override public boolean canUndo() { if (undoState.size() > 1) { return true; } return false; } @Override public boolean canRedo() { return (!redoState.empty()); } @Override public boolean isModified() { if ( originalIndex == (undoState.size()-1) ) { return false; } return true; } @Override public void setOriginalBase() { originalIndex = undoState.size() - 1; } @Override public void setFileStateObserver(IFileStateObserver observer) { this.fileStateObserver = observer; } private void makeBackup() { try { backupString = compress(makeBackupString()); } catch (IOException e) { backupString = null; } } private void writeStack(PrintStream out, Stack<byte[]> data) throws IOException { out.println(data.size()); for (int i = 0; i < data.size(); i++) { out.println( Base64.getEncoder().encodeToString( data.get(i) )); } } private void readStack(BufferedReader in, Stack<byte[]> data) throws IOException { data.clear(); int count = Integer.parseInt(in.readLine()); for (int i = 0; i < count; i++) { data.add( Base64.getDecoder().decode( in.readLine() )); } } private String makeBackupString() throws IOException { ByteArrayOutputStream bstream = new ByteArrayOutputStream(); PrintStream pstream = new PrintStream(bstream, false, "UTF-8"); pstream.println(serialVersionUID); // undoState@Stack<byte[]> writeStack(pstream, undoState); // redoState@Stack<byte[]> writeStack(pstream, redoState); // originalIndex@int pstream.println(originalIndex); pstream.close(); return new String( bstream.toByteArray() ); } private boolean parseBackupString(String s) throws IOException, NumberFormatException { BufferedReader breader = new BufferedReader(new StringReader(s)); long serial = Long.parseLong( breader.readLine() ); if (serial != serialVersionUID) { return false; } // undoState@Stack<byte[]> readStack(breader, undoState); // redoState@Stack<byte[]> readStack(breader, redoState); // originalIndex@int originalIndex = Integer.parseInt( breader.readLine() ); return true; } private String compress(String s) { try { ByteArrayOutputStream bstream = new ByteArrayOutputStream(); GZIPOutputStream out = new GZIPOutputStream( bstream ); out.write(s.getBytes()); out.close(); bstream.close(); return Base64.getEncoder().encodeToString(bstream.toByteArray()); } catch (IOException e) { System.out.println(e.getMessage()); } return null; } private String decompress(String s) { try { GZIPInputStream in = new GZIPInputStream( new ByteArrayInputStream( Base64.getDecoder().decode(s.getBytes()))); ByteArrayOutputStream bstream = new ByteArrayOutputStream(); while (in.available() != 0) { int c = in.read(); if (c >= 0) { bstream.write(c); } } bstream.close(); return new String(bstream.toByteArray()); } catch (IOException e) { System.out.println(e.getMessage()); } return null; } public boolean recover(String s) { try { boolean result = parseBackupString(decompress(s)); makeBackup(); return result; } catch (NumberFormatException | IOException e) { System.out.println(e.getMessage()); } return false; } public String getBackupString() { return backupString; } }