/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on 12/10/2005 */ package org.python.pydev.core; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.python.pydev.core.log.Log; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.string.FastStringBuffer; /** * This class can be used to work on deltas. It is able to save and restore data on a 'delta' fashion. * * It is supposed to be used in the following way: * * When a class needs to save a lot of data in the disk and this data is gathered in deltas, it only does the first large * processing and then uses this class to keep track of other changes (and from time to time, reorganizes all the data and * erases the deltas). * * This object is supposed to be used by another that knows what is the data being added and how to restore that data. * Also, the order in which the deltas are generated is important. * * @author Fabio */ public class DeltaSaver<X> { /** * Superclass of all commands * * @author Fabio */ public abstract class DeltaCommand { public final X data; public DeltaCommand(X o) { this.data = o; } public abstract void processWith(IDeltaProcessor<X> deltaProcessor); /** * @return String with 3 letters describing the command (written to disk and used to restore it later on). */ public abstract String getCommandFileDesc(); } /** * Delete command * * @author Fabio */ public class DeltaDeleteCommand extends DeltaCommand { public DeltaDeleteCommand(X o) { super(o); } @Override public void processWith(IDeltaProcessor<X> deltaProcessor) { deltaProcessor.processDelete(data); } @Override public String getCommandFileDesc() { return "DEL"; } } /** * Insert command * * @author Fabio */ public class DeltaInsertCommand extends DeltaCommand { public DeltaInsertCommand(X o) { super(o); } @Override public void processWith(IDeltaProcessor<X> deltaProcessor) { deltaProcessor.processInsert(data); } @Override public String getCommandFileDesc() { return "INS"; } } /** * Update command * * @author Fabio */ public class DeltaUpdateCommand extends DeltaCommand { public DeltaUpdateCommand(X o) { super(o); } @Override public void processWith(IDeltaProcessor<X> deltaProcessor) { deltaProcessor.processUpdate(data); } @Override public String getCommandFileDesc() { return "UPD"; } } /** * Directory where the deltas should be saved / restored. */ private File dirToSaveDeltas; /** * This is equal to '.'+extension */ private String suffix; /** * List of commands */ private final List<DeltaCommand> commands; private final Object commandsLock = new Object(); /** * Used to keep track of a number to use to save the command */ private int nCommands; /** * This is the method that should read the data in the delta from a file... */ private ICallback<X, String> readFromFileMethod; /** * Convert the object to a representation to be put on the disk (readFromFileMethod will be used with the same * data passed here later on... if it cannot be read, null should be returned). */ private ICallback<String, X> toFileMethod; /** * @param dirToSaveDeltas this is the directory where the deltas should be saved * @param extension this is the extension that should be given to the deltas */ public DeltaSaver(File dirToSaveDeltas, String extension, ICallback<X, String> readFromFileMethod, ICallback<String, X> toFileMethod) { this.dirToSaveDeltas = dirToSaveDeltas; this.suffix = "." + extension; this.commands = new ArrayList<DeltaCommand>(); this.readFromFileMethod = readFromFileMethod; this.toFileMethod = toFileMethod; validateDir(); loadDeltas(); } /** * Checks if the dir is correct */ private void validateDir() { if (this.dirToSaveDeltas.exists() == false) { throw new RuntimeException("The path passed to save / restore deltas does not exist (" + dirToSaveDeltas + ")"); } if (this.dirToSaveDeltas.isDirectory() == false) { throw new RuntimeException("The path passed to save / restore deltas is not actually a directory (" + dirToSaveDeltas + ")"); } } /** * Gets existing deltas in the disk */ private void loadDeltas() { synchronized (this.commandsLock) { ArrayList<File> deltasFound = findDeltas(); for (File file : deltasFound) { try { @SuppressWarnings("unchecked") DeltaCommand cmd = readFromFile(file, this.readFromFileMethod); if (cmd != null && cmd.data != null) { addRestoredCommand(cmd); } } catch (Exception e) { Log.log(e); } } } } /** * @return a list of files with all the deltas in the dir we are acting upon */ private ArrayList<File> findDeltas() { ArrayList<File> deltasFound = new ArrayList<File>(); File[] files = this.dirToSaveDeltas.listFiles(); if (files != null) { for (File file : files) { if (file.isFile() && file.getName().endsWith(suffix)) { deltasFound.add(file); } } } //also, sort by the name (which must be an integer) Collections.sort(deltasFound, new Comparator<File>() { @Override public int compare(File o1, File o2) { String i = FullRepIterable.headAndTail(o1.getName())[0]; String j = FullRepIterable.headAndTail(o2.getName())[0]; return Integer.compare(Integer.parseInt(i), Integer.parseInt(j)); } }); return deltasFound; } /** * Adds some command (adds to list and NOT to the disk) * * @param command the command found in the disk */ private void addRestoredCommand(DeltaCommand command) { synchronized (this.commandsLock) { this.commands.add(command); } } /** * Adds some command (adds to list and to the disk) * * @param command the command to be added */ public void addCommand(final DeltaCommand command) { synchronized (this.commandsLock) { final File file = new File(this.dirToSaveDeltas, nCommands + suffix); nCommands++; String write = toFileMethod.call(command.data); if (write == null) { Log.log("Null returned to write from data: " + command.data); } else { FastStringBuffer buf = new FastStringBuffer(command.getCommandFileDesc(), write.length()); buf.append(write); FileUtils.writeStrToFile(buf.toString(), file); this.commands.add(command); } } } /** * @return the number of available deltas */ public int availableDeltas() { synchronized (this.commandsLock) { return this.commands.size(); } } /** * Clears all deltas in the disk (and in memory... also restarts numbering the deltas) */ public void clearAll() { synchronized (this.commandsLock) { ArrayList<File> deltas = findDeltas(); for (File file : deltas) { try { file.delete(); } catch (Exception e) { Log.log(e); } } this.commands.clear(); nCommands = 0; } } public void addInsertCommand(X o) { addCommand(new DeltaInsertCommand(o)); } public void addDeleteCommand(X o) { addCommand(new DeltaDeleteCommand(o)); } public void addUpdateCommand(X o) { addCommand(new DeltaUpdateCommand(o)); } /** * Passes the current deltas to the delta processor. */ public synchronized void processDeltas(IDeltaProcessor<X> deltaProcessor) { synchronized (this.commandsLock) { ArrayList<DeltaCommand> commandsToProcess = new ArrayList<DeltaCommand>(this.commands); boolean processed = false; for (DeltaCommand cmd : commandsToProcess) { try { cmd.processWith(deltaProcessor); processed = true; } catch (Exception e) { Log.log(e); } } if (processed) { //if nothing happened, we don't end the processing (no need to do it) deltaProcessor.endProcessing(); } this.clearAll(); } } @SuppressWarnings({ "rawtypes", "unchecked" }) public DeltaSaver.DeltaCommand readFromFile(File astOutputFile, ICallback<X, String> readFromFileMethod) { try { boolean deletFile = false; //the file is not even there if (!astOutputFile.exists()) { return null; } String fileContents = FileUtils.getFileContents(astOutputFile); DeltaSaver.DeltaCommand o = null; try { if (fileContents.startsWith("UPD")) { o = new DeltaSaver.DeltaUpdateCommand(readFromFileMethod.call(fileContents.substring(3))); } else if (fileContents.startsWith("DEL")) { o = new DeltaSaver.DeltaDeleteCommand(readFromFileMethod.call(fileContents.substring(3))); } else if (fileContents.startsWith("INS")) { o = new DeltaSaver.DeltaInsertCommand(readFromFileMethod.call(fileContents.substring(3))); } } catch (Exception e) { //the format has changed (no real problem here... just erase the file) deletFile = true; o = null; } if (deletFile) { if (astOutputFile.exists()) { astOutputFile.delete(); } } return o; } catch (Exception e) { Log.log(e); return null; } } }