2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
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.
42
package org.netbeans.modules.editor.settings.storage;
44
import java.lang.reflect.Field;
45
import java.util.ArrayList;
46
import java.util.Enumeration;
47
import java.util.HashMap;
48
import java.util.HashSet;
49
import java.util.List;
52
import java.util.logging.Level;
53
import java.util.logging.Logger;
54
import org.netbeans.api.editor.settings.CodeTemplateSettings;
55
import org.netbeans.api.editor.settings.FontColorSettings;
56
import org.netbeans.api.editor.settings.KeyBindingSettings;
57
import org.openide.filesystems.FileObject;
58
import org.openide.util.TopologicalSortException;
59
import org.openide.util.Utilities;
63
* @author Vita Stejskal
65
public enum SettingsType {
68
"FontsColors", //NOI18N
70
FontColorSettings.class,
71
"text/x-nbeditor-fontcolorsettings", //NOI18N
75
"Keybindings", //NOI18N
77
KeyBindingSettings.class,
78
"text/x-nbeditor-keybindingsettings", //NOI18N
79
"keybindings.xml" //NOI18N
82
"CodeTemplates", //NOI18N
84
CodeTemplateSettings.class,
85
"text/x-nbeditor-codetemplatesettings", //NOI18N
86
"abbreviations.xml" //NOI18N
89
public static SettingsType get(Class apiClass) {
90
assert apiClass != null : "The parameter apiClass can't be null"; //NOI18N
92
for (SettingsType type : SettingsType.values()) {
93
if (type.apiClass.equals(apiClass)) {
100
public static interface Locator {
101
public void scan(FileObject baseFolder, String mimeType, String profileId, boolean fullScan, boolean scanModules, boolean scanUsers, Map<String, List<Object []>> results);
102
public String getWritableFileName(String mimeType, String profileId, String fileId, boolean modulesFile);
105
// ------------------------------------------------------------------
106
// private implementation
107
// ------------------------------------------------------------------
109
private static final Logger LOG = Logger.getLogger(SettingsType.class.getName());
111
private final String settingsTypeId;
112
private final boolean usesProfiles;
113
private final Class apiClass;
114
private final String mimeType;
115
private Locator locator;
116
private final String legacyFileName;
118
private SettingsType(String settingsTypeId, boolean usesProfiles, Class apiClass, String mimeType, String legacyFileName) {
119
this.settingsTypeId = settingsTypeId;
120
this.usesProfiles = usesProfiles;
121
this.apiClass = apiClass;
122
this.mimeType = mimeType;
123
this.legacyFileName = legacyFileName;
126
public String getId() {
127
return settingsTypeId;
130
public boolean isUsingProfiles() {
134
public String getMimeType() {
138
public Locator getLocator() {
139
if (locator == null) {
141
case FONTSCOLORS: locator = new FontsColorsLocator(); break;
142
case KEYBINDINGS: locator = new KeybindingsLocator(); break;
143
default: locator = new DefaultLocator(this);
149
public String getLegacyFileName() {
150
return legacyFileName;
153
private static class DefaultLocator implements Locator {
155
protected static final String MODULE_FILES_FOLDER = "Defaults"; //NOI18N
156
protected static final String DEFAULT_PROFILE_NAME = EditorSettingsImpl.DEFAULT_PROFILE;
158
private static final String WRITABLE_FILE_PREFIX = "org-netbeans-modules-editor-settings-Custom"; //NOI18N
159
private static final String WRITABLE_FILE_SUFFIX = ".xml"; //NOI18N
160
private static final String FA_TARGET_OS = "nbeditor-settings-targetOS"; //NOI18N
162
private final SettingsType settingType;
163
private final String writableFilePrefix;
164
private final String modulesWritableFilePrefix;
166
public DefaultLocator(SettingsType settingType) {
167
assert settingType != null : "The parameter settingType can't be null"; //NOI18N
168
this.settingType = settingType;
169
this.writableFilePrefix = WRITABLE_FILE_PREFIX + settingType.getId();
170
this.modulesWritableFilePrefix = MODULE_FILES_FOLDER + "/" + writableFilePrefix; //NOI18N
173
public final void scan(
174
FileObject baseFolder,
180
Map<String, List<Object []>> results
182
assert results != null : "The parameter results can't be null"; //NOI18N
184
FileObject mimeFolder = null;
185
FileObject legacyMimeFolder = null;
187
if (baseFolder != null) {
188
mimeFolder = getMimeFolder(baseFolder, mimeType);
189
legacyMimeFolder = getLegacyMimeFolder(baseFolder, mimeType);
193
if (legacyMimeFolder != null && legacyMimeFolder.isFolder()) {
194
addModulesLegacyFiles(legacyMimeFolder, profileId, fullScan, results);
196
if (mimeFolder != null && mimeFolder.isFolder()) {
197
addModulesFiles(mimeFolder, profileId, fullScan, results);
202
if (legacyMimeFolder != null && legacyMimeFolder.isFolder()) {
203
addUsersLegacyFiles(legacyMimeFolder, profileId, fullScan, results);
205
if (mimeFolder != null && mimeFolder.isFolder()) {
206
addUsersFiles(mimeFolder, profileId, fullScan, results);
211
public final String getWritableFileName(String mimeType, String profileId, String fileId, boolean modulesFile) {
212
StringBuilder part = new StringBuilder(127);
214
if (mimeType == null || mimeType.length() == 0) {
215
part.append(settingType.getId()).append('/'); //NOI18N
217
part.append(mimeType).append('/').append(settingType.getId()).append('/'); //NOI18N
220
if (settingType.isUsingProfiles()) {
221
assert profileId != null : "The profileId parameter must not be null"; //NOI18N
222
part.append(profileId).append('/'); //NOI18N
226
part.append(modulesWritableFilePrefix);
228
part.append(writableFilePrefix);
231
if (fileId != null && fileId.length() != 0) {
235
part.append(WRITABLE_FILE_SUFFIX);
237
return part.toString();
240
protected FileObject getLegacyMimeFolder(FileObject baseFolder, String mimeType) {
241
return mimeType == null ? baseFolder : baseFolder.getFileObject(mimeType);
244
protected void addModulesLegacyFiles(FileObject mimeFolder, String profileId, boolean fullScan, Map<String, List<Object []>> files) {
245
if (settingType.getLegacyFileName() != null) {
249
MODULE_FILES_FOLDER + "/" + settingType.getLegacyFileName(), //NOI18N
256
protected void addUsersLegacyFiles(FileObject mimeFolder, String profileId, boolean fullScan, Map<String, List<Object []>> files) {
257
if (settingType.getLegacyFileName() != null) {
261
settingType.getLegacyFileName(),
268
private FileObject getMimeFolder(FileObject baseFolder, String mimeType) {
269
return mimeType == null ? baseFolder : baseFolder.getFileObject(mimeType);
272
private void addModulesFiles(FileObject mimeFolder, String profileId, boolean fullScan, Map<String, List<Object []>> files) {
273
if (profileId == null) {
274
FileObject settingHome = mimeFolder.getFileObject(settingType.getId());
275
if (settingHome != null && settingHome.isFolder()) {
276
if (settingType.isUsingProfiles()) {
277
FileObject [] profileHomes = settingHome.getChildren();
278
for(FileObject f : profileHomes) {
283
String id = f.getNameExt();
284
FileObject folder = f.getFileObject(MODULE_FILES_FOLDER);
285
if (folder != null && folder.isFolder()) {
286
addFiles(folder, fullScan, files, id, f, true);
290
FileObject folder = settingHome.getFileObject(MODULE_FILES_FOLDER);
291
if (folder != null && folder.isFolder()) {
292
addFiles(folder, fullScan, files, null, null, true);
297
FileObject folder = mimeFolder.getFileObject(settingType.getId() + "/" + profileId + "/" + MODULE_FILES_FOLDER); //NOI18N
298
if (folder != null && folder.isFolder()) {
299
addFiles(folder, fullScan, files, profileId, folder.getParent(), true);
304
private void addUsersFiles(FileObject mimeFolder, String profileId, boolean fullScan, Map<String, List<Object []>> files) {
305
if (profileId == null) {
306
FileObject settingHome = mimeFolder.getFileObject(settingType.getId());
307
if (settingHome != null && settingHome.isFolder()) {
308
if (settingType.isUsingProfiles()) {
309
FileObject [] profileHomes = settingHome.getChildren();
310
for(FileObject f : profileHomes) {
312
String id = f.getNameExt();
313
addFiles(f, fullScan, files, id, f, false);
317
addFiles(settingHome, fullScan, files, null, null, false);
321
FileObject folder = mimeFolder.getFileObject(settingType.getId() + "/" + profileId); //NOI18N
322
if (folder != null && folder.isFolder()) {
323
addFiles(folder, fullScan, files, profileId, folder, false);
328
private final void addFiles(FileObject folder, boolean fullScan, Map<String, List<Object []>> files, String profileId, FileObject profileHome, boolean moduleFiles) {
329
List<Object []> writableFiles = new ArrayList<Object []>();
330
List<Object []> osSpecificFiles = new ArrayList<Object []>();
332
FileObject [] ff = getOrderedChildren(folder);
333
for(FileObject f : ff) {
338
if (f.getMIMEType().equals(settingType.getMimeType())) {
339
Object targetOs = f.getAttribute(FA_TARGET_OS);
340
if (targetOs != null) {
342
if (!isApplicableForThisTargetOs(targetOs)) {
343
LOG.fine("Ignoring OS specific file: '" + f.getPath() + "', it's targetted for '" + targetOs + "'"); //NOI18N
346
} catch (Exception e) {
347
LOG.log(Level.WARNING, "Ignoring editor settings file with invalid OS type mask '" + targetOs + "' file: '" + f.getPath() + "'"); //NOI18N
352
List<Object []> infos = files.get(profileId);
354
infos = new ArrayList<Object[]>();
355
files.put(profileId, infos);
357
Object [] oo = new Object [] { profileHome, f, moduleFiles };
359
// There can be a writable file in the modules folder and it
360
// needs to be added last so that it does not get hidden by
361
// other module files.
363
if (f.getNameExt().startsWith(writableFilePrefix)) {
364
writableFiles.add(oo);
365
} else if (targetOs != null) {
366
osSpecificFiles.add(oo);
374
// Stop scanning if this is not a full scan mode
379
LOG.fine("Ignoring file: '" + f.getPath() + "' of type " + f.getMIMEType()); //NOI18N
383
if (!osSpecificFiles.isEmpty()) {
384
List<Object []> infos = files.get(profileId);
385
infos.addAll(osSpecificFiles);
388
// Add the writable file if there is any
389
if (!writableFiles.isEmpty()) {
390
List<Object []> infos = files.get(profileId);
391
infos.addAll(writableFiles);
395
private boolean isApplicableForThisTargetOs(Object targetOs) throws NoSuchFieldException, IllegalAccessException {
396
if (targetOs instanceof Boolean) {
397
return ((Boolean) targetOs).booleanValue();
398
} else if (targetOs instanceof String) {
399
Field field = Utilities.class.getDeclaredField((String) targetOs);
400
int targetOsMask = field.getInt(null);
401
int currentOsId = Utilities.getOperatingSystem();
402
return (currentOsId & targetOsMask) != 0;
408
protected static FileObject [] getOrderedChildren(FileObject folder) {
409
// Collect all children
410
Map<String, FileObject> children = new HashMap<String, FileObject>();
411
for (FileObject f : folder.getChildren()) {
412
String name = f.getNameExt();
413
children.put(name, f);
417
Map<FileObject, Set<FileObject>> edges = new HashMap<FileObject, Set<FileObject>>();
418
for (Enumeration<String> attrNames = folder.getAttributes(); attrNames.hasMoreElements(); ) {
419
String attrName = attrNames.nextElement();
420
Object attrValue = folder.getAttribute(attrName);
422
// Check whether the attribute affects sorting
423
int slashIdx = attrName.indexOf('/'); //NOI18N
424
if (slashIdx == -1 || !(attrValue instanceof Boolean)) {
428
// Get the file names
429
String name1 = attrName.substring(0, slashIdx);
430
String name2 = attrName.substring(slashIdx + 1);
431
if (!((Boolean) attrValue).booleanValue()) {
438
// Get the files and add them among the edges
439
FileObject from = children.get(name1);
440
FileObject to = children.get(name2);
442
if (from != null && to != null) {
443
Set<FileObject> vertices = edges.get(from);
444
if (vertices == null) {
445
vertices = new HashSet<FileObject>();
446
edges.put(from, vertices);
453
List<FileObject> sorted;
456
sorted = Utilities.topologicalSort(children.values(), edges);
457
} catch (TopologicalSortException e) {
458
LOG.log(Level.WARNING, "Can't sort folder children.", e); //NOI18N
459
@SuppressWarnings("unchecked")
460
List<FileObject> whyTheHellDoINeedToDoThis = e.partialSort();
461
sorted = whyTheHellDoINeedToDoThis;
464
return sorted.toArray(new FileObject[sorted.size()]);
467
private void addLegacyFiles(FileObject mimeFolder, String profileId, String filePath, Map<String, List<Object []>> files, boolean moduleFiles) {
468
if (profileId == null) {
469
String defaultProfileId;
471
if (settingType.isUsingProfiles()) {
472
FileObject [] profileHomes = mimeFolder.getChildren();
473
for(FileObject f : profileHomes) {
474
if (!f.isFolder() || f.getNameExt().equals(MODULE_FILES_FOLDER)) {
478
String id = f.getNameExt();
479
FileObject legacyFile = f.getFileObject(filePath);
480
if (legacyFile != null) {
481
addFile(legacyFile, files, id, f, true);
484
defaultProfileId = DEFAULT_PROFILE_NAME;
486
defaultProfileId = null;
489
FileObject legacyFile = mimeFolder.getFileObject(filePath);
490
if (legacyFile != null) {
491
addFile(legacyFile, files, defaultProfileId, null, true);
494
if (profileId.equals(DEFAULT_PROFILE_NAME)) {
495
FileObject file = mimeFolder.getFileObject(filePath); //NOI18N
497
addFile(file, files, profileId, null, moduleFiles);
500
FileObject profileHome = mimeFolder.getFileObject(profileId);
501
if (profileHome != null && profileHome.isFolder()) {
502
FileObject file = profileHome.getFileObject(filePath);
504
addFile(file, files, profileId, profileHome, moduleFiles);
511
private void addFile(FileObject file, Map<String, List<Object []>> files, String profileId, FileObject profileHome, boolean moduleFiles) {
512
List<Object []> pair = files.get(profileId);
514
pair = new ArrayList<Object[]>();
515
files.put(profileId, pair);
517
pair.add(new Object [] { profileHome, file, moduleFiles });
519
if (LOG.isLoggable(Level.INFO)) {
520
Utils.logOnce(LOG, Level.INFO, settingType.getId() + " settings " + //NOI18N
521
"should reside in '" + settingType.getId() + "' subfolder, " + //NOI18N
522
"see #90403 for details. Offending file '" + file.getPath() + "'", null); //NOI18N
525
} // End of DefaultLocator class
527
private static final class FontsColorsLocator extends DefaultLocator {
529
private static final String [] M_LEGACY_FILE_NAMES = new String [] {
530
MODULE_FILES_FOLDER + "/defaultColoring.xml", // NOI18N
531
MODULE_FILES_FOLDER + "/coloring.xml", // NOI18N
532
MODULE_FILES_FOLDER + "/editorColoring.xml", // NOI18N
535
private static final String [] U_LEGACY_FILE_NAMES = new String [] {
536
"defaultColoring.xml", // NOI18N
537
"coloring.xml", // NOI18N
538
"editorColoring.xml", // NOI18N
541
public FontsColorsLocator() {
546
protected void addModulesLegacyFiles(
547
FileObject mimeFolder,
550
Map<String, List<Object []>> files
552
addFiles(mimeFolder, profileId, fullScan, M_LEGACY_FILE_NAMES, files, true);
556
protected void addUsersLegacyFiles(
557
FileObject mimeFolder,
560
Map<String, List<Object []>> files
562
addFiles(mimeFolder, profileId, fullScan, U_LEGACY_FILE_NAMES, files, false);
565
private void addFiles(
566
FileObject mimeFolder,
570
Map<String, List<Object []>> files,
573
if (profileId == null) {
574
FileObject [] profileHomes = mimeFolder.getChildren();
575
for(FileObject f : profileHomes) {
580
String id = f.getNameExt();
581
addFiles(f, filePaths, fullScan, files, id, f, moduleFiles); //NOI18N
584
FileObject profileHome = mimeFolder.getFileObject(profileId);
585
if (profileHome != null && profileHome.isFolder()) {
586
addFiles(profileHome, filePaths, fullScan, files, profileId, profileHome, moduleFiles);
591
private void addFiles(FileObject folder, String [] filePaths, boolean fullScan, Map<String, List<Object []>> files, String profileId, FileObject profileHome, boolean moduleFiles) {
592
for(String filePath : filePaths) {
593
FileObject f = folder.getFileObject(filePath);
595
List<Object []> pair = files.get(profileId);
597
pair = new ArrayList<Object[]>();
598
files.put(profileId, pair);
600
pair.add(new Object [] { profileHome, f, moduleFiles });
602
if (LOG.isLoggable(Level.INFO)) {
603
Utils.logOnce(LOG, Level.INFO, FONTSCOLORS.getId() + " settings " + //NOI18N
604
"should reside in '" + FONTSCOLORS.getId() + "' subfolder, " + //NOI18N
605
"see #90403 for details. Offending file '" + f.getPath() + "'", null); //NOI18N
614
} // End of FontsColorsLocator class
616
private static final class KeybindingsLocator extends DefaultLocator {
618
public KeybindingsLocator() {
623
protected FileObject getLegacyMimeFolder(FileObject baseFolder, String mimeType) {
624
if (mimeType == null || mimeType.length() == 0) {
625
return baseFolder.getFileObject(EditorSettingsImpl.TEXT_BASE_MIME_TYPE);
627
return super.getMimeFolder(baseFolder, mimeType);
630
} // End of KeybindingsLocator class