This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 207232
Collapse All | Expand All

(-)a/openide.filesystems/apichanges.xml (+19 lines)
Lines 49-54 Link Here
49
        <apidef name="filesystems">Filesystems API</apidef>
49
        <apidef name="filesystems">Filesystems API</apidef>
50
    </apidefs>
50
    </apidefs>
51
    <changes>
51
    <changes>
52
        <change id="TreeStamp">
53
            <api name="filesystems"/>
54
            <summary>Support for time stamps on subtrees of files</summary>
55
            <version major="7" minor="56"/>
56
            <date year="2012" month="1" day="19"/>
57
            <author login="jtulach"/>
58
            <compatibility addition="yes" binary="compatible" semantic="compatible"/>
59
            <description>
60
                <p>
61
                    Added new class for communication between
62
                    <a href="@org-netbeans-modules-parsing-api@/overview-summary.html">Parsing API</a>
63
                    and file systems that are capable to record revision of 
64
                    a file tree (like version control systems and Mac OS X file
65
                    system).
66
                </p>
67
            </description>
68
            <class package="org.openide.filesystems" name="TreeStamp"/>
69
            <issue number="207232"/>
70
        </change>
52
        <change id="FileObject.revert">
71
        <change id="FileObject.revert">
53
            <api name="filesystems"/>
72
            <api name="filesystems"/>
54
            <summary>Introduced <code>FileObject.revert</code></summary>
73
            <summary>Introduced <code>FileObject.revert</code></summary>
(-)a/openide.filesystems/arch.xml (+8 lines)
Lines 874-879 Link Here
874
          about dynamically changing the system filesystem.
874
          about dynamically changing the system filesystem.
875
      </p>
875
      </p>
876
  </usecase>
876
  </usecase>
877
  <usecase id="tree-stamp" name="Record a Stamp on a Subtree of Files">
878
    <p>Since version 7.56 it is possible to talk to various filesystems and
879
    let them record a state of files under given root effectively. Later 
880
    one can query the changes that happened meanwhile. For more information
881
    see the documentation to the <a href="@TOP@org/openide/filesystems/TreeStamp.html">TreeStamp</a>
882
    class.
883
    </p>
884
  </usecase>
877
 </answer>
885
 </answer>
878
886
879
887
(-)a/openide.filesystems/manifest.mf (-1 / +1 lines)
Lines 2-6 Link Here
2
OpenIDE-Module: org.openide.filesystems
2
OpenIDE-Module: org.openide.filesystems
3
OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties
4
OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml
4
OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml
5
OpenIDE-Module-Specification-Version: 7.55
5
OpenIDE-Module-Specification-Version: 7.56
6
6
(-)c3b7c6f096ef (+266 lines)
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
}
(-)c3b7c6f096ef (+226 lines)
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 Development and
11
 * Distribution License("CDDL") (collectively, the "License"). You may not use
12
 * this file except in compliance with the License. You can obtain a copy of
13
 * the License at http://www.netbeans.org/cddl-gplv2.html or
14
 * nbbuild/licenses/CDDL-GPL-2-CP. See the License for the specific language
15
 * governing permissions and limitations under the License. When distributing
16
 * the software, include this License Header Notice in each file and include
17
 * the License file at nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
18
 * particular file as subject to the "Classpath" exception as provided by
19
 * Oracle in the GPL Version 2 section of the License file that accompanied
20
 * this code. If applicable, add the following below the License Header, with
21
 * the fields enclosed by brackets [] replaced by your own identifying
22
 * information: "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL or
25
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
26
 * elects to include this software in this distribution under the [CDDL or GPL
27
 * Version 2] license." If you do not indicate a single choice of license, a
28
 * recipient has the option to distribute your version of this file under
29
 * either the CDDL, the GPL Version 2 or to extend the choice of license to its
30
 * licensees as provided above. However, if you add GPL Version 2 code and
31
 * therefore, elected the GPL Version 2 license, then the option applies only
32
 * if the new code is made subject to such option by the copyright holder.
33
 *
34
 * Contributor(s):
35
 *
36
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
37
 */
38
package org.openide.filesystems;
39
40
import java.beans.PropertyVetoException;
41
import java.io.File;
42
import java.io.IOException;
43
import java.util.ArrayList;
44
import java.util.Arrays;
45
import java.util.Collections;
46
import java.util.Enumeration;
47
import java.util.HashMap;
48
import java.util.List;
49
import java.util.Map;
50
import java.util.Set;
51
import org.netbeans.junit.NbTestCase;
52
53
/** Tests finding the {@link TreeStamp.Provider} and the behavior of its 
54
 * grouping.
55
 *
56
 * @author Jaroslav Tulach <jtulach@netbeans.org>
57
 */
58
public class TreeStampTest extends NbTestCase {
59
    private LFSWithProvider fs1;
60
    private LFSWithProvider fs2;
61
    
62
    public TreeStampTest(String n) {
63
        super(n);
64
    }
65
66
67
    @Override
68
    protected void setUp() throws IOException, PropertyVetoException {
69
        clearWorkDir();
70
        File r1 = new File(getWorkDir(), "root1");
71
        File r2 = new File(getWorkDir(), "root2");
72
        
73
        r1.mkdirs();
74
        r2.mkdirs();
75
        
76
        fs1 = new LFSWithProvider(r1);
77
        fs2 = new LFSWithProvider(r2);
78
    }
79
    
80
    public void testFindStampIndividualCalls() throws Exception {
81
        fs1.provider = new LFSProvider();
82
        fs2.provider = new LFSProvider();
83
        fs1.provider.myId = "alone";
84
        fs2.provider.myId = "lonely";
85
        
86
        final List<FileObject> roots = Arrays.asList(fs1.getRoot(), fs2.getRoot());
87
        Map<FileObject, TreeStamp> res = TreeStamp.findStamp(roots);
88
        assertNotNull("Map returned", res);
89
        assertEquals("Two elements there", 2, res.size());
90
        assertEquals("grouping is the id1", "alone", res.get(fs1.getRoot()).getId());
91
        assertEquals("grouping is the id2", "lonely", res.get(fs2.getRoot()).getId());
92
        
93
        assertEquals("Individual queries1", 1, fs1.provider.roots.size());
94
        assertEquals("Individual queries2", 1, fs2.provider.roots.size());
95
        assertEquals("Root fs queried1", fs1.getRoot(), fs1.provider.roots.iterator().next());
96
        assertEquals("Root fs queried2", fs2.getRoot(), fs2.provider.roots.iterator().next());
97
    }
98
    
99
    public void testFindStampGrouping() throws Exception {
100
        final LFSProvider p = new LFSProvider();
101
        fs1.provider = p;
102
        fs2.provider = p;
103
        p.myId = "grouping";
104
        
105
        final List<FileObject> roots = Arrays.asList(fs1.getRoot(), fs2.getRoot());
106
        Map<FileObject, TreeStamp> res = TreeStamp.findStamp(roots);
107
        assertNotNull("Map returned", res);
108
        assertEquals("Two elements there", 2, res.size());
109
        assertEquals("grouping is the id1", "grouping", res.get(fs1.getRoot()).getId());
110
        assertEquals("grouping is the id2", "grouping", res.get(fs2.getRoot()).getId());
111
        
112
        assertEquals("Both roots provided at once", roots, new ArrayList<FileObject>(p.roots));
113
    }
114
115
    public void testFindModifiedIndividualCalls() throws Exception {
116
        fs1.provider = new LFSProvider();
117
        fs2.provider = new LFSProvider();
118
        fs1.provider.baseId = "basic";
119
        fs2.provider.baseId = "extra";
120
        
121
        final Map<FileObject,String> rootsAndIds = new HashMap<FileObject, String>();
122
        rootsAndIds.put(fs1.getRoot(), "basic");
123
        rootsAndIds.put(fs2.getRoot(), "extra");
124
        
125
        Map<FileObject, Set<String>> res = TreeStamp.findModified(rootsAndIds);
126
        
127
        assertNotNull("Map returned", res);
128
        assertEquals("Two elements there", 2, res.size());
129
        assertEquals("root1 is modified", Collections.singleton(""), res.get(fs1.getRoot()));
130
        assertEquals("root2 is modified", Collections.singleton(""), res.get(fs2.getRoot()));
131
        
132
        assertEquals("Each provide queried1", 1, fs1.provider.rootsAndIds.size());
133
        assertEquals("Each provide queried2", 1, fs2.provider.rootsAndIds.size());
134
    }
135
    
136
    public void testFindModifiedGrouping() throws Exception {
137
        final LFSProvider p = new LFSProvider();
138
        fs1.provider = p;
139
        fs2.provider = p;
140
        p.baseId = "basic";
141
        
142
        final Map<FileObject,String> rootsAndIds = new HashMap<FileObject, String>();
143
        rootsAndIds.put(fs1.getRoot(), "different");
144
        rootsAndIds.put(fs2.getRoot(), "basic");
145
        
146
        Map<FileObject, Set<String>> res = TreeStamp.findModified(rootsAndIds);
147
        
148
        assertNotNull("Map returned", res);
149
        assertEquals("One elements there", 1, res.size());
150
        assertEquals("root is modified", Collections.singleton(""), res.get(fs2.getRoot()));
151
        
152
        assertEquals("Both roots provided at once", rootsAndIds, p.rootsAndIds);
153
    }
154
    
155
    
156
    private static final class LFSWithProvider extends LocalFileSystem 
157
    implements AbstractFileSystem.Attr {
158
        LFSProvider provider;
159
        
160
        public LFSWithProvider(File root) throws IOException, PropertyVetoException {
161
            this.attr = this;
162
            setRootDirectory(root);
163
        }
164
165
        @Override
166
        public Object readAttribute(String name, String attrName) {
167
            if (TreeStamp.Provider.class.getName().equals(attrName)) {
168
                return provider;
169
            }
170
            return ((Attr)change).readAttribute(name, attrName);
171
        }
172
173
        @Override
174
        public void writeAttribute(String name, String attrName, Object value) throws IOException {
175
            ((Attr)change).writeAttribute(name, attrName, value);
176
        }
177
178
        @Override
179
        public Enumeration<String> attributes(String name) {
180
            return ((Attr)change).attributes(name);
181
        }
182
183
        @Override
184
        public void renameAttributes(String oldName, String newName) {
185
            ((Attr)change).renameAttributes(oldName, newName);
186
        }
187
188
        @Override
189
        public void deleteAttributes(String name) {
190
            ((Attr)change).deleteAttributes(name);
191
        }
192
    }
193
    
194
    private static final class LFSProvider extends TreeStamp.Provider {
195
        String myId;
196
        Set<? extends FileObject> roots;
197
        String baseId;
198
        Map<? extends FileObject, String> rootsAndIds;
199
        
200
        @Override
201
        protected Map<FileObject, TreeStamp> snapshot(Set<? extends FileObject> roots) {
202
            assertNull("No call to snapshot yet", this.roots);
203
            this.roots = roots;
204
            
205
            Map<FileObject, TreeStamp> map = new HashMap<FileObject, TreeStamp>();
206
            for (FileObject f : roots) {
207
                map.put(f, create(myId, null, null));
208
            }
209
            return map;
210
        }
211
212
        @Override
213
        protected Map<FileObject, Set<String>> differences(Map<? extends FileObject, String> rootsAndIds) {
214
            assertNull("No call to differences yet", this.rootsAndIds);
215
            this.rootsAndIds = rootsAndIds;
216
            
217
            Map<FileObject, Set<String>> map = new HashMap<FileObject, Set<String>>();
218
            for (FileObject f : rootsAndIds.keySet()) {
219
                if (baseId.equals(rootsAndIds.get(f))) {
220
                    map.put(f, Collections.singleton(""));
221
                }
222
            }
223
            return map;
224
        }
225
    }
226
}

Return to bug 207232