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/java.j2seproject/nbproject/project.xml (-1 / +1 lines)
Lines 171-177 Link Here
171
                    <compile-dependency/>
171
                    <compile-dependency/>
172
                    <run-dependency>
172
                    <run-dependency>
173
                        <release-version>1</release-version>
173
                        <release-version>1</release-version>
174
                        <specification-version>1.17</specification-version>
174
                        <specification-version>1.21</specification-version>
175
                    </run-dependency>
175
                    </run-dependency>
176
                </dependency>
176
                </dependency>
177
                <dependency>
177
                <dependency>
(-)a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java (+1 lines)
Lines 260-265 Link Here
260
            new Info(),
260
            new Info(),
261
            aux,
261
            aux,
262
            helper.createCacheDirectoryProvider(),
262
            helper.createCacheDirectoryProvider(),
263
            helper.createAuxiliaryProperties(),
263
            spp,
264
            spp,
264
            actionProvider,
265
            actionProvider,
265
            new J2SELogicalViewProvider(this, this.updateHelper, evaluator(), spp, refHelper),
266
            new J2SELogicalViewProvider(this, this.updateHelper, evaluator(), spp, refHelper),
(-)a/project.ant/apichanges.xml (+16 lines)
Lines 105-110 Link Here
105
105
106
    <changes>
106
    <changes>
107
107
108
        <change id="AntProjectHelper-createAuxiliaryProperties">
109
            <api name="general"/>
110
            <summary>Added AntProjectHelper.createAuxiliaryProperties()</summary>
111
            <version major="1" minor="21"/>
112
            <date day="9" month="6" year="2008"/>
113
            <author login="jlahoda"/>
114
            <compatibility addition="yes"/>
115
            <description>
116
                <p>
117
                    A new API method <code>AntProjectHelper.createAuxiliaryProperties</code>.
118
                </p>
119
            </description>
120
            <class package="org.netbeans.spi.project.support.ant" name="AntProjectHelper"/>
121
            <issue number="134580"/>
122
        </change>
123
        
108
        <change id="ant-project-libraries">
124
        <change id="ant-project-libraries">
109
            <api name="general"/>
125
            <api name="general"/>
110
            <summary>Support for project-specific libraries</summary>
126
            <summary>Support for project-specific libraries</summary>
(-)a/project.ant/manifest.mf (-1 / +1 lines)
Lines 1-6 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.project.ant/1
2
OpenIDE-Module: org.netbeans.modules.project.ant/1
3
OpenIDE-Module-Specification-Version: 1.20
3
OpenIDE-Module-Specification-Version: 1.21
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties
5
OpenIDE-Module-Install: org/netbeans/modules/project/ant/AntProjectModule.class
5
OpenIDE-Module-Install: org/netbeans/modules/project/ant/AntProjectModule.class
6
6
(-)a/project.ant/src/org/netbeans/spi/project/support/ant/AntProjectHelper.java (+16 lines)
Lines 64-69 Link Here
64
import org.netbeans.modules.project.ant.UserQuestionHandler;
64
import org.netbeans.modules.project.ant.UserQuestionHandler;
65
import org.netbeans.modules.project.ant.Util;
65
import org.netbeans.modules.project.ant.Util;
66
import org.netbeans.spi.project.AuxiliaryConfiguration;
66
import org.netbeans.spi.project.AuxiliaryConfiguration;
67
import org.netbeans.spi.project.AuxiliaryProperties;
67
import org.netbeans.spi.project.CacheDirectoryProvider;
68
import org.netbeans.spi.project.CacheDirectoryProvider;
68
import org.netbeans.spi.project.ProjectState;
69
import org.netbeans.spi.project.ProjectState;
69
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
70
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
Lines 930-935 Link Here
930
    }
931
    }
931
    
932
    
932
    /**
933
    /**
934
     * Create an object permitting this project to expose {@link AuxiliaryProperties}.
935
     * Would be placed into the project's lookup.
936
     * 
937
     * This implementation places the properties into {@link #PROJECT_PROPERTIES_PATH}
938
     * or {@link #PRIVATE_PROPERTIES_PATH} (depending on shared value). The properties are
939
     * prefixed with "<code>auxiliary.</code>".
940
     * 
941
     * @return an instance of {@link AuxiliaryProperties} suitable for the project lookup
942
     * @since 1.21
943
     */
944
    public AuxiliaryProperties createAuxiliaryProperties() {
945
        return new AuxiliaryPropertiesImpl(this);
946
    }
947
    
948
    /**
933
     * Create an implementation of {@link org.netbeans.api.queries.FileBuiltQuery} that works with files
949
     * Create an implementation of {@link org.netbeans.api.queries.FileBuiltQuery} that works with files
934
     * within the project based on simple glob pattern mappings.
950
     * within the project based on simple glob pattern mappings.
935
     * <p>
951
     * <p>
(-)a/projectapi/apichanges.xml (+18 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 new API method <code>ProjectUtils.getPreferences</code> and new SPI interface <code>AuxiliaryProperties</code>.
118
		</p>
119
            </description>
120
            <class package="org.netbeans.api.project" name="ProjectUtils"/>
121
            <class package="org.netbeans.spi.project" name="AuxiliaryProperties"/>
122
            <issue number="134580"/>
123
        </change>
124
        
107
        <change id="lookup-provider">
125
        <change id="lookup-provider">
108
            <api name="general"/>
126
            <api name="general"/>
109
            <summary>Added support for composing project's lookup from multiple sources.</summary>
127
            <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 / +16 lines)
Lines 72-77 Link Here
72
                    </run-dependency>
72
                    </run-dependency>
73
                </dependency>
73
                </dependency>
74
                <dependency>
74
                <dependency>
75
                    <code-name-base>org.openide.modules</code-name-base>
76
                    <build-prerequisite/>
77
                    <compile-dependency/>
78
                    <run-dependency>
79
                        <specification-version>7.7</specification-version>
80
                    </run-dependency>
81
                </dependency>
82
                <dependency>
75
                    <code-name-base>org.openide.nodes</code-name-base>
83
                    <code-name-base>org.openide.nodes</code-name-base>
76
                    <build-prerequisite/>
84
                    <build-prerequisite/>
77
                    <compile-dependency/>
85
                    <compile-dependency/>
Lines 92-114 Link Here
92
                <test-type>
100
                <test-type>
93
                    <name>unit</name>
101
                    <name>unit</name>
94
                    <test-dependency>
102
                    <test-dependency>
103
                        <code-name-base>org.netbeans.modules.masterfs</code-name-base>
104
                    </test-dependency>
105
                    <test-dependency>
95
                        <code-name-base>org.netbeans.modules.projectapi</code-name-base>
106
                        <code-name-base>org.netbeans.modules.projectapi</code-name-base>
96
                        <recursive/>
107
                        <recursive/>
97
                        <compile-dependency/>
108
                        <compile-dependency/>
98
                    </test-dependency>
109
                    </test-dependency>
99
                    <test-dependency>
110
                    <test-dependency>
100
                        <code-name-base>org.openide.modules</code-name-base>
111
                        <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/>
112
                        <compile-dependency/>
108
                        <test/>
113
                        <test/>
109
                    </test-dependency>
114
                    </test-dependency>
110
                    <test-dependency>
115
                    <test-dependency>
111
                        <code-name-base>org.openide.filesystems</code-name-base>
116
                        <code-name-base>org.openide.modules</code-name-base>
117
                    </test-dependency>
118
                    <test-dependency>
119
                        <code-name-base>org.openide.util</code-name-base>
112
                        <compile-dependency/>
120
                        <compile-dependency/>
113
                        <test/>
121
                        <test/>
114
                    </test-dependency>
122
                    </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.
150
     * 
151
     * @param project project for which preferences should be returned
152
     * @param clazz module specification as in {@link org.openide.util.NbPreferences#forModule(java.lang.Class)}
153
     * @param shared whether the returned settings should be shared
154
     * @return {@link Preferences} for the given project or null if the required
155
     *         {@link Preferences} are not supported for the given project.
156
     * @since 1.16
157
     */
158
    public static Preferences getPreferences(Project project, Class clazz, boolean shared) {
159
        Parameters.notNull("project", project);
160
        Parameters.notNull("clazz", clazz);
161
        
162
        return AuxiliaryConfigBasedPreferencesProvider.getPreferences(project, clazz, 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 (+690 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.Enumeration;
50
import java.util.HashMap;
51
import java.util.HashSet;
52
import java.util.LinkedHashSet;
53
import java.util.LinkedList;
54
import java.util.List;
55
import java.util.Map;
56
import java.util.Map.Entry;
57
import java.util.Set;
58
import java.util.prefs.AbstractPreferences;
59
import java.util.prefs.BackingStoreException;
60
import java.util.prefs.Preferences;
61
import org.netbeans.api.project.Project;
62
import org.netbeans.api.project.ProjectManager;
63
import org.netbeans.spi.project.AuxiliaryConfiguration;
64
import org.netbeans.spi.project.AuxiliaryProperties;
65
import org.openide.filesystems.FileObject;
66
import org.openide.modules.ModuleInfo;
67
import org.openide.util.Exceptions;
68
import org.openide.util.Lookup;
69
import org.openide.util.RequestProcessor;
70
import org.openide.util.RequestProcessor.Task;
71
import org.openide.util.Utilities;
72
import org.openide.xml.XMLUtil;
73
import org.w3c.dom.DOMException;
74
import org.w3c.dom.Element;
75
import org.w3c.dom.Node;
76
import org.w3c.dom.NodeList;
77
78
/**
79
 * @author Jan Lahoda
80
 */
81
public class AuxiliaryConfigBasedPreferencesProvider {
82
    
83
    private static Map<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>> projects2SharedPrefs = new HashMap<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>>();
84
    private static Map<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>> projects2PrivatePrefs = new HashMap<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>>();
85
    
86
    static synchronized AuxiliaryConfigBasedPreferencesProvider findProvider(Project p, boolean shared) {
87
        Map<Project, Reference<AuxiliaryConfigBasedPreferencesProvider>> target = shared ? projects2SharedPrefs : projects2PrivatePrefs;
88
        Reference<AuxiliaryConfigBasedPreferencesProvider> provRef = target.get(p);
89
        AuxiliaryConfigBasedPreferencesProvider prov = provRef != null ? provRef.get() : null;
90
        
91
        if (prov != null) {
92
            return prov;
93
        }
94
        
95
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
96
        AuxiliaryProperties ap = p.getLookup().lookup(AuxiliaryProperties.class);
97
        
98
        if (ac != null || ap != null) {
99
            target.put(p, new CleaningWeakReference<AuxiliaryConfigBasedPreferencesProvider>(prov = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, shared), target, p));
100
        } else {
101
            if (!shared) {
102
                ap = new FallbackAuxiliaryPropertiesImpl(p.getProjectDirectory());
103
                target.put(p, new CleaningWeakReference<AuxiliaryConfigBasedPreferencesProvider>(prov = new AuxiliaryConfigBasedPreferencesProvider(p, null, ap, shared), target, p));
104
            }
105
        }
106
        
107
        return prov;
108
    }
109
    
110
    public static Preferences getPreferences(Project project, Class clazz, boolean shared) {
111
        AuxiliaryConfigBasedPreferencesProvider provider = findProvider(project, shared);
112
113
        if (provider == null) {
114
            return null;
115
        }
116
        
117
        return provider.findModule(AuxiliaryConfigBasedPreferencesProvider.findCNBForClass(clazz));
118
    }
119
120
    private static String encodeString(String s, Character additionalValid) {
121
        StringBuilder result = new StringBuilder();
122
        
123
        for (char c : s.toCharArray()) {
124
            if (VALID_KEY_CHARACTERS.indexOf(c) != (-1) || (additionalValid != null && ((char) additionalValid) == c)) {
125
                result.append(c);
126
            } else {
127
                result.append("_");
128
                result.append(Integer.toHexString((int) c));
129
                result.append("_");
130
            }
131
        }
132
        
133
        return result.toString();
134
    }
135
    
136
    private static String decodeString(String s) {
137
        StringBuilder result = new StringBuilder();
138
        String[]      parts  = s.split("_");
139
        
140
        for (int cntr = 0; cntr < parts.length; cntr += 2) {
141
            result.append(parts[cntr]);
142
                
143
            if (cntr + 1 < parts.length) {
144
                result.append((char) Integer.parseInt(parts[cntr + 1], 16));
145
            }
146
        }
147
        
148
        return result.toString();
149
    }
150
    
151
            static final String NAMESPACE = "http://www.netbeans.org/ns/auxiliary-configuration-preferences/1";
152
153
            static final String EL_PREFERENCES = "preferences";
154
    private static final String EL_MODULE = "module";
155
    private static final String EL_PROPERTY = "property";
156
    private static final String EL_NODE = "node";
157
    
158
    private static final String ATTR_NAME = "name";
159
    private static final String ATTR_VALUE = "value";
160
    
161
    private static final String VALID_KEY_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTVUWXYZabcdefghijklmnopqrstvuwxyz0123456789-";
162
    
163
    private static final RequestProcessor WORKER = new RequestProcessor("AuxiliaryConfigBasedPreferencesProvider worker", 1);
164
    private static final int AUTOFLUSH_TIMEOUT = 5000;
165
166
    private final Project project;
167
    private final AuxiliaryConfiguration ac;
168
    private final AuxiliaryProperties ap;
169
    private final boolean shared;
170
    private final Map<String, Reference<AuxiliaryConfigBasedPreferences>> module2Preferences = new HashMap<String, Reference<AuxiliaryConfigBasedPreferences>>();
171
    private Element configRoot;
172
    private boolean modified;
173
    private final Task autoFlushTask = WORKER.create(new Runnable() {
174
        public void run() {
175
            flush();
176
        }
177
    });
178
    
179
    private final Map<String, Map<String, String>> path2Data = new HashMap<String, Map<String, String>>();
180
    private final Map<String, Set<String>> path2Removed = new HashMap<String, Set<String>>();
181
    private final Set<String> removedNodes = new HashSet<String>();
182
    private final Set<String> createdNodes = new HashSet<String>();
183
184
    AuxiliaryConfigBasedPreferencesProvider(Project project, AuxiliaryConfiguration ac, AuxiliaryProperties ap, boolean shared) {
185
        this.project = project;
186
        this.ac = ac;
187
        this.ap = ap;
188
        this.shared = shared;
189
        loadConfigRoot();
190
    }
191
    
192
    private void loadConfigRoot() {
193
        if (ac == null) {
194
            return ;
195
        }
196
        
197
        Element configRootLoc = ac.getConfigurationFragment(EL_PREFERENCES, NAMESPACE, shared);
198
199
        if (configRootLoc == null) {
200
            configRootLoc = XMLUtil.createDocument(EL_PREFERENCES, NAMESPACE, null, null).createElementNS(NAMESPACE,
201
                    EL_PREFERENCES);
202
        }
203
204
        this.configRoot = configRootLoc;
205
    }
206
    
207
    synchronized void flush() {
208
        if (!modified) {
209
            return ;
210
        }
211
        
212
        boolean domModified = false;
213
        
214
        for (String removedNode : removedNodes) {
215
            if (ac != null) {
216
                Element el = findRelative(removedNode, false);
217
218
                if (el != null) {
219
                    el.getParentNode().removeChild(el);
220
                }
221
                
222
                domModified = true;
223
            }
224
            
225
            if (ap != null) {
226
                String propName = toPropertyName(removedNode, "");
227
                
228
                for (String key : ap.listKeys(shared)) {
229
                    if (key.startsWith(propName)) {
230
                        ap.put(key, null, shared);
231
                    }
232
                }
233
            }
234
        }
235
        
236
        for (String createdNode : createdNodes) {
237
            if (ap != null) {
238
                String propName = toPropertyName(createdNode, "");
239
                
240
                ap.put(propName, "", shared);
241
            } else {
242
                findRelative(createdNode, true);
243
                
244
                domModified = true;
245
            }
246
        }
247
        
248
        for (Entry<String, Map<String, String>> e : path2Data.entrySet()) {
249
            if (ap != null) {
250
                for (Entry<String, String> value : e.getValue().entrySet()) {
251
                    ap.put(toPropertyName(e.getKey(), value.getKey()), value.getValue(), shared);
252
                }
253
            } else {
254
                Element el = findRelative(e.getKey(), true);
255
256
                if (el != null) {
257
                    for (Entry<String, String> value : e.getValue().entrySet()) {
258
                        Element p = find(el, value.getKey(), EL_PROPERTY, true);
259
260
                        p.setAttribute(ATTR_VALUE, value.getValue());
261
                    }
262
263
                    domModified = true;
264
                }
265
            }
266
        }
267
        
268
        for (Entry<String, Set<String>> e : path2Removed.entrySet()) {
269
            if (ac != null) {
270
                Element el = findRelative(e.getKey(), false);
271
272
                if (el != null) {
273
                    for (String removed : e.getValue()) {
274
                        Element p = find(el, removed, EL_PROPERTY, true);
275
276
                        el.removeChild(p);
277
                    }
278
279
                    domModified = true;
280
                }
281
            }
282
            
283
            if (ap != null) {
284
                for (String removed : e.getValue()) {
285
                    ap.put(toPropertyName(e.getKey(), removed), "", shared);
286
                }
287
            }
288
        }
289
        
290
        if (domModified) {
291
            ac.putConfigurationFragment(configRoot, true);
292
        }
293
        
294
        try {
295
            ProjectManager.getDefault().saveProject(project);
296
        } catch (IOException ex) {
297
            Exceptions.printStackTrace(ex);
298
        }
299
        
300
        path2Data.clear();
301
        path2Removed.clear();
302
        removedNodes.clear();
303
        createdNodes.clear();
304
        modified = false;
305
    }
306
    
307
    synchronized void sync() {
308
        loadConfigRoot();
309
        flush();
310
    }
311
    
312
    private void markModified() {
313
        autoFlushTask.cancel();
314
        autoFlushTask.schedule(AUTOFLUSH_TIMEOUT);
315
        modified = true;
316
    }
317
    
318
    private static String findCNBForClass(Class cls) {
319
        String absolutePath = null;
320
        ClassLoader cl = cls.getClassLoader();
321
        for (ModuleInfo module : Lookup.getDefault().lookupAll(ModuleInfo.class)) {
322
            if (module.isEnabled() && module.getClassLoader() == cl) {
323
                absolutePath = module.getCodeNameBase();
324
                break;
325
            }
326
        }
327
        if (absolutePath == null) {
328
            absolutePath = cls.getName().replaceFirst("(^|\\.)[^.]+$", "");//NOI18N
329
        }
330
        assert absolutePath != null;
331
        return absolutePath.replace('.', '-');
332
    }
333
    
334
    public synchronized Preferences findModule(String moduleName) {
335
        Reference<AuxiliaryConfigBasedPreferences> prefRef = module2Preferences.get(moduleName);
336
        AuxiliaryConfigBasedPreferences pref = prefRef != null ? prefRef.get() : null;
337
        
338
        if (pref == null) {
339
            module2Preferences.put(moduleName, new CleaningWeakReference<AuxiliaryConfigBasedPreferences>(pref = new AuxiliaryConfigBasedPreferences(null, "", moduleName), module2Preferences, moduleName));
340
        }
341
        
342
        return pref;
343
    }
344
    
345
    private Element findRelative(String path, boolean createIfMissing) {
346
        if (ac == null) {
347
            return null;
348
        }
349
        
350
        String[] sep = path.split("/");
351
        
352
        assert sep.length > 0;
353
        
354
        Element e = find(configRoot, sep[0], EL_MODULE, createIfMissing);
355
        
356
        for (int cntr = 1; cntr < sep.length && e != null; cntr++) {
357
            e = find(e, sep[cntr], EL_NODE, createIfMissing);
358
        }
359
        
360
        return e;
361
    }
362
363
    private Map<String, String> getData(String path) {
364
        Map<String, String> data = path2Data.get(path);
365
        
366
        if (data == null) {
367
            path2Data.put(path, data = new HashMap<String, String>());
368
        }
369
        
370
        return data;
371
    }
372
    
373
    private Set<String> getRemoved(String path) {
374
        Set<String> removed = path2Removed.get(path);
375
        
376
        if (removed == null) {
377
            path2Removed.put(path, removed = new HashSet<String>());
378
        }
379
        
380
        return removed;
381
    }
382
    
383
    private void removeNode(String path) {
384
        path2Data.remove(path);
385
        path2Removed.remove(path);
386
        createdNodes.remove(path);
387
        removedNodes.add(path);
388
    }
389
    
390
    private boolean isRemovedNode(String path) {
391
        return removedNodes.contains(path);
392
    }
393
    
394
    private static Element find(Element dom, String key, String elementName, boolean createIfMissing) {
395
        NodeList nl = dom.getChildNodes();
396
        
397
        for (int cntr = 0; cntr < nl.getLength(); cntr++) {
398
            Node n = nl.item(cntr);
399
            
400
            if (n.getNodeType() == Node.ELEMENT_NODE && NAMESPACE.equals(n.getNamespaceURI()) && elementName.equals(n.getLocalName())) {
401
                if (key.equals(((Element) n).getAttribute(ATTR_NAME))) {
402
                    return (Element) n;
403
                }
404
            }
405
        }
406
        
407
        if (!createIfMissing) {
408
            return null;
409
        }
410
        
411
        Element el = dom.getOwnerDocument().createElementNS(NAMESPACE, elementName);
412
        
413
        el.setAttribute(ATTR_NAME, key);
414
        
415
        dom.appendChild(el);
416
        
417
        return el;
418
    }
419
    
420
    private String toPropertyName(String path, String propertyName) {
421
        return encodeString(path, '/').replace('/', '.') + '.' + encodeString(propertyName, null);
422
    }
423
424
    private class AuxiliaryConfigBasedPreferences extends AbstractPreferences {
425
426
        private final String path;
427
        
428
        public AuxiliaryConfigBasedPreferences(AbstractPreferences parent, String name, String path) {
429
            super(parent, name);
430
            this.path = path;
431
        }
432
433
        @Override
434
        protected void putSpi(String key, String value) {
435
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
436
                getData(path).put(key, value);
437
                getRemoved(path).remove(key);
438
439
                markModified();
440
            }
441
        }
442
443
        @Override
444
        protected String getSpi(String key) {
445
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
446
                if (getRemoved(path).contains(key)) {
447
                    return null;
448
                }
449
450
                if (getData(path).containsKey(key)) {
451
                    return getData(path).get(key);
452
                }
453
454
                if (isRemovedNode(path)) {
455
                    return null;
456
                }
457
458
                if (ap != null ) {
459
                    String keyProp = toPropertyName(path, key);
460
                    String res = AuxiliaryConfigBasedPreferencesProvider.this.ap.get(keyProp, shared);
461
                    
462
                    if (res != null) {
463
                        return res;
464
                    }
465
                }
466
                Element p = findRelative(path, false);
467
468
                p = p != null ? AuxiliaryConfigBasedPreferencesProvider.find(p, key, EL_PROPERTY, false) : null;
469
470
                if (p == null) {
471
                    return null;
472
                }
473
474
                return p.getAttribute(ATTR_VALUE);
475
            }
476
        }
477
478
        @Override
479
        protected void removeSpi(String key) {
480
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
481
                getData(path).remove(key);
482
                getRemoved(path).add(key);
483
484
                markModified();
485
            }
486
        }
487
488
        @Override
489
        protected void removeNodeSpi() throws BackingStoreException {
490
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
491
                AuxiliaryConfigBasedPreferencesProvider.this.removeNode(path);
492
                markModified();
493
            }
494
        }
495
496
        @Override
497
        protected String[] keysSpi() throws BackingStoreException {
498
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
499
                Collection<String> result = new LinkedHashSet<String>();
500
501
                if (!isRemovedNode(path)) {
502
                    result.addAll(list(EL_PROPERTY));
503
                }
504
                
505
                if (ap != null) {
506
                    String prefix = toPropertyName(path, "");
507
                    
508
                    for (String key : ap.listKeys(shared)) {
509
                        if (key.startsWith(prefix)) {
510
                            String name = key.substring(prefix.length());
511
                            
512
                            if (name.length() > 0 && name.indexOf('.') == (-1)) {
513
                                result.add(decodeString(name));
514
                            }
515
                        }
516
                    }
517
                }
518
519
                result.addAll(getData(path).keySet());
520
                result.removeAll(getRemoved(path));
521
522
                return result.toArray(new String[0]);
523
            }
524
        }
525
526
        @Override
527
        protected String[] childrenNamesSpi() throws BackingStoreException {
528
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
529
                return getChildrenNames().toArray(new String[0]);
530
            }
531
        }
532
533
        @Override
534
        protected AbstractPreferences childSpi(String name) {
535
            synchronized (AuxiliaryConfigBasedPreferencesProvider.this) {
536
                String nuePath = path + "/" + name;
537
                if (!getChildrenNames().contains(name)) {
538
                    AuxiliaryConfigBasedPreferencesProvider.this.createdNodes.add(nuePath);
539
                }
540
541
                return new AuxiliaryConfigBasedPreferences(this, name, nuePath);
542
            }
543
        }
544
545
        @Override
546
        public void sync() throws BackingStoreException {
547
            AuxiliaryConfigBasedPreferencesProvider.this.sync();
548
        }
549
550
        @Override
551
        protected void syncSpi() throws BackingStoreException {
552
            throw new UnsupportedOperationException("Should never be called.");
553
        }
554
555
        @Override
556
        public void flush() throws BackingStoreException {
557
            AuxiliaryConfigBasedPreferencesProvider.this.flush();
558
        }
559
560
        @Override
561
        protected void flushSpi() throws BackingStoreException {
562
            throw new UnsupportedOperationException("Should never be called.");
563
        }
564
565
        private Collection<String> getChildrenNames() {
566
            Collection<String> result = new LinkedHashSet<String>();
567
568
            if (!isRemovedNode(path)) {
569
                result.addAll(list(EL_NODE));
570
            }
571
            
572
            for (String removed : removedNodes) {
573
                int slash = removed.lastIndexOf('/');
574
                
575
                if (path.equals(removed.substring(slash))) {
576
                    result.remove(removed.substring(slash + 1));
577
                }
578
            }
579
            
580
            if (ap != null) {
581
                String prefix = toPropertyName(path, "");
582
583
                for (String key : ap.listKeys(shared)) {
584
                    if (key.startsWith(prefix)) {
585
                        String name = key.substring(prefix.length());
586
587
                        if (name.length() > 0 && name.indexOf('.') != (-1)) {
588
                            name = name.substring(0, name.indexOf('.'));
589
                            result.add(decodeString(name));
590
                        }
591
                    }
592
                }
593
            }
594
                
595
            for (String created : createdNodes) {
596
                int slash = created.lastIndexOf('/');
597
                
598
                if (path.equals(created.substring(slash))) {
599
                    result.add(created.substring(slash + 1));
600
                }
601
            }
602
603
            return result;
604
        }
605
606
        private Collection<String> list(String elementName) throws DOMException {
607
            Element dom = findRelative(path, false);
608
            
609
            if (dom == null) {
610
                return Collections.emptyList();
611
            }
612
            
613
            List<String> names = new LinkedList<String>();
614
            NodeList nl = dom.getElementsByTagNameNS(NAMESPACE, elementName);
615
616
            for (int cntr = 0; cntr < nl.getLength(); cntr++) {
617
                Node n = nl.item(cntr);
618
619
                names.add(((Element) n).getAttribute(ATTR_NAME));
620
            }
621
622
            return names;
623
        }
624
625
    }
626
    
627
    private static final class FallbackAuxiliaryPropertiesImpl implements AuxiliaryProperties {
628
629
        private static final String PREFIX = "auxiliary.";
630
        private FileObject projectDir;
631
632
        public FallbackAuxiliaryPropertiesImpl(FileObject projectDir) {
633
            this.projectDir = projectDir;
634
        }
635
        
636
        public String get(String key, boolean shared) {
637
            assert !shared;
638
            
639
            Object v = projectDir.getAttribute(PREFIX + key);
640
            
641
            return v instanceof String ? (String) v : null;
642
        }
643
644
        public void put(String key, String value, boolean shared) {
645
            assert !shared;
646
            
647
            try {
648
                projectDir.setAttribute(PREFIX + key, value);
649
            } catch (IOException ex) {
650
                Exceptions.printStackTrace(ex);
651
            }
652
        }
653
654
        public Iterable<String> listKeys(boolean shared) {
655
            assert !shared;
656
            
657
            List<String> result = new LinkedList<String>();
658
            
659
            for (Enumeration<String> en = projectDir.getAttributes(); en.hasMoreElements(); ) {
660
                String key = en.nextElement();
661
                
662
                if (key.startsWith(PREFIX)) {
663
                    key = key.substring(PREFIX.length());
664
                    
665
                    if (get(key, shared) != null) {
666
                        result.add(key);
667
                    }
668
                }
669
            }
670
            
671
            return result;
672
        }
673
        
674
    }
675
    
676
    private static final class CleaningWeakReference<T> extends WeakReference<T> implements Runnable {
677
        private final Map<?, ?> map;
678
        private final Object key;
679
680
        public CleaningWeakReference(T data, Map<?, ?> map, Object key) {
681
            super(data, Utilities.activeReferenceQueue());
682
            this.map = map;
683
            this.key = key;
684
        }
685
686
        public void run() {
687
            map.remove(key);
688
        }
689
    }
690
}
(-)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-or-node"/>
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/src/org/netbeans/spi/project/AuxiliaryProperties.java (+84 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
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 * 
35
 * Contributor(s):
36
 * 
37
 * Portions Copyrighted 2008 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.spi.project;
41
42
/**<p>Allow to store arbitrary properties in the project, similarly as {@link AuxiliaryConfiguration}.
43
 * Used as backing store for {@link org.netbeans.api.project.ProjectUtils#getPreferences(org.netbeans.api.project.Project, java.lang.Class, boolean)}.
44
 * </p>
45
 * 
46
 * <p>Note to API clients: do not use this interface directly, use
47
 * {@link org.netbeans.api.project.ProjectUtils#getPreferences(org.netbeans.api.project.Project, java.lang.Class, boolean)} instead.
48
 * </p>
49
 * 
50
 * @author Jan Lahoda
51
 * @since 1.16
52
 */
53
public interface AuxiliaryProperties {
54
55
    /**
56
     * Get a property value.
57
     * 
58
     * @param key name of the property
59
     * @param shared true to look in a sharable settings area, false to look in a private
60
     *               settings area
61
     * @return value of the selected property, or null if not set.
62
     */
63
    public String get(String key, boolean shared);
64
    
65
    /**
66
     * Put a property value.
67
     * 
68
     * @param key name of the property
69
     * @param value value of the property. <code>null</code> will remove the property.
70
     * @param shared true to look in a sharable settings area, false to look in a private
71
     *               settings area
72
     */
73
    public void put(String key, String value, boolean shared);
74
    
75
    /**
76
     * List all keys of all known properties.
77
     * 
78
     * @param shared true to look in a sharable settings area, false to look in a private
79
     *               settings area
80
     * @return known keys.
81
     */
82
    public Iterable<String> listKeys(boolean shared);
83
    
84
}
(-)a/projectapi/test/unit/src/org/netbeans/api/project/TestUtil.java (-3 / +17 lines)
Lines 52-57 Link Here
52
import java.util.WeakHashMap;
52
import java.util.WeakHashMap;
53
import junit.framework.Assert;
53
import junit.framework.Assert;
54
import org.netbeans.junit.NbTestCase;
54
import org.netbeans.junit.NbTestCase;
55
import org.netbeans.spi.project.AuxiliaryConfiguration;
55
import org.netbeans.spi.project.ProjectFactory;
56
import org.netbeans.spi.project.ProjectFactory;
56
import org.netbeans.spi.project.ProjectState;
57
import org.netbeans.spi.project.ProjectState;
57
import org.openide.filesystems.FileObject;
58
import org.openide.filesystems.FileObject;
Lines 60-66 Link Here
60
import org.openide.filesystems.Repository;
61
import org.openide.filesystems.Repository;
61
import org.openide.filesystems.URLMapper;
62
import org.openide.filesystems.URLMapper;
62
import org.openide.util.Lookup;
63
import org.openide.util.Lookup;
64
import org.openide.util.lookup.Lookups;
63
import org.openide.util.test.MockLookup;
65
import org.openide.util.test.MockLookup;
66
import org.openide.xml.XMLUtil;
67
import org.w3c.dom.Document;
68
import org.w3c.dom.Element;
69
import org.w3c.dom.Node;
70
import org.w3c.dom.NodeList;
64
71
65
/**
72
/**
66
 * Help set up org.netbeans.api.project.*Test.
73
 * Help set up org.netbeans.api.project.*Test.
Lines 220-225 Link Here
220
     */
227
     */
221
    public static Object BROKEN_PROJECT_LOAD_LOCK = null;
228
    public static Object BROKEN_PROJECT_LOAD_LOCK = null;
222
    
229
    
230
    /**If non-null, use the value as the Lookup for newly created projects.
231
     * 
232
     */
233
    public static Lookup LOOKUP = null;
234
    
223
    private static final class TestProjectFactory implements ProjectFactory {
235
    private static final class TestProjectFactory implements ProjectFactory {
224
        
236
        
225
        TestProjectFactory() {}
237
        TestProjectFactory() {}
Lines 246-252 Link Here
246
                    }
258
                    }
247
                    throw new IOException("Load failed of " + projectDirectory);
259
                    throw new IOException("Load failed of " + projectDirectory);
248
                } else {
260
                } else {
249
                    return new TestProject(projectDirectory, state);
261
                    return new TestProject(projectDirectory, LOOKUP != null ? LOOKUP : Lookup.EMPTY, state);
250
                }
262
                }
251
            } else {
263
            } else {
252
                return null;
264
                return null;
Lines 279-295 Link Here
279
    private static final class TestProject implements Project {
291
    private static final class TestProject implements Project {
280
        
292
        
281
        private final FileObject dir;
293
        private final FileObject dir;
294
        private final Lookup lookup;
282
        final ProjectState state;
295
        final ProjectState state;
283
        Throwable error;
296
        Throwable error;
284
        int saveCount = 0;
297
        int saveCount = 0;
285
        
298
        
286
        public TestProject(FileObject dir, ProjectState state) {
299
        public TestProject(FileObject dir, Lookup lookup, ProjectState state) {
287
            this.dir = dir;
300
            this.dir = dir;
301
            this.lookup = lookup;
288
            this.state = state;
302
            this.state = state;
289
        }
303
        }
290
        
304
        
291
        public Lookup getLookup() {
305
        public Lookup getLookup() {
292
            return Lookup.EMPTY;
306
            return lookup;
293
        }
307
        }
294
        
308
        
295
        public FileObject getProjectDirectory() {
309
        public FileObject getProjectDirectory() {
(-)a/projectapi/test/unit/src/org/netbeans/modules/projectapi/AuxiliaryConfigBasedPreferencesProviderTest.java (+405 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.Collections;
50
import java.util.Enumeration;
51
import java.util.LinkedList;
52
import java.util.List;
53
import java.util.Properties;
54
import java.util.concurrent.atomic.AtomicInteger;
55
import java.util.prefs.BackingStoreException;
56
import java.util.prefs.Preferences;
57
import org.netbeans.api.project.Project;
58
import org.netbeans.api.project.ProjectManager;
59
import org.netbeans.api.project.TestUtil;
60
import org.netbeans.junit.NbTestCase;
61
import org.netbeans.spi.project.AuxiliaryConfiguration;
62
import org.netbeans.spi.project.AuxiliaryProperties;
63
import org.openide.filesystems.FileObject;
64
import org.openide.filesystems.FileUtil;
65
import org.openide.util.Lookup;
66
import org.openide.util.lookup.Lookups;
67
import org.openide.util.lookup.ProxyLookup;
68
import org.openide.util.test.MockLookup;
69
import org.openide.xml.XMLUtil;
70
import org.w3c.dom.Document;
71
import org.w3c.dom.Element;
72
import org.w3c.dom.Node;
73
import org.w3c.dom.NodeList;
74
75
/**
76
 *
77
 * @author Jan Lahoda
78
 */
79
public class AuxiliaryConfigBasedPreferencesProviderTest extends NbTestCase {
80
    
81
    public AuxiliaryConfigBasedPreferencesProviderTest(String testName) {
82
        super(testName);
83
    }            
84
85
    private FileObject fo;
86
    private Project p;
87
    private TestLookup lookup;
88
    
89
    @Override
90
    protected void setUp() throws Exception {
91
        MockLookup.setInstances(TestUtil.testProjectFactory());
92
        clearWorkDir();
93
        File wd = getWorkDir();
94
        FileUtil.refreshAll();
95
        File f = new File(new File(wd, "test"), "testproject");
96
        FileObject testprojectFO = FileUtil.createFolder(f);
97
        assertNotNull(testprojectFO);
98
        fo = testprojectFO.getParent();
99
        TestUtil.LOOKUP = lookup = new TestLookup();
100
        p = ProjectManager.getDefault().findProject(fo);
101
        assertNotNull(p);
102
    }
103
104
    public void testStorage() throws IOException, BackingStoreException {
105
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl()));
106
        doTestStorage();
107
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryProperties()));
108
        doTestStorage();
109
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl(), new TestAuxiliaryProperties()));
110
        doTestStorage();
111
    }
112
    
113
    private void doTestStorage() throws IOException, BackingStoreException {
114
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
115
        AuxiliaryProperties ap = p.getLookup().lookup(AuxiliaryProperties.class);
116
        
117
        assertTrue(ac != null || ap != null);
118
        
119
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, true);
120
        Preferences pref = provider.findModule("test");
121
        
122
        pref.put("test", "test");
123
        
124
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
125
        
126
        assertEquals(Arrays.asList("somekey"), Arrays.asList(pref.node("subnode1/subnode2").keys()));
127
        
128
        pref.flush();
129
        
130
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, true);
131
        pref = provider.findModule("test");
132
        
133
        assertEquals("test", pref.get("test", null));
134
        assertEquals("somevalue", pref.node("subnode1/subnode2").get("somekey", null));
135
        assertEquals(Arrays.asList("somekey"), Arrays.asList(pref.node("subnode1/subnode2").keys()));
136
        pref.node("subnode1/subnode2").remove("somekey");
137
        assertEquals(Arrays.<String>asList(), Arrays.asList(pref.node("subnode1/subnode2").keys()));
138
    }
139
140
    public void testNoSaveWhenNotModified() throws IOException, BackingStoreException {
141
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl()));
142
        
143
        final AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
144
        
145
        assertNotNull(ac);
146
        
147
        final AtomicInteger putCount = new AtomicInteger();
148
        
149
        AuxiliaryConfiguration newAC = new AuxiliaryConfiguration() {
150
            public Element getConfigurationFragment(String elementName, String namespace, boolean shared) {
151
                return ac.getConfigurationFragment(elementName, namespace, shared);
152
            }
153
            public void putConfigurationFragment(Element fragment, boolean shared) throws IllegalArgumentException {
154
                putCount.incrementAndGet();
155
                ac.putConfigurationFragment(fragment, shared);
156
            }
157
            public boolean removeConfigurationFragment(String elementName, String namespace, boolean shared) throws IllegalArgumentException {
158
                return ac.removeConfigurationFragment(elementName, namespace, shared);
159
            }
160
        };
161
        
162
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, newAC, null, true);
163
        Preferences pref = provider.findModule("test");
164
        
165
        pref.put("test", "test");
166
        
167
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
168
        
169
        assertEquals(0, putCount.get());
170
        pref.flush();
171
        assertEquals(1, putCount.get());
172
        pref.flush();
173
        assertEquals(1, putCount.get());
174
    }
175
    
176
    public void testSubnodes() throws IOException, BackingStoreException {
177
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl()));
178
        doTestSubnodes();
179
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryProperties()));
180
        doTestSubnodes();
181
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl(), new TestAuxiliaryProperties()));
182
        doTestSubnodes();
183
    }
184
    
185
    private void doTestSubnodes() throws IOException, BackingStoreException {
186
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
187
        AuxiliaryProperties ap = p.getLookup().lookup(AuxiliaryProperties.class);
188
        
189
        assertTrue(ac != null || ap != null);
190
191
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, true);
192
        Preferences pref = provider.findModule("test");
193
194
        pref.put("test", "test");
195
196
        pref.node("subnode1/subnode2").put("somekey", "somevalue1");
197
        pref.node("subnode1").put("somekey", "somevalue2");
198
199
        pref.flush();
200
        
201
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, true);
202
        pref = provider.findModule("test");
203
        
204
        assertTrue(pref.node("subnode1").nodeExists("subnode2"));
205
        assertEquals("somevalue1", pref.node("subnode1/subnode2").get("somekey", null));
206
        assertEquals("somevalue2", pref.node("subnode1").get("somekey", null));
207
        pref.node("subnode1").removeNode();
208
        assertEquals(null, pref.node("subnode1/subnode2").get("somekey", null));
209
        assertEquals(null, pref.node("subnode1").get("somekey", null));
210
211
        pref.flush();
212
213
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, ac, ap, true);
214
        pref = provider.findModule("test");
215
216
        assertEquals(null, pref.node("subnode1/subnode2").get("somekey", null));
217
        assertEquals(null, pref.node("subnode1").get("somekey", null));
218
    }
219
    
220
    public void testSync() throws IOException, BackingStoreException {
221
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl()));
222
        
223
        AuxiliaryConfiguration ac = p.getLookup().lookup(AuxiliaryConfiguration.class);
224
        
225
        assertNotNull(ac);
226
                
227
        AuxiliaryConfigBasedPreferencesProvider toSync = new AuxiliaryConfigBasedPreferencesProvider(p, ac, null, true);
228
        Preferences pref = toSync.findModule("test");
229
        
230
        pref.put("test", "test");
231
        
232
        pref.node("subnode1/subnode2").put("somekey", "somevalue");
233
        pref.flush();
234
        
235
        AuxiliaryConfigBasedPreferencesProvider orig = new AuxiliaryConfigBasedPreferencesProvider(p, ac, null, true);
236
        
237
        Preferences origNode = orig.findModule("test").node("subnode1/subnode2");
238
        
239
        pref.node("subnode1/subnode2").put("somekey", "somevalue2");
240
        pref.flush();
241
        
242
        origNode.sync();
243
        
244
        assertEquals("somevalue2", origNode.get("somekey", null));
245
    }
246
    
247
    public void testReclaimable() throws IOException, BackingStoreException, InterruptedException {
248
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryConfigurationImpl()));
249
        
250
        Preferences pref = AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true);
251
        
252
        //the same preferences instance is returned as long as the previous one exists:
253
        assertTrue(pref == AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true));
254
        
255
        //but the preferences can be reclaimed, as well as the project if noone holds them:
256
        Reference<Preferences> rPref = new WeakReference<Preferences>(pref);
257
        Reference<Project> rProject = new WeakReference<Project>(p);
258
        
259
        TestUtil.notifyDeleted(p);
260
        
261
        Thread.sleep(5000);
262
        
263
        p = null;
264
        pref = null;
265
        
266
        assertGC("", rPref);
267
        assertGC("", rProject);
268
    }
269
    
270
    public void testComplexNames() throws IOException, BackingStoreException, InterruptedException {
271
        lookup.setDelegates(Lookups.fixed(new TestAuxiliaryProperties()));
272
        
273
        AuxiliaryProperties ap = p.getLookup().lookup(AuxiliaryProperties.class);
274
        
275
        assertNotNull(ap != null);
276
277
        AuxiliaryConfigBasedPreferencesProvider provider = new AuxiliaryConfigBasedPreferencesProvider(p, null, ap, true);
278
        Preferences pref = provider.findModule("test");
279
        
280
        pref.node(".:./.:.").put(".:.", "correct");
281
        
282
        pref.flush();
283
        
284
        provider = new AuxiliaryConfigBasedPreferencesProvider(p, null, ap, true);
285
        pref = provider.findModule("test");
286
        
287
        assertTrue(pref.nodeExists(".:./.:."));
288
        assertEquals(Arrays.asList(".:."), Arrays.asList(pref.node(".:./.:.").keys()));
289
    }
290
    
291
    public void testNoAuxiliaryImplInLookup() {
292
        Preferences pref = AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true);
293
        
294
        assertNull(pref);
295
        
296
        pref = AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, false);
297
        
298
        assertNotNull(pref);
299
        
300
        pref = AuxiliaryConfigBasedPreferencesProvider.getPreferences(p, Object.class, true);
301
        
302
        assertNull(pref);
303
    }
304
    
305
    private static final class TestAuxiliaryConfigurationImpl implements AuxiliaryConfiguration {
306
307
        private final Document sharedDOM;
308
        private final Document privDOM;
309
310
        public TestAuxiliaryConfigurationImpl() {
311
            sharedDOM = XMLUtil.createDocument("test", null, null, null);
312
            privDOM = XMLUtil.createDocument("test", null, null, null);
313
        }
314
        
315
        public Element getConfigurationFragment(String elementName, String namespace, boolean shared) {
316
            Element el = find(shared, namespace, elementName);
317
318
            if (el != null) {
319
                Document dummy = XMLUtil.createDocument("test", null, null, null);
320
                return (Element) dummy.importNode(el, true);
321
            }
322
            
323
            return null;
324
        }
325
326
        public void putConfigurationFragment(Element fragment, boolean shared) throws IllegalArgumentException {
327
            removeConfigurationFragment(fragment.getLocalName(), fragment.getNamespaceURI(), shared);
328
            
329
            Document dom = shared ? sharedDOM : privDOM;
330
            
331
            dom.getDocumentElement().appendChild(dom.importNode(fragment, true));
332
        }
333
334
        public boolean removeConfigurationFragment(String elementName, String namespace, boolean shared) throws IllegalArgumentException {
335
            Element el = find(shared, namespace, elementName);
336
337
            if (el != null) {
338
                el.getParentNode().removeChild(el);
339
                return true;
340
            }
341
            
342
            return false;
343
        }
344
345
        private Element find(boolean shared, String namespace, String elementName) {
346
            Document dom = shared ? sharedDOM : privDOM;
347
            NodeList nl = dom.getDocumentElement().getChildNodes();
348
            
349
            for (int cntr = 0; cntr < nl.getLength(); cntr++) {
350
                Node n = nl.item(cntr);
351
                
352
                if (n.getNodeType() == Node.ELEMENT_NODE && namespace.equals(n.getNamespaceURI()) && elementName.equals(n.getLocalName())) {
353
                    return (Element) n;
354
                }
355
            }
356
            return null;
357
        }
358
        
359
    }
360
    
361
    private static final class TestAuxiliaryProperties implements AuxiliaryProperties {
362
363
        private Properties pub;
364
        private Properties priv;
365
366
        public TestAuxiliaryProperties() {
367
            this.pub = new Properties();
368
            this.priv = new Properties();
369
        }
370
        
371
        public String get(String key, boolean shared) {
372
            return (shared ? pub : priv).getProperty(key);
373
        }
374
375
        public void put(String key, String value, boolean shared) {
376
            if (value != null) {
377
                (shared ? pub : priv).setProperty(key, value);
378
            } else {
379
                (shared ? pub : priv).remove(key);
380
            }
381
        }
382
383
        public Iterable<String> listKeys(boolean shared) {
384
            Enumeration en  = (shared ? pub : priv).propertyNames();
385
            List<String> result = new LinkedList<String>();
386
            
387
            while (en.hasMoreElements()) {
388
                Object el = en.nextElement();
389
                
390
                if (el instanceof String) {
391
                    result.add((String) el);
392
                }
393
            }
394
            
395
            return Collections.unmodifiableList(result);
396
        }
397
398
    }
399
    
400
    private static final class TestLookup extends ProxyLookup {
401
        public void setDelegates(Lookup... l) {
402
            setLookups(l);
403
        }
404
    }
405
}

Return to bug 134580