diff -r c3b7c6f096ef openide.filesystems/apichanges.xml
--- a/openide.filesystems/apichanges.xml Tue Jan 10 12:13:19 2012 +0100
+++ b/openide.filesystems/apichanges.xml Thu Jan 12 18:28:03 2012 +0100
@@ -49,6 +49,25 @@
Filesystems API
+
+
+ Support for time stamps on subtrees of files
+
+
+
+
+
+
+ Added new class for communication between
+ Parsing API
+ and file systems that are capable to record revision of
+ a file tree (like version control systems and Mac OS X file
+ system).
+
+
+
+
+
Introduced FileObject.revert
diff -r c3b7c6f096ef openide.filesystems/arch.xml
--- a/openide.filesystems/arch.xml Tue Jan 10 12:13:19 2012 +0100
+++ b/openide.filesystems/arch.xml Thu Jan 12 18:28:03 2012 +0100
@@ -874,6 +874,14 @@
about dynamically changing the system filesystem.
+
+ Since version 7.56 it is possible to talk to various filesystems and
+ let them record a state of files under given root effectively. Later
+ one can query the changes that happened meanwhile. For more information
+ see the documentation to the TreeStamp
+ class.
+
+
diff -r c3b7c6f096ef openide.filesystems/manifest.mf
--- a/openide.filesystems/manifest.mf Tue Jan 10 12:13:19 2012 +0100
+++ b/openide.filesystems/manifest.mf Thu Jan 12 18:28:03 2012 +0100
@@ -2,5 +2,5 @@
OpenIDE-Module: org.openide.filesystems
OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties
OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml
-OpenIDE-Module-Specification-Version: 7.55
+OpenIDE-Module-Specification-Version: 7.56
diff -r c3b7c6f096ef openide.filesystems/src/org/openide/filesystems/TreeStamp.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/openide.filesystems/src/org/openide/filesystems/TreeStamp.java Thu Jan 12 18:28:03 2012 +0100
@@ -0,0 +1,266 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2012 Sun Microsystems, Inc.
+ */
+package org.openide.filesystems;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Provides support for snapshot over tree of files on a file system. The
+ * Parsing API
+ * uses this information (if available) to keep track about the change files
+ * under given source root. Typical usage involves obtaining the stamp:
+ * TreeStamp stamp = TreeStamp.{@link #findStamp(org.openide.filesystems.FileObject) findStamp(root)}
+ * persisting it (either via serialization or by storing {@link #getId() base id} and
+ * {@link #getModified() list of modified files} manually). Later, during next
+ * start one can ask for:
+ * {@code Set} changedSince = TreeStamp.{@link #findModified(org.openide.filesystems.FileObject, java.lang.String) findModified(root, stamp.getId())}
+ * The set of changed files is then obtained by
+ * unifying {@link #getModified() stamp.getModified()} and changedSince
sets.
+ *
+ * After creation, this class is unmodifiable and thread safe for access.
+ *
+ * @author Jaroslav Tulach
+ * @since 7.56
+ */
+public final class TreeStamp implements java.io.Serializable {
+ static final long serialVersionId = 45903850438L;
+ private static final String[] EMPTY = new String[0];
+
+ private final String id;
+ private final String series;
+ private final String[] modified;
+
+ private TreeStamp(
+ String id, String series, Set modified
+ ) {
+ this.id = id;
+ this.series = series;
+ this.modified = modified == null ? EMPTY : modified.toArray(EMPTY);
+ }
+
+ /** The identification of the tree of files. In case of Mercurial that would be
+ * the current change set of the repository. In case of Subversion the
+ * revision of the repository. Some files may differ from this ideal
+ * id, those are then enumerated in {@link #getModified()} list.
+ *
+ * @return string identifying the tree
+ */
+ public String getId() {
+ return id;
+ }
+
+ /** Branch or other stable identification of the tree. Some
+ * systems (like Mercurial) often switch from one revision of sources
+ * to another while the files remain on the same place. In some situations
+ * the Parsing API
+ * should realize that in spite the files stay on the same place,
+ * they are in fact supposed to be completely different and that they
+ * deserve separate storage.
+ *
+ * @return null or identification of a sequence of {@link #getId() ids}
+ */
+ public String getSeries() {
+ return series;
+ }
+
+ /** Usually the sources slightly differ with respect to {@link #getId()}.
+ * In such situation the {@link TreeStamp stamp} should contain enumeration
+ * of the file names that are different.
+ *
+ * @return immutable collection with relative paths to files that differ
+ * from the {@link #getId() baseId}
+ */
+ public Set getModified() {
+ return Collections.unmodifiableSet(new LinkedHashSet(Arrays.asList(modified)));
+ }
+
+ /** Computes {@link TreeStamp time stamp} for the files under given
+ * root. Locates appropriate {@link Provider} and asks it to compute
+ * the stamp. If the {@link Provider} refuses to compute it, the
+ * return value may be null
.
+ *
+ * @param rootOfTree the root to compute stamp for
+ * @return null
or snapshot of the state under the root
+ */
+ public static TreeStamp findStamp(FileObject rootOfTree) {
+ return findStamp(Collections.singleton(rootOfTree)).get(rootOfTree);
+ }
+
+ /** A batch search for time stamps. Locates appropriate {@link #Provider} for
+ * each root. Groups them together and asks them to compute a stamp.
+ *
+ *
+ * @param roots the roots to compute stamp for
+ * @return map of roots and their appropriate stamps. It is possible
+ * that some of the roots do not have their {@link TreeStamp} in the
+ * map - a sign that no provider decided to compute one
+ */
+ public static Map findStamp(
+ Collection extends FileObject> roots
+ ) {
+ Map snapshots = new LinkedHashMap();
+ for (Map.Entry> entry : groups(roots).entrySet()) {
+ Map map = entry.getKey().snapshot(entry.getValue());
+ if (map != null) {
+ snapshots.putAll(map);
+ }
+ }
+ return Collections.unmodifiableMap(snapshots);
+ }
+
+ /** Computes the {@link FileObject files} that
+ * differ from the recorded {@link #getId() baseId}
+ * recorded last time.
+ *
+ * @param root
+ * @param baseId
+ * @return immutable set of relative paths of files that differ from baseId
+ * @throws IOException if the operation fails on I/O or if the baseId
cannot
+ * be understood
+ */
+ public static Set findModified(FileObject root, String baseId)
+ throws IOException {
+ Set ret = findModified(Collections.singletonMap(root, baseId)).get(root);
+ if (ret == null) {
+ throw new IOException("Nothing computed for " + root);
+ }
+ return ret;
+ }
+
+ /** Batch operation to find modified files under provided roots. The input
+ * is the map of roots and their {@link #getId() baseIds}. The system
+ * finds provider for each root, groups them for effectiveness and then
+ * ask each of the provider to find differences since the previous base.
+ *
+ * @param rootsAndIds map from a root to its associated {@link #getId() baseId}
+ * @return map from a subset of provided roots to set of relative paths that
+ * changed under their tree
+ */
+ public static Map> findModified(
+ Map extends FileObject, String> rootsAndIds
+ ) {
+ Map> modified = new LinkedHashMap>();
+ for (Map.Entry> entry : groups(rootsAndIds.keySet()).entrySet()) {
+ Map copy = new LinkedHashMap(rootsAndIds);
+ copy.keySet().retainAll(entry.getValue());
+ Map> ret = entry.getKey().differences(copy);
+ if (ret != null) {
+ modified.putAll(ret);
+ }
+ }
+ return modified;
+ }
+
+ private static Map> groups(Collection extends FileObject> roots) {
+ Map> groups = new IdentityHashMap>();
+ for (FileObject fo : roots) {
+ Object p = fo.getAttribute(Provider.class.getName());
+ if (p instanceof Provider) {
+ Provider provider = (Provider) p;
+ Set arr = groups.get(provider);
+ if (arr == null) {
+ arr = new LinkedHashSet();
+ groups.put(provider, arr);
+ }
+ arr.add(fo);
+ }
+ }
+ return groups;
+ }
+
+ /** Class that can be provided by a file system, if it has a capability
+ * to capture {@link TreeStamp} for some folders. Each folder that is
+ * capable to provide {@link TreeStamp} should respond to
+ * fo.getAttribute(Provider.class.getName())
+ * by returning a shared instance of the {@link Provider}. In case
+ * of batch operations the providers are compared using ==
+ * when doing {@link #findStamp(java.util.Collection) grouping}, that is
+ * why usually return the same instance for each folder of the same type.
+ */
+ public static abstract class Provider {
+ /** Constructor for subclasses */
+ protected Provider() {
+ }
+
+ /** Factory method to create a {@link TreeStamp} based on its
+ * {@link TreeStamp#getId() baseId}, {@link TreeStamp#getSeries() series identification}
+ * and set of {@link TreeStamp#getModified() modified files}.
+ *
+ * @param id base ID.
+ * @param series series ID. May be null
.
+ * @param modified set of relative names of modified files. May be null
if
+ * no files are changed.
+ * @return immutable instance of tree stamp
+ */
+ protected final TreeStamp create(String id, String series, Set modified) {
+ return new TreeStamp(id, series, modified);
+ }
+
+ /** If the provider is capable to compute stamp for all files under provided root,
+ * it should {@link #create(java.lang.String, java.lang.String, java.util.Set) create a
+ * stamp} object and return a mapping between individual roots and such
+ * {@link TreeStamp} instances.
+ *
+ * @param roots the queried roots
+ * @return null
or map from subset of roots
to {@link TreeStamp}
+ */
+ protected abstract Map snapshot(Set extends FileObject> roots);
+
+ /** If the provider recognized provide {@link TreeStamp#getId() baseId} for a root,
+ * it should try to check what changes happened compared to such baseId
.
+ * If the baseId
cannot be recognized or the differences cannot be
+ * obtained for a root, the root should not be present in the returned map.
+ *
+ * @param rootsAndIds the queried roots and their {@link TreeStamp#getId() baseIds}
+ * @return null
or map from subset of roots
to
+ * relative names of files that changed under the root
+ */
+ protected abstract Map> differences(Map extends FileObject, String> rootsAndIds);
+ }
+}
diff -r c3b7c6f096ef openide.filesystems/test/unit/src/org/openide/filesystems/TreeStampTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/openide.filesystems/test/unit/src/org/openide/filesystems/TreeStampTest.java Thu Jan 12 18:28:03 2012 +0100
@@ -0,0 +1,226 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development and
+ * Distribution License("CDDL") (collectively, the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy of
+ * the License at http://www.netbeans.org/cddl-gplv2.html or
+ * nbbuild/licenses/CDDL-GPL-2-CP. See the License for the specific language
+ * governing permissions and limitations under the License. When distributing
+ * the software, include this License Header Notice in each file and include
+ * the License file at nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided by
+ * Oracle in the GPL Version 2 section of the License file that accompanied
+ * this code. If applicable, add the following below the License Header, with
+ * the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you do not indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to its
+ * licensees as provided above. However, if you add GPL Version 2 code and
+ * therefore, elected the GPL Version 2 license, then the option applies only
+ * if the new code is made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2012 Sun Microsystems, Inc.
+ */
+package org.openide.filesystems;
+
+import java.beans.PropertyVetoException;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.netbeans.junit.NbTestCase;
+
+/** Tests finding the {@link TreeStamp.Provider} and the behavior of its
+ * grouping.
+ *
+ * @author Jaroslav Tulach
+ */
+public class TreeStampTest extends NbTestCase {
+ private LFSWithProvider fs1;
+ private LFSWithProvider fs2;
+
+ public TreeStampTest(String n) {
+ super(n);
+ }
+
+
+ @Override
+ protected void setUp() throws IOException, PropertyVetoException {
+ clearWorkDir();
+ File r1 = new File(getWorkDir(), "root1");
+ File r2 = new File(getWorkDir(), "root2");
+
+ r1.mkdirs();
+ r2.mkdirs();
+
+ fs1 = new LFSWithProvider(r1);
+ fs2 = new LFSWithProvider(r2);
+ }
+
+ public void testFindStampIndividualCalls() throws Exception {
+ fs1.provider = new LFSProvider();
+ fs2.provider = new LFSProvider();
+ fs1.provider.myId = "alone";
+ fs2.provider.myId = "lonely";
+
+ final List roots = Arrays.asList(fs1.getRoot(), fs2.getRoot());
+ Map res = TreeStamp.findStamp(roots);
+ assertNotNull("Map returned", res);
+ assertEquals("Two elements there", 2, res.size());
+ assertEquals("grouping is the id1", "alone", res.get(fs1.getRoot()).getId());
+ assertEquals("grouping is the id2", "lonely", res.get(fs2.getRoot()).getId());
+
+ assertEquals("Individual queries1", 1, fs1.provider.roots.size());
+ assertEquals("Individual queries2", 1, fs2.provider.roots.size());
+ assertEquals("Root fs queried1", fs1.getRoot(), fs1.provider.roots.iterator().next());
+ assertEquals("Root fs queried2", fs2.getRoot(), fs2.provider.roots.iterator().next());
+ }
+
+ public void testFindStampGrouping() throws Exception {
+ final LFSProvider p = new LFSProvider();
+ fs1.provider = p;
+ fs2.provider = p;
+ p.myId = "grouping";
+
+ final List roots = Arrays.asList(fs1.getRoot(), fs2.getRoot());
+ Map res = TreeStamp.findStamp(roots);
+ assertNotNull("Map returned", res);
+ assertEquals("Two elements there", 2, res.size());
+ assertEquals("grouping is the id1", "grouping", res.get(fs1.getRoot()).getId());
+ assertEquals("grouping is the id2", "grouping", res.get(fs2.getRoot()).getId());
+
+ assertEquals("Both roots provided at once", roots, new ArrayList(p.roots));
+ }
+
+ public void testFindModifiedIndividualCalls() throws Exception {
+ fs1.provider = new LFSProvider();
+ fs2.provider = new LFSProvider();
+ fs1.provider.baseId = "basic";
+ fs2.provider.baseId = "extra";
+
+ final Map rootsAndIds = new HashMap();
+ rootsAndIds.put(fs1.getRoot(), "basic");
+ rootsAndIds.put(fs2.getRoot(), "extra");
+
+ Map> res = TreeStamp.findModified(rootsAndIds);
+
+ assertNotNull("Map returned", res);
+ assertEquals("Two elements there", 2, res.size());
+ assertEquals("root1 is modified", Collections.singleton(""), res.get(fs1.getRoot()));
+ assertEquals("root2 is modified", Collections.singleton(""), res.get(fs2.getRoot()));
+
+ assertEquals("Each provide queried1", 1, fs1.provider.rootsAndIds.size());
+ assertEquals("Each provide queried2", 1, fs2.provider.rootsAndIds.size());
+ }
+
+ public void testFindModifiedGrouping() throws Exception {
+ final LFSProvider p = new LFSProvider();
+ fs1.provider = p;
+ fs2.provider = p;
+ p.baseId = "basic";
+
+ final Map rootsAndIds = new HashMap();
+ rootsAndIds.put(fs1.getRoot(), "different");
+ rootsAndIds.put(fs2.getRoot(), "basic");
+
+ Map> res = TreeStamp.findModified(rootsAndIds);
+
+ assertNotNull("Map returned", res);
+ assertEquals("One elements there", 1, res.size());
+ assertEquals("root is modified", Collections.singleton(""), res.get(fs2.getRoot()));
+
+ assertEquals("Both roots provided at once", rootsAndIds, p.rootsAndIds);
+ }
+
+
+ private static final class LFSWithProvider extends LocalFileSystem
+ implements AbstractFileSystem.Attr {
+ LFSProvider provider;
+
+ public LFSWithProvider(File root) throws IOException, PropertyVetoException {
+ this.attr = this;
+ setRootDirectory(root);
+ }
+
+ @Override
+ public Object readAttribute(String name, String attrName) {
+ if (TreeStamp.Provider.class.getName().equals(attrName)) {
+ return provider;
+ }
+ return ((Attr)change).readAttribute(name, attrName);
+ }
+
+ @Override
+ public void writeAttribute(String name, String attrName, Object value) throws IOException {
+ ((Attr)change).writeAttribute(name, attrName, value);
+ }
+
+ @Override
+ public Enumeration attributes(String name) {
+ return ((Attr)change).attributes(name);
+ }
+
+ @Override
+ public void renameAttributes(String oldName, String newName) {
+ ((Attr)change).renameAttributes(oldName, newName);
+ }
+
+ @Override
+ public void deleteAttributes(String name) {
+ ((Attr)change).deleteAttributes(name);
+ }
+ }
+
+ private static final class LFSProvider extends TreeStamp.Provider {
+ String myId;
+ Set extends FileObject> roots;
+ String baseId;
+ Map extends FileObject, String> rootsAndIds;
+
+ @Override
+ protected Map snapshot(Set extends FileObject> roots) {
+ assertNull("No call to snapshot yet", this.roots);
+ this.roots = roots;
+
+ Map map = new HashMap();
+ for (FileObject f : roots) {
+ map.put(f, create(myId, null, null));
+ }
+ return map;
+ }
+
+ @Override
+ protected Map> differences(Map extends FileObject, String> rootsAndIds) {
+ assertNull("No call to differences yet", this.rootsAndIds);
+ this.rootsAndIds = rootsAndIds;
+
+ Map> map = new HashMap>();
+ for (FileObject f : rootsAndIds.keySet()) {
+ if (baseId.equals(rootsAndIds.get(f))) {
+ map.put(f, Collections.singleton(""));
+ }
+ }
+ return map;
+ }
+ }
+}