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 134580
Collapse All | Expand All

(-)a/projectapi/apichanges.xml (+17 lines)
Lines 104-109 Link Here
104
    <!-- ACTUAL CHANGES BEGIN HERE: -->
104
    <!-- ACTUAL CHANGES BEGIN HERE: -->
105
105
106
    <changes>
106
    <changes>
107
        <change id="projectutils-getPreferences">
108
            <api name="general"/>
109
            <summary>Added ProjectUtils.getPreferences.</summary>
110
            <version major="1" minor="16"/>
111
            <date day="6" month="6" year="2008"/>
112
            <author login="jlahoda"/>
113
            <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" modification="no" semantic="compatible" source="compatible">
114
            </compatibility>
115
            <description>
116
	        <p>
117
                    Added ProjectUtils.getPreferences.
118
		</p>
119
            </description>
120
            <class package="org.netbeans.api.project" name="ProjectUtils"/>
121
            <issue number="134580"/>
122
        </change>
123
        
107
        <change id="lookup-provider">
124
        <change id="lookup-provider">
108
            <api name="general"/>
125
            <api name="general"/>
109
            <summary>Added support for composing project's lookup from multiple sources.</summary>
126
            <summary>Added support for composing project's lookup from multiple sources.</summary>
(-)a/projectapi/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
3
OpenIDE-Module-Specification-Version: 1.15
3
OpenIDE-Module-Specification-Version: 1.16
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
5
5
(-)a/projectapi/nbproject/project.xml (-8 / +13 lines)
Lines 92-114 Link Here
92
                <test-type>
92
                <test-type>
93
                    <name>unit</name>
93
                    <name>unit</name>
94
                    <test-dependency>
94
                    <test-dependency>
95
                        <code-name-base>org.netbeans.modules.java.j2seproject</code-name-base>
96
                        <recursive/>
97
                        <compile-dependency/>
98
                    </test-dependency>
99
                    <test-dependency>
100
                        <code-name-base>org.netbeans.modules.masterfs</code-name-base>
101
                    </test-dependency>
102
                    <test-dependency>
95
                        <code-name-base>org.netbeans.modules.projectapi</code-name-base>
103
                        <code-name-base>org.netbeans.modules.projectapi</code-name-base>
96
                        <recursive/>
104
                        <recursive/>
97
                        <compile-dependency/>
105
                        <compile-dependency/>
98
                    </test-dependency>
106
                    </test-dependency>
99
                    <test-dependency>
107
                    <test-dependency>
100
                        <code-name-base>org.openide.modules</code-name-base>
108
                        <code-name-base>org.openide.filesystems</code-name-base>
101
                    </test-dependency>
102
                    <test-dependency>
103
                        <code-name-base>org.netbeans.modules.masterfs</code-name-base>
104
                    </test-dependency>
105
                    <test-dependency>
106
                        <code-name-base>org.openide.util</code-name-base>
107
                        <compile-dependency/>
109
                        <compile-dependency/>
108
                        <test/>
110
                        <test/>
109
                    </test-dependency>
111
                    </test-dependency>
110
                    <test-dependency>
112
                    <test-dependency>
111
                        <code-name-base>org.openide.filesystems</code-name-base>
113
                        <code-name-base>org.openide.modules</code-name-base>
114
                    </test-dependency>
115
                    <test-dependency>
116
                        <code-name-base>org.openide.util</code-name-base>
112
                        <compile-dependency/>
117
                        <compile-dependency/>
113
                        <test/>
118
                        <test/>
114
                    </test-dependency>
119
                    </test-dependency>
(-)a/projectapi/src/org/netbeans/api/project/ProjectUtils.java (+20 lines)
Lines 47-58 Link Here
47
import java.util.HashSet;
47
import java.util.HashSet;
48
import java.util.Map;
48
import java.util.Map;
49
import java.util.Set;
49
import java.util.Set;
50
import java.util.prefs.Preferences;
50
import javax.swing.Icon;
51
import javax.swing.Icon;
51
import javax.swing.ImageIcon;
52
import javax.swing.ImageIcon;
53
import org.netbeans.modules.projectapi.AuxiliaryConfigBasedPreferencesProvider;
52
import org.netbeans.spi.project.SubprojectProvider;
54
import org.netbeans.spi.project.SubprojectProvider;
53
import org.netbeans.spi.project.support.GenericSources;
55
import org.netbeans.spi.project.support.GenericSources;
54
import org.openide.filesystems.FileStateInvalidException;
56
import org.openide.filesystems.FileStateInvalidException;
55
import org.openide.util.Mutex;
57
import org.openide.util.Mutex;
58
import org.openide.util.Parameters;
56
import org.openide.util.Utilities;
59
import org.openide.util.Utilities;
57
60
58
/**
61
/**
Lines 143-148 Link Here
143
    }
146
    }
144
    
147
    
145
    /**
148
    /**
149
     * Return {@link Preferences} for the given project and given module (specified
150
     * as using {@link Class} as in {@link org.openide.util.NbPreferences#forModule(java.lang.Class)}).
151
     * 
152
     * @param p for which project should be the preferences returned
153
     * @param c module specification as in {@link org.openide.util.NbPreferences#forModule(java.lang.Class)}
154
     * @param shared whether the returned settings should be shared
155
     * @return {@link Preferences} for the given project
156
     * @since 1.16
157
     */
158
    public static Preferences getPreferences(Project p, Class c, boolean shared) {
159
        Parameters.notNull("p", p);
160
        Parameters.notNull("c", c);
161
        
162
        return AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, c, shared);
163
    }
164
    
165
    /**
146
     * Do a DFS traversal checking for cycles.
166
     * Do a DFS traversal checking for cycles.
147
     * @param encountered projects already encountered in the DFS (added and removed as you go)
167
     * @param encountered projects already encountered in the DFS (added and removed as you go)
148
     * @param curr current node to visit
168
     * @param curr current node to visit
(-)a/projectapi/src/org/netbeans/modules/projectapi/AuxiliaryConfigBasedPreferencesProvider.java (+543 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 2008 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.modules.projectapi;
43
44
import java.io.IOException;
45
import java.lang.ref.Reference;
46
import java.lang.ref.WeakReference;
47
import java.util.Collection;
48
import java.util.Collections;
49
import java.util.HashMap;
50
import java.util.HashSet;
51
import java.util.LinkedHashSet;
52
import java.util.LinkedList;
53
import java.util.List;
54
import java.util.Map;
55
import java.util.Map.Entry;
56
import java.util.Set;
57
import java.util.prefs.AbstractPreferences;
58
import java.util.prefs.BackingStoreException;
59
import java.util.prefs.Preferences;
60
import org.netbeans.api.project.Project;
61
import org.netbeans.api.project.ProjectManager;
62
import org.netbeans.spi.project.AuxiliaryConfiguration;
63
import org.openide.filesystems.FileStateInvalidException;
64
import org.openide.util.Exceptions;
65
import org.openide.util.NbPreferences;
66
import org.openide.util.Parameters;
67
import org.openide.util.RequestProcessor;
68
import org.openide.util.RequestProcessor.Task;
69
import org.openide.util.Utilities;
70
import org.openide.xml.XMLUtil;
71
import org.w3c.dom.DOMException;
72
import org.w3c.dom.Element;
73
import org.w3c.dom.Node;
74
import org.w3c.dom.NodeList;
75
76
/**
77
 * @author Jan Lahoda
78
 */
79
public class AuxiliaryConfigBasedPreferencesProvider {
80
    
81
    private static Map<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>> projects = new HashMap<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>>();
82
    
83
    static synchronized AuxiliaryConfigBasedPreferencesProvider findProvider(Project p) {
84
        Reference<AuxiliaryConfigBasedPreferencesProvider> provRef = projects.get(p);
85
        AuxiliaryConfigBasedPreferencesProvider prov = provRef != null ? provRef.get() : null;
86
        
87
        if (prov != null) {
88
            return prov;
89
        }
90
        
91
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
92
        
93
        if (ac != null) {
94
            projects.put(p, new CleaningWeakReference<AuxiliaryConfigBasedPreferencesProvider>(prov = new AuxiliaryConfigBasedPreferencesProvider(p, ac), projects, p));
95
        }
96
        
97
        return prov;
98
    }
99
    
100
    public static Preferences getPreferences(Project p, Class c, boolean shared) {
101
        Parameters.notNull("p", p);
102
        Parameters.notNull("c", c);
103
        
104
        return shared ? getSharedSettings(p, c) : getPrivateSettings(p, c);
105
    }
106
107
    private static Preferences getSharedSettings(Project p, Class c) {
108
        AuxiliaryConfigBasedPreferencesProvider provider = findProvider(p);
109
110
        if (provider == null) {
111
            return null;
112
        }
113
114
        return provider.findModule(AuxiliaryConfigBasedPreferencesProvider.findCNBForClass(c));
115
    }
116
    
117
    private static synchronized Preferences getPrivateSettings(Project p, Class c) {
118
        Preferences prefs = NbPreferences.forModule(AuxiliaryConfigBasedPreferencesProvider.class);
119
        
120
        prefs = prefs.node(PREF_NODE_AUXILIARY_CONFIG_BASE_PREFERENCES);
121
        
122
        try {
123
            String projectDir = encodeString(p.getProjectDirectory().getURL().toExternalForm());
124
            String projectPrivateKey = prefs.get(projectDir, null);
125
            
126
            if (projectPrivateKey == null) {
127
                //find one:
128
                int key = 0;
129
                
130
                while (prefs.getBoolean(projectPrivateKey = "__id" + Integer.toHexString(key), false))
131
                    key++;
132
                
133
                prefs.put(projectDir, projectPrivateKey);
134
                prefs.putBoolean(projectPrivateKey, true);
135
            }
136
            
137
            return prefs.node(projectPrivateKey);
138
        } catch (FileStateInvalidException ex) {
139
            Exceptions.printStackTrace(ex);
140
            return null;
141
        }
142
    }
143
    
144
    private static String encodeString(String s) {
145
        StringBuilder result = new StringBuilder();
146
        
147
        for (char c : s.toCharArray()) {
148
            if (VALID_KEY_CHARACTERS.indexOf(c) != (-1)) {
149
                result.append(c);
150
            } else {
151
                result.append("_");
152
                result.append(Integer.toHexString((int) c));
153
                result.append("_");
154
            }
155
        }
156
        
157
        return result.toString();
158
    }
159
    
160
    private static final String NAMESPACE = "http://www.netbeans.org/ns/auxiliary-configuration-preferences/1";
161
162
    private static final String EL_PREFERENCES = "preferences";
163
    private static final String EL_MODULE = "module";
164
    private static final String EL_PROPERTY = "property";
165
    private static final String EL_NODE = "node";
166
    
167
    private static final String ATTR_NAME = "name";
168
    private static final String ATTR_VALUE = "value";
169
    
170
    private static final String PREF_NODE_AUXILIARY_CONFIG_BASE_PREFERENCES = "auxiliaryConfigBasedPreferences";
171
    private static final String VALID_KEY_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTVUWXYZabcdefghijklmnopqrstvuwxyz0123456789";
172
    
173
    private static final RequestProcessor WORKER = new RequestProcessor("AuxiliaryConfigBasedPreferencesProvider worker", 1);
174
    private static final int AUTOFLUSH_TIMEOUT = 5000;
175
176
    private final Project project;
177
    private final AuxiliaryConfiguration ac;
178
    private final Map<String, Reference<AuxiliaryConfigBasedPreferences>> module2Preferences = new HashMap<String, Reference<AuxiliaryConfigBasedPreferences>>();
179
    private Element configRoot;
180
    private boolean modified;
181
    private final Task autoFlushTask = WORKER.create(new Runnable() {
182
        public void run() {
183
            flush();
184
        }
185
    });
186
    
187
    private final Map<String, Map<String, String>> path2Data = new HashMap<String, Map<String, String>>();
188
    private final Map<String, Set<String>> path2Removed = new HashMap<String, Set<String>>();
189
    private final Set<String> removedNodes = new HashSet<String>();
190
    private final Set<String> createdNodes = new HashSet<String>();
191
192
    AuxiliaryConfigBasedPreferencesProvider(Project project, AuxiliaryConfiguration ac) {
193
        this.project = project;
194
        this.ac = ac;
195
        loadConfigRoot();
196
    }
197
    
198
    private void loadConfigRoot() {
199
        Element configRootLoc = ac.getConfigurationFragment(EL_PREFERENCES, NAMESPACE, true);
200
201
        if (configRootLoc == null) {
202
            configRootLoc = XMLUtil.createDocument(EL_PREFERENCES, NAMESPACE, null, null).createElementNS(NAMESPACE,
203
                    EL_PREFERENCES);
204
        }
205
206
        this.configRoot = configRootLoc;
207
    }
208
    
209
    synchronized void flush() {
210
        if (!modified) {
211
            return ;
212
        }
213
        
214
        for (String removedNode : removedNodes) {
215
            Element el = findRelative(removedNode, false);
216
            
217
            if (el != null) {
218
                el.getParentNode().removeChild(el);
219
            }
220
        }
221
        
222
        for (String createdNode : createdNodes) {
223
            findRelative(createdNode, true);
224
        }
225
        
226
        for (Entry<String, Map<String, String>> e : path2Data.entrySet()) {
227
            Element el = findRelative(e.getKey(), false);
228
            
229
            assert el != null;
230
            
231
            for (Entry<String, String> value : e.getValue().entrySet()) {
232
                Element p = find(el, value.getKey(), EL_PROPERTY, true);
233
                
234
                p.setAttribute(ATTR_VALUE, value.getValue());
235
            }
236
        }
237
        
238
        for (Entry<String, Set<String>> e : path2Removed.entrySet()) {
239
            Element el = findRelative(e.getKey(), false);
240
            
241
            assert el != null;
242
            
243
            for (String removed : e.getValue()) {
244
                Element p = find(el, removed, EL_PROPERTY, true);
245
                
246
                el.removeChild(p);
247
            }
248
        }
249
        
250
        ac.putConfigurationFragment(configRoot, true);
251
        
252
        try {
253
            ProjectManager.getDefault().saveProject(project);
254
        } catch (IOException ex) {
255
            Exceptions.printStackTrace(ex);
256
        }
257
        
258
        path2Data.clear();
259
        path2Removed.clear();
260
        removedNodes.clear();
261
        createdNodes.clear();
262
        modified = false;
263
    }
264
    
265
    synchronized void sync() {
266
        loadConfigRoot();
267
        flush();
268
    }
269
    
270
    private void markModified() {
271
        autoFlushTask.cancel();
272
        autoFlushTask.schedule(AUTOFLUSH_TIMEOUT);
273
        modified = true;
274
    }
275
    
276
    private static String findCNBForClass(Class c) {
277
        Preferences p = NbPreferences.forModule(c);
278
        
279
        return p.absolutePath().replaceFirst("/", "").replace('/', '.');
280
    }
281
    
282
    public synchronized Preferences findModule(String moduleName) {
283
        Reference<AuxiliaryConfigBasedPreferences> prefRef = module2Preferences.get(moduleName);
284
        AuxiliaryConfigBasedPreferences pref = prefRef != null ? prefRef.get() : null;
285
        
286
        if (pref == null) {
287
            module2Preferences.put(moduleName, new CleaningWeakReference<AuxiliaryConfigBasedPreferences>(pref = new AuxiliaryConfigBasedPreferences(null, "", moduleName), module2Preferences, moduleName));
288
        }
289
        
290
        return pref;
291
    }
292
    
293
    private Element findRelative(String path, boolean createIfMissing) {
294
        String[] sep = path.split("/");
295
        
296
        assert sep.length > 0;
297
        
298
        Element e = find(configRoot, sep[0], EL_MODULE, createIfMissing);
299
        
300
        for (int cntr = 1; cntr < sep.length && e != null; cntr++) {
301
            e = find(e, sep[cntr], EL_NODE, createIfMissing);
302
        }
303
        
304
        return e;
305
    }
306
307
    private Map<String, String> getData(String path) {
308
        Map<String, String> data = path2Data.get(path);
309
        
310
        if (data == null) {
311
            path2Data.put(path, data = new HashMap<String, String>());
312
        }
313
        
314
        return data;
315
    }
316
    
317
    private Set<String> getRemoved(String path) {
318
        Set<String> removed = path2Removed.get(path);
319
        
320
        if (removed == null) {
321
            path2Removed.put(path, removed = new HashSet<String>());
322
        }
323
        
324
        return removed;
325
    }
326
    
327
    private void removeNode(String path) {
328
        path2Data.remove(path);
329
        path2Removed.remove(path);
330
        createdNodes.remove(path);
331
        removedNodes.add(path);
332
    }
333
    
334
    private boolean isRemovedNode(String path) {
335
        return removedNodes.contains(path);
336
    }
337
    
338
    private static Element find(Element dom, String key, String elementName, boolean createIfMissing) {
339
        NodeList nl = dom.getChildNodes();
340
        
341
        for (int cntr = 0; cntr < nl.getLength(); cntr++) {
342
            Node n = nl.item(cntr);
343
            
344
            if (n.getNodeType() == Node.ELEMENT_NODE && NAMESPACE.equals(n.getNamespaceURI()) && elementName.equals(n.getLocalName())) {
345
                if (key.equals(((Element) n).getAttribute(ATTR_NAME))) {
346
                    return (Element) n;
347
                }
348
            }
349
        }
350
        
351
        if (!createIfMissing) {
352
            return null;
353
        }
354
        
355
        Element el = dom.getOwnerDocument().createElementNS(NAMESPACE, elementName);
356
        
357
        el.setAttribute(ATTR_NAME, key);
358
        
359
        dom.appendChild(el);
360
        
361
        return el;
362
    }
363
364
    private class AuxiliaryConfigBasedPreferences extends AbstractPreferences {
365
366
        private final String path;
367
        
368
        public AuxiliaryConfigBasedPreferences(AbstractPreferences parent, String name, String path) {
369
            super(parent, name);
370
            this.path = path;
371
        }
372
373
        @Override
374
        protected void putSpi(String key, String value) {
375
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
376
                getData(path).put(key, value);
377
                getRemoved(path).remove(key);
378
379
                markModified();
380
            }
381
        }
382
383
        @Override
384
        protected String getSpi(String key) {
385
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
386
                if (getRemoved(path).contains(key)) {
387
                    return null;
388
                }
389
390
                if (getData(path).containsKey(key)) {
391
                    return getData(path).get(key);
392
                }
393
394
                if (isRemovedNode(path)) {
395
                    return null;
396
                }
397
398
                Element p = findRelative(path, false);
399
400
                p = p != null ? AuxiliaryConfigBasedPreferencesProvider.find(p, key, EL_PROPERTY, false) : null;
401
402
                if (p == null) {
403
                    return null;
404
                }
405
406
                return p.getAttribute(ATTR_VALUE);
407
            }
408
        }
409
410
        @Override
411
        protected void removeSpi(String key) {
412
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
413
                getData(path).remove(key);
414
                getRemoved(path).add(key);
415
416
                markModified();
417
            }
418
        }
419
420
        @Override
421
        protected void removeNodeSpi() throws BackingStoreException {
422
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
423
                AuxiliaryConfigBasedPreferencesProvider.this.removeNode(path);
424
                markModified();
425
            }
426
        }
427
428
        @Override
429
        protected String[] keysSpi() throws BackingStoreException {
430
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
431
                Collection<String> result = new LinkedHashSet<String>();
432
433
                if (!isRemovedNode(path)) {
434
                    result.addAll(list(EL_PROPERTY));
435
                }
436
437
                result.addAll(getData(path).keySet());
438
                result.removeAll(getRemoved(path));
439
440
                return result.toArray(new String[0]);
441
            }
442
        }
443
444
        @Override
445
        protected String[] childrenNamesSpi() throws BackingStoreException {
446
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
447
                return getChildrenNames().toArray(new String[0]);
448
            }
449
        }
450
451
        @Override
452
        protected AbstractPreferences childSpi(String name) {
453
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
454
                String nuePath = path + "/" + name;
455
                if (!getChildrenNames().contains("name")) {
456
                    AuxiliaryConfigBasedPreferencesProvider.this.createdNodes.add(nuePath);
457
                }
458
459
                return new AuxiliaryConfigBasedPreferences(this, name, nuePath);
460
            }
461
        }
462
463
        @Override
464
        public void sync() throws BackingStoreException {
465
            AuxiliaryConfigBasedPreferencesProvider.this.sync();
466
        }
467
468
        @Override
469
        protected void syncSpi() throws BackingStoreException {
470
            throw new UnsupportedOperationException("Should never be called.");
471
        }
472
473
        @Override
474
        public void flush() throws BackingStoreException {
475
            AuxiliaryConfigBasedPreferencesProvider.this.flush();
476
        }
477
478
        @Override
479
        protected void flushSpi() throws BackingStoreException {
480
            throw new UnsupportedOperationException("Should never be called.");
481
        }
482
483
        private Collection<String> getChildrenNames() {
484
            Collection<String> result = new LinkedHashSet<String>();
485
486
            if (!isRemovedNode(path)) {
487
                result.addAll(list(EL_NODE));
488
            }
489
            
490
            for (String removed : removedNodes) {
491
                int slash = removed.lastIndexOf('/');
492
                
493
                if (path.equals(removed.substring(slash))) {
494
                    result.remove(removed.substring(slash + 1));
495
                }
496
            }
497
            for (String created : createdNodes) {
498
                int slash = created.lastIndexOf('/');
499
                
500
                if (path.equals(created.substring(slash))) {
501
                    result.add(created.substring(slash + 1));
502
                }
503
            }
504
505
            return result;
506
        }
507
508
        private Collection<String> list(String elementName) throws DOMException {
509
            Element dom = findRelative(path, false);
510
            
511
            if (dom == null) {
512
                return Collections.emptyList();
513
            }
514
            
515
            List<String> names = new LinkedList<String>();
516
            NodeList nl = dom.getElementsByTagNameNS(NAMESPACE, elementName);
517
518
            for (int cntr = 0; cntr < nl.getLength(); cntr++) {
519
                Node n = nl.item(cntr);
520
521
                names.add(((Element) n).getAttribute(ATTR_NAME));
522
            }
523
524
            return names;
525
        }
526
527
    }
528
    
529
    private static final class CleaningWeakReference<T> extends WeakReference<T> implements Runnable {
530
        private final Map<?, ?> map;
531
        private final Object key;
532
533
        public CleaningWeakReference(T data, Map<?, ?> map, Object key) {
534
            super(data, Utilities.activeReferenceQueue());
535
            this.map = map;
536
            this.key = key;
537
        }
538
539
        public void run() {
540
            map.remove(key);
541
        }
542
    }
543
}
(-)a/projectapi/src/org/netbeans/modules/projectapi/resources/auxiliary-configuration-preferences.xsd (+78 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8"?>
2
<!--
3
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
5
Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
6
7
8
The contents of this file are subject to the terms of either the GNU
9
General Public License Version 2 only ("GPL") or the Common
10
Development and Distribution License("CDDL") (collectively, the
11
"License"). You may not use this file except in compliance with the
12
License. You can obtain a copy of the License at
13
http://www.netbeans.org/cddl-gplv2.html
14
or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
15
specific language governing permissions and limitations under the
16
License.  When distributing the software, include this License Header
17
Notice in each file and include the License file at
18
nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
19
particular file as subject to the "Classpath" exception as provided
20
by Sun in the GPL Version 2 section of the License file that
21
accompanied this code. If applicable, add the following below the
22
License Header, with the fields enclosed by brackets [] replaced by
23
your own identifying information:
24
"Portions Copyrighted [year] [name of copyright owner]"
25
26
Contributor(s):
27
28
The Original Software is NetBeans. The Initial Developer of the Original
29
Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
30
Microsystems, Inc. All Rights Reserved.
31
32
If you wish your version of this file to be governed by only the CDDL
33
or only the GPL Version 2, indicate your decision by adding
34
"[Contributor] elects to include this software in this distribution
35
under the [CDDL or GPL Version 2] license." If you do not indicate a
36
single choice of license, a recipient has the option to distribute
37
your version of this file under either the CDDL, the GPL Version 2 or
38
to extend the choice of license to its licensees as provided above.
39
However, if you add GPL Version 2 code and therefore, elected the GPL
40
Version 2 license, then the option applies only if the new code is
41
made subject to such option by the copyright holder.
42
-->
43
44
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
45
            targetNamespace="http://www.netbeans.org/ns/auxiliary-configuration-preferences/1"
46
            xmlns="http://www.netbeans.org/ns/auxiliary-configuration-preferences/1"
47
            elementFormDefault="qualified">
48
49
    <xsd:element name="preferences">
50
        <xsd:complexType>
51
            <xsd:sequence>
52
                <xsd:element name="module" minOccurs="0" maxOccurs="unbounded" type="module"/>
53
            </xsd:sequence>
54
        </xsd:complexType>
55
    </xsd:element>
56
57
    <xsd:element name="module-or-node">
58
        <xsd:simpleContent>
59
            <xsd:attribute name="name" />
60
        </xsd:simpleContent>
61
        <xsd:complexType>
62
            <xsd:sequence>
63
                <xsd:choice minOccurs="0" maxOccurs=unbounded">
64
                    <xsd:element name="property" type="property"/>
65
                    <xsd:element name="node" type="module-or-node"/>
66
                </xsd:choice>
67
            </xsd:sequence>
68
        </xsd:complexType>
69
    </xsd:element>
70
71
    <xsd:element name="property">
72
        <xsd:simpleContent>
73
            <xsd:attribute name="name" />
74
            <xsd:attribute name="value" />
75
        </xsd:simpleContent>
76
    </xsd:element>
77
78
</xsd:schema>
(-)a/projectapi/test/unit/src/org/netbeans/modules/projectapi/AuxiliaryConfigBasedPreferencesProviderTest.java (+210 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 2008 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.modules.projectapi;
43
44
import java.io.File;
45
import java.io.IOException;
46
import java.lang.ref.Reference;
47
import java.lang.ref.WeakReference;
48
import java.util.Arrays;
49
import java.util.concurrent.atomic.AtomicInteger;
50
import java.util.prefs.BackingStoreException;
51
import java.util.prefs.Preferences;
52
import org.netbeans.api.project.Project;
53
import org.netbeans.api.project.ProjectManager;
54
import org.netbeans.junit.NbTestCase;
55
import org.netbeans.modules.java.j2seproject.J2SEProjectGenerator;
56
import org.netbeans.spi.project.AuxiliaryConfiguration;
57
import org.openide.filesystems.FileObject;
58
import org.openide.filesystems.FileUtil;
59
import org.w3c.dom.Element;
60
61
/**
62
 *
63
 * @author Jan Lahoda
64
 */
65
public class AuxiliaryConfigBasedPreferencesProviderTest extends NbTestCase {
66
    
67
    public AuxiliaryConfigBasedPreferencesProviderTest(String testName) {
68
        super(testName);
69
    }            
70
71
    private FileObject fo;
72
    private Project p;
73
    
74
    @Override
75
    protected void setUp() throws Exception {
76
        clearWorkDir();
77
        File wd = getWorkDir();
78
        FileUtil.refreshAll();
79
        File f = new File(wd, "test");
80
        J2SEProjectGenerator.createProject(f, "test", null, null, null);
81
        fo = FileUtil.toFileObject(f);
82
        assertNotNull(fo);
83
        p = ProjectManager.getDefault().findProject(fo);
84
        assertNotNull(p);
85
    }
86
87
    public void testStorage() throws IOException, BackingStoreException {
88
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
89
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
90
        Preferences pref = provider.findModule("test");
91
        
92
        pref.put("test", "test");
93
        
94
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
95
        
96
        assertEquals(Arrays.asList("somekey"), Arrays.asList(pref.node("subnode1/subnode2").keys()));
97
        
98
        pref.flush();
99
        
100
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
101
        pref = provider.findModule("test");
102
        
103
        assertEquals("test", pref.get("test", null));
104
        assertEquals("somevalue", pref.node("subnode1/subnode2").get("somekey", null));
105
        assertEquals(Arrays.asList("somekey"), Arrays.asList(pref.node("subnode1/subnode2").keys()));
106
        pref.node("subnode1/subnode2").remove("somekey");
107
        assertEquals(Arrays.<String>asList(), Arrays.asList(pref.node("subnode1/subnode2").keys()));
108
    }
109
110
    public void testNoSaveWhenNotModified() throws IOException, BackingStoreException {
111
        final AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
112
        final AtomicInteger putCount = new AtomicInteger();
113
        
114
        AuxiliaryConfiguration newAC = new AuxiliaryConfiguration() {
115
            public Element getConfigurationFragment(String elementName, String namespace, boolean shared) {
116
                return ac.getConfigurationFragment(elementName, namespace, shared);
117
            }
118
            public void putConfigurationFragment(Element fragment, boolean shared) throws IllegalArgumentException {
119
                putCount.incrementAndGet();
120
                ac.putConfigurationFragment(fragment, shared);
121
            }
122
            public boolean removeConfigurationFragment(String elementName, String namespace, boolean shared) throws IllegalArgumentException {
123
                return ac.removeConfigurationFragment(elementName, namespace, shared);
124
            }
125
        };
126
        
127
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, newAC);
128
        Preferences pref = provider.findModule("test");
129
        
130
        pref.put("test", "test");
131
        
132
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
133
        
134
        assertEquals(0, putCount.get());
135
        pref.flush();
136
        assertEquals(1, putCount.get());
137
        pref.flush();
138
        assertEquals(1, putCount.get());
139
    }
140
    
141
    public void testSubnodes() throws IOException, BackingStoreException {
142
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
143
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
144
        Preferences pref = provider.findModule("test");
145
146
        pref.put("test", "test");
147
148
        pref.node("subnode1/subnode2").put("somekey", "somevalue1");
149
        pref.node("subnode1").put("somekey", "somevalue2");
150
151
        pref.flush();
152
        
153
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
154
        pref = provider.findModule("test");
155
        
156
        assertEquals("somevalue1", pref.node("subnode1/subnode2").get("somekey", null));
157
        assertEquals("somevalue2", pref.node("subnode1").get("somekey", null));
158
        pref.node("subnode1").removeNode();
159
        assertEquals(null, pref.node("subnode1/subnode2").get("somekey", null));
160
        assertEquals(null, pref.node("subnode1").get("somekey", null));
161
162
        pref.flush();
163
164
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
165
        pref = provider.findModule("test");
166
167
        assertEquals(null, pref.node("subnode1/subnode2").get("somekey", null));
168
        assertEquals(null, pref.node("subnode1").get("somekey", null));
169
    }
170
    
171
    public void testSync() throws IOException, BackingStoreException {
172
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
173
        AuxiliaryConfigBasedPreferencesProvider toSync = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
174
        Preferences pref = toSync.findModule("test");
175
        
176
        pref.put("test", "test");
177
        
178
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
179
        pref.flush();
180
        
181
        AuxiliaryConfigBasedPreferencesProvider orig = new AuxiliaryConfigBasedPreferencesProvider(p, ac);
182
        
183
        Preferences origNode = orig.findModule("test").node("subnode1/subnode2");
184
        
185
        pref.node("subnode1/subnode2").put("somekey", "somevalue2");
186
        pref.flush();
187
        
188
        origNode.sync();
189
        
190
        assertEquals("somevalue2", origNode.get("somekey", null));
191
    }
192
    
193
    public void testReclaimable() throws IOException, BackingStoreException {
194
        Preferences pref = AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true);
195
        
196
        //the same preferences instance is returned as long as the previous one exists:
197
        assertTrue(pref == AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true));
198
        
199
        //but the preferences can be reclaimed, as well as the project if noone holds them:
200
        Reference<Preferences> rPref = new WeakReference<Preferences>(pref);
201
        Reference<Project> rProject = new WeakReference<Project>(p);
202
        
203
        p = null;
204
        pref = null;
205
        
206
        assertGC("", rPref);
207
        assertGC("", rProject);
208
    }
209
    
210
}

Return to bug 134580