package com.treelight.krnl; import java.net.URL; import java.util.*; /** * Maintain and update a list of unstamped versions. * Note: All methods are static. This class cannot be * instantiated. *

* ___TODO:
* The methods in this class need to be thread-safe, so that * no new nodes are added while version-stamping is going on. * * @version 0.1 * @author Eric Armstrong * @see ../NodesAndLists.html */ public class VersionControl { // U.S. Naval Observatory is the default time server //SNTP = Synchronous Network Time Protocol // (keeps the time on remote systems reasonably synchronized) private static final URL defaultServer = new URL("sntp://tick.usno.navy.mil"); private static URL timeServer; private static UserID userID; // Identifies the author of the current version private static SystemID systemID; // Identifies the system the version was created on public String getUserID() { return userID; } public String getSystemID() { return systemID; } /** * This list provides a place to record all versionStamps that have * been created, but not time stamped. */ static Set unstampedNodeSet = new HashSet(); /** * The list of all unstamped versions. Versions are inserted at the * front of this list, so that the list iterator retreives the most * recent first. *

* Note that we don't want to use the AbstractList structure for this * purpose, since that class supports versioning! Here, we need normal * lists that we can manage without having to worry about recursively * versioning a version of the version list version -- or words to that * effect. */ static LinkedList unstampedVersions = new LinkedList(); /** * Return a list iterator on the set of unstamped versions. * The outer system can use that iterator to perform undo operations, * removing items from it and moving them to a redo list as the * user steps through the chain of undoable changes. */ public ListIterator unstampedVersionsIterator() { return unstampedVersions.listIterator(); } static { // ___TODO: // Determine the globally unique ID of the user's system. // this.systemID = ...; // ___TODO: // Set up the user ID, if not already stored in the system. // this.userID = new UserID(...); // ??????????????????? (may not be needed, given userID + systemID) // IF: we've previously stored the last node ID value // for the user's system, retrieve it now. // ___TODO___ // ELSE: the value has not already been stored. Examine // all nodes in the system and find the last ID that // matches the user system's ID. Save the value in // persistent storage for faster startup next time. // ___TODO___ // ??????????????????? } /** * Configure the time server. */ public static void configureServer(URL timeServer) { this.timeServer = timeServer; } /** * Add an entry to the list of unstamped versions. * * If the node already exists on the list, it does not * not need to be added again, because the node itself * contains a list of its previous versions. *

* Synchronize on the set of unstamped nodes, to ensure * that some clever multitasking system isn't creating * new nodes and timestamping unstamped nodes at the * same time. */ public static void addUnstampedVersion(VersionStamp versionStamp) { syncrhonize(unstampedNodeSet) { // Add the node to a set, so it only exists once. this.unstampedNodeSet.add(versionStamp.getNode()); // Insert the version node at the head of the unstamped list this.unstampedVersions.insert(0, versionStamp); } } /** * Use the currently configured timeServer, or the default server if none * is specified, to set the time. It is expected that offline systems * will create versionStamps without times, and keep a list of nodes that * have been created (in some atomic manner, of course). This function * will then be invoked on each entry on that list when the user goes * online to synchronize with other systems. *

* Synchronize on the set of unstamped nodes, to ensure * that some clever multitasking system isn't creating * new nodes and timestamping unstamped nodes at the * same time. *

* ___TODO: * For each node on the unstampedVersions list, collapse the differences * down to a minimal set. For example, if one list item changed position * 3 times, then collapse those changes down to a single move, and * eliminate the intervening changes. */ public static void stampVersionTimes() { syncrhonize(unstampedNodeSet) { doVersionStamping(); } } private static void doVersionStamping() { URL server = this.timeServer; if (server == null) { server = defaultServer; } // For now, set time = system's current time long time = System.currentTimeMillis(); //___TODO: open a URLConnection to the server and revise the time // Reduce the time by the size of the list, so that each version // can get its own unique timestamp. time = time - unstampedVersions.size(); //___TODO: Maintain an "all versions" list, as well as the undo // list (unstamped versions). When publishing, stamp version // times, and move the list of changes to a tree, where the // version identifer is identified by the user as "major, minor, // or micro". // Step through the list, adding one millisecond each time, // so that each version gets a unique stamp. Stamp the version // with that time and remove the version from the list. iterator it = unstampedNodeSet.iterator(); while (lit.hasNext()) { AbstractNode node = (AbstractNode) it.next(); //___TODO:___ //As a result of UNDO operations, a node could be on the //list which has no changes. In that case, simply ignore it. // ___TODO:___ // Cycle through all of the node's versions, collapse to a // minimal set of changes, and time stamp those. //FOR EACH AS-YET UNSTAMPED VERSION OF A NODE: //{ //___TODO??: Add the node to a list that can be returned //by this operation, so the using system can store that //list in a version tree VersionStamp versionStamp = node.getVersionStamp(); versionStamp.setTime(++time); //} } // Reinitialize the data structures unstampedNodeSet = new HashMap(); unstampedVesions = new LinkedList(); } /** * A private constructor, so systems using this package * won't create VersionControl instances. */ private VersionControl() { } }//VersionControl