Added
Link Here
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2012 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
* |
40 |
* Portions Copyrighted 2012 Sun Microsystems, Inc. |
41 |
*/ |
42 |
package org.openide.filesystems; |
43 |
|
44 |
import java.io.IOException; |
45 |
import java.util.Arrays; |
46 |
import java.util.Collection; |
47 |
import java.util.Collections; |
48 |
import java.util.IdentityHashMap; |
49 |
import java.util.LinkedHashMap; |
50 |
import java.util.LinkedHashSet; |
51 |
import java.util.Map; |
52 |
import java.util.Set; |
53 |
|
54 |
/** Provides support for snapshot over tree of files on a file system. The |
55 |
* <a href="@org-netbeans-modules-parsing-api@/overview-summary.html">Parsing API</a> |
56 |
* uses this information (if available) to keep track about the change files |
57 |
* under given source root. Typical usage involves obtaining the stamp: |
58 |
* <pre>TreeStamp stamp = TreeStamp.{@link #findStamp(org.openide.filesystems.FileObject) findStamp(root)}</pre> |
59 |
* persisting it (either via serialization or by storing {@link #getId() base id} and |
60 |
* {@link #getModified() list of modified files} manually). Later, during next |
61 |
* start one can ask for: |
62 |
* <pre>{@code Set<String>} changedSince = TreeStamp.{@link #findModified(org.openide.filesystems.FileObject, java.lang.String) findModified(root, stamp.getId())}</pre> |
63 |
* The set of changed files is then obtained by |
64 |
* unifying {@link #getModified() stamp.getModified()} and <code>changedSince</code> sets. |
65 |
* <p> |
66 |
* After creation, this class is unmodifiable and thread safe for access. |
67 |
* |
68 |
* @author Jaroslav Tulach <jtulach@netbeans.org> |
69 |
* @since 7.56 |
70 |
*/ |
71 |
public final class TreeStamp implements java.io.Serializable { |
72 |
static final long serialVersionId = 45903850438L; |
73 |
private static final String[] EMPTY = new String[0]; |
74 |
|
75 |
private final String id; |
76 |
private final String series; |
77 |
private final String[] modified; |
78 |
|
79 |
private TreeStamp( |
80 |
String id, String series, Set<String> modified |
81 |
) { |
82 |
this.id = id; |
83 |
this.series = series; |
84 |
this.modified = modified == null ? EMPTY : modified.toArray(EMPTY); |
85 |
} |
86 |
|
87 |
/** The identification of the tree of files. In case of Mercurial that would be |
88 |
* the current change set of the repository. In case of Subversion the |
89 |
* revision of the repository. Some files may differ from this ideal |
90 |
* <em>id</em>, those are then enumerated in {@link #getModified()} list. |
91 |
* |
92 |
* @return string identifying the tree |
93 |
*/ |
94 |
public String getId() { |
95 |
return id; |
96 |
} |
97 |
|
98 |
/** Branch or other <em>stable</em> identification of the tree. Some |
99 |
* systems (like Mercurial) often switch from one revision of sources |
100 |
* to another while the files remain on the same place. In some situations |
101 |
* the <a href="@org-netbeans-modules-parsing-api@/overview-summary.html">Parsing API</a> |
102 |
* should realize that in spite the files stay on the same place, |
103 |
* they are in fact supposed to be completely different and that they |
104 |
* deserve separate storage. |
105 |
* |
106 |
* @return null or identification of a sequence of {@link #getId() ids} |
107 |
*/ |
108 |
public String getSeries() { |
109 |
return series; |
110 |
} |
111 |
|
112 |
/** Usually the sources slightly differ with respect to {@link #getId()}. |
113 |
* In such situation the {@link TreeStamp stamp} should contain enumeration |
114 |
* of the file names that are different. |
115 |
* |
116 |
* @return immutable collection with relative paths to files that differ |
117 |
* from the {@link #getId() baseId} |
118 |
*/ |
119 |
public Set<String> getModified() { |
120 |
return Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(modified))); |
121 |
} |
122 |
|
123 |
/** Computes {@link TreeStamp time stamp} for the files under given |
124 |
* root. Locates appropriate {@link Provider} and asks it to compute |
125 |
* the stamp. If the {@link Provider} refuses to compute it, the |
126 |
* return value may be <code>null</code>. |
127 |
* |
128 |
* @param rootOfTree the root to compute stamp for |
129 |
* @return <code>null</code> or snapshot of the state under the root |
130 |
*/ |
131 |
public static TreeStamp findStamp(FileObject rootOfTree) { |
132 |
return findStamp(Collections.singleton(rootOfTree)).get(rootOfTree); |
133 |
} |
134 |
|
135 |
/** A batch search for time stamps. Locates appropriate {@link #Provider} for |
136 |
* each root. Groups them together and asks them to compute a stamp. |
137 |
* |
138 |
* |
139 |
* @param roots the roots to compute stamp for |
140 |
* @return map of roots and their appropriate stamps. It is possible |
141 |
* that some of the roots do not have their {@link TreeStamp} in the |
142 |
* map - a sign that no provider decided to compute one |
143 |
*/ |
144 |
public static Map<FileObject,TreeStamp> findStamp( |
145 |
Collection<? extends FileObject> roots |
146 |
) { |
147 |
Map<FileObject,TreeStamp> snapshots = new LinkedHashMap<FileObject, TreeStamp>(); |
148 |
for (Map.Entry<Provider, Set<FileObject>> entry : groups(roots).entrySet()) { |
149 |
Map<FileObject, TreeStamp> map = entry.getKey().snapshot(entry.getValue()); |
150 |
if (map != null) { |
151 |
snapshots.putAll(map); |
152 |
} |
153 |
} |
154 |
return Collections.unmodifiableMap(snapshots); |
155 |
} |
156 |
|
157 |
/** Computes the {@link FileObject files} that |
158 |
* differ from the recorded {@link #getId() baseId} |
159 |
* recorded last time. |
160 |
* |
161 |
* @param root |
162 |
* @param baseId |
163 |
* @return immutable set of relative paths of files that differ from <code>baseId</code> |
164 |
* @throws IOException if the operation fails on I/O or if the <code>baseId</code> cannot |
165 |
* be understood |
166 |
*/ |
167 |
public static Set<String> findModified(FileObject root, String baseId) |
168 |
throws IOException { |
169 |
Set<String> ret = findModified(Collections.singletonMap(root, baseId)).get(root); |
170 |
if (ret == null) { |
171 |
throw new IOException("Nothing computed for " + root); |
172 |
} |
173 |
return ret; |
174 |
} |
175 |
|
176 |
/** Batch operation to find modified files under provided roots. The input |
177 |
* is the map of roots and their {@link #getId() baseIds}. The system |
178 |
* finds provider for each root, groups them for effectiveness and then |
179 |
* ask each of the provider to find differences since the previous base. |
180 |
* |
181 |
* @param rootsAndIds map from a root to its associated {@link #getId() baseId} |
182 |
* @return map from a subset of provided roots to set of relative paths that |
183 |
* changed under their tree |
184 |
*/ |
185 |
public static Map<FileObject,Set<String>> findModified( |
186 |
Map<? extends FileObject, String> rootsAndIds |
187 |
) { |
188 |
Map<FileObject,Set<String>> modified = new LinkedHashMap<FileObject, Set<String>>(); |
189 |
for (Map.Entry<Provider, Set<FileObject>> entry : groups(rootsAndIds.keySet()).entrySet()) { |
190 |
Map<FileObject, String> copy = new LinkedHashMap<FileObject, String>(rootsAndIds); |
191 |
copy.keySet().retainAll(entry.getValue()); |
192 |
Map<FileObject, Set<String>> ret = entry.getKey().differences(copy); |
193 |
if (ret != null) { |
194 |
modified.putAll(ret); |
195 |
} |
196 |
} |
197 |
return modified; |
198 |
} |
199 |
|
200 |
private static Map<Provider, Set<FileObject>> groups(Collection<? extends FileObject> roots) { |
201 |
Map<Provider, Set<FileObject>> groups = new IdentityHashMap<Provider, Set<FileObject>>(); |
202 |
for (FileObject fo : roots) { |
203 |
Object p = fo.getAttribute(Provider.class.getName()); |
204 |
if (p instanceof Provider) { |
205 |
Provider provider = (Provider) p; |
206 |
Set<FileObject> arr = groups.get(provider); |
207 |
if (arr == null) { |
208 |
arr = new LinkedHashSet<FileObject>(); |
209 |
groups.put(provider, arr); |
210 |
} |
211 |
arr.add(fo); |
212 |
} |
213 |
} |
214 |
return groups; |
215 |
} |
216 |
|
217 |
/** Class that can be provided by a file system, if it has a capability |
218 |
* to capture {@link TreeStamp} for some folders. Each folder that is |
219 |
* capable to provide {@link TreeStamp} should respond to |
220 |
* <pre>fo.getAttribute(Provider.class.getName())</pre> |
221 |
* by returning a shared instance of the {@link Provider}. In case |
222 |
* of batch operations the providers are compared using <code>==</code> |
223 |
* when doing {@link #findStamp(java.util.Collection) grouping}, that is |
224 |
* why usually return the same instance for each folder of the same type. |
225 |
*/ |
226 |
public static abstract class Provider { |
227 |
/** Constructor for subclasses */ |
228 |
protected Provider() { |
229 |
} |
230 |
|
231 |
/** Factory method to create a {@link TreeStamp} based on its |
232 |
* {@link TreeStamp#getId() baseId}, {@link TreeStamp#getSeries() series identification} |
233 |
* and set of {@link TreeStamp#getModified() modified files}. |
234 |
* |
235 |
* @param id base ID. |
236 |
* @param series series ID. May be <code>null</code>. |
237 |
* @param modified set of relative names of modified files. May be <code>null</code> if |
238 |
* no files are changed. |
239 |
* @return immutable instance of tree stamp |
240 |
*/ |
241 |
protected final TreeStamp create(String id, String series, Set<String> modified) { |
242 |
return new TreeStamp(id, series, modified); |
243 |
} |
244 |
|
245 |
/** If the provider is capable to compute stamp for all files under provided root, |
246 |
* it should {@link #create(java.lang.String, java.lang.String, java.util.Set) create a |
247 |
* stamp} object and return a mapping between individual roots and such |
248 |
* {@link TreeStamp} instances. |
249 |
* |
250 |
* @param roots the queried roots |
251 |
* @return <code>null</code> or map from subset of <code>roots</code> to {@link TreeStamp} |
252 |
*/ |
253 |
protected abstract Map<FileObject,TreeStamp> snapshot(Set<? extends FileObject> roots); |
254 |
|
255 |
/** If the provider recognized provide {@link TreeStamp#getId() baseId} for a root, |
256 |
* it should try to check what changes happened compared to such <code>baseId</code>. |
257 |
* If the <code>baseId</code> cannot be recognized or the differences cannot be |
258 |
* obtained for a root, the root should not be present in the returned map. |
259 |
* |
260 |
* @param rootsAndIds the queried roots and their {@link TreeStamp#getId() baseIds} |
261 |
* @return <code>null</code> or map from subset of <code>roots</code> to |
262 |
* relative names of files that changed under the root |
263 |
*/ |
264 |
protected abstract Map<FileObject,Set<String>> differences(Map<? extends FileObject, String> rootsAndIds); |
265 |
} |
266 |
} |