blob: 3dbf17e0fe2bbcae74d0159e59555f43b2921f3b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.core.validation;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.Manifest;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.netbeans.core.startup.layers.LayerCacheManager;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.Log;
import org.netbeans.junit.RandomlyFails;
import org.openide.cookies.InstanceCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.MultiFileSystem;
import org.openide.filesystems.XMLFileSystem;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataShadow;
import org.openide.modules.Dependency;
import org.openide.util.Enumerations;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.NbCollections;
/** Checks consistency of System File System contents.
*/
public class ValidateLayerConsistencyTest extends NbTestCase {
static {
System.setProperty("java.awt.headless", "true");
// System.setProperty("org.openide.util.lookup.level", "FINE");
}
private static final String SFS_LB = "SystemFileSystem.localizingBundle";
private ClassLoader contextClassLoader;
public ValidateLayerConsistencyTest(String name) {
super (name);
}
public @Override void setUp() throws Exception {
clearWorkDir();
Mutex.EVENT.readAccess(new Mutex.Action<Void>() {
public @Override Void run() {
contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(Lookup.getDefault().lookup(ClassLoader.class));
return null;
}
});
}
public @Override void tearDown() {
Mutex.EVENT.readAccess(new Mutex.Action<Void>() {
public @Override Void run() {
Thread.currentThread().setContextClassLoader(contextClassLoader);
return null;
}
});
}
protected @Override boolean runInEQ() {
return true;
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(NbModuleSuite.createConfiguration(ValidateLayerConsistencyTest.class).
clusters("(?!ergonomics).*").enableClasspathModules(false).enableModules(".*").gui(false).suite());
suite.addTest(NbModuleSuite.createConfiguration(ValidateLayerConsistencyTest.class).
clusters("platform|ide").enableClasspathModules(false).enableModules(".*").gui(false).suite());
return suite;
}
private List<String> failures = new ArrayList<>();
private String appendFailure(String message, Collection<String> warnings) {
if (warnings.isEmpty()) {
return null;
}
StringBuilder b = new StringBuilder(message);
for (String warning : new TreeSet<String>(warnings)) {
b.append('\n').append(warning);
}
String s = b.toString();
failures.add(s);
return s;
}
private void assertNoFailures() {
if (failures.isEmpty()) {
return;
}
fail(String.join("", failures));
}
private void assertNoErrors(String message, Collection<String> warnings) {
String s = appendFailure(message, warnings);
if (s == null) {
return;
}
fail(s);
}
/* Causes mysterious failure in otherwise OK-looking UI/Runtime/org-netbeans-modules-db-explorer-nodes-RootNode.instance:
@Override
protected Level logLevel() {
return Level.FINER;
}
*/
/** whether an attribute will be handled in testInstantiateAllInstances anyway */
private static boolean isInstanceAttribute(String attributeName) {
if (attributeName.equals("instanceCreate")) {
return true;
}
if (attributeName.equals("component")) {
return true; // probably being used by TopComponent.openAction
}
return false;
}
public void testAreAttributesFine () {
List<String> errors = new ArrayList<String>();
FileObject root = FileUtil.getConfigRoot();
Enumeration<? extends FileObject> files = Enumerations.concat(Enumerations.singleton(root), root.getChildren(true));
while (files.hasMoreElements()) {
FileObject fo = files.nextElement();
if (
"Keymaps/NetBeans/D-BACK_QUOTE.shadow".equals(fo.getPath()) ||
"Keymaps/NetBeans55/D-BACK_QUOTE.shadow".equals(fo.getPath()) ||
"Keymaps/Emacs/D-BACK_QUOTE.shadow".equals(fo.getPath())
) {
// #46753
continue;
}
if (
"Services/Browsers/FirefoxBrowser.settings".equals(fo.getPath()) ||
"Services/Browsers/MozillaBrowser.settings".equals(fo.getPath()) ||
"Services/Browsers/NetscapeBrowser.settings".equals(fo.getPath())
) {
// #161784
continue;
}
Enumeration<String> attrs = fo.getAttributes();
while (attrs.hasMoreElements()) {
String name = attrs.nextElement();
if (isInstanceAttribute(name)) {
continue;
}
if (name.indexOf('\\') != -1) {
errors.add("File: " + fo.getPath() + " attribute name must not contain backslashes: " + name);
}
Object attr = fo.getAttribute(name);
if (attr == null) {
CharSequence warning = Log.enable("", Level.WARNING);
if (
fo.getAttribute("class:" + name) != null &&
fo.getAttribute(name) == null &&
warning.length() == 0
) {
// ok, factory method returned null
continue;
}
errors.add("File: " + fo.getPath() + " attribute name: " + name);
}
if (attr instanceof URL) {
URL u = (URL) attr;
int read = -1;
try {
read = u.openStream().read(new byte[4096]);
} catch (IOException ex) {
errors.add(fo.getPath() + ": " + ex.getMessage());
}
if (read <= 0) {
errors.add("URL resource does not exist: " + fo.getPath() + " attr: " + name + " value: " + attr);
}
}
}
}
assertNoErrors("Some attributes in files are unreadable", errors);
}
public void testValidShadows () {
// might be better to move into editor/options tests as it is valid only if there are options
List<String> errors = new ArrayList<String>();
FileObject root = FileUtil.getConfigRoot();
Enumeration<? extends FileObject> en = root.getChildren(true);
int cnt = 0;
while (en.hasMoreElements()) {
FileObject fo = en.nextElement();
cnt++;
// XXX #16761 Removing attr in MFO causes storing special-null value even in unneeded cases.
// When the issue is fixed remove this hack.
if("Windows2/Modes/debugger".equals(fo.getPath()) // NOI18N
|| "Windows2/Modes/explorer".equals(fo.getPath())) { // NOI18N
continue;
}
if (
"Keymaps/NetBeans/D-BACK_QUOTE.shadow".equals(fo.getPath()) ||
"Keymaps/NetBeans55/D-BACK_QUOTE.shadow".equals(fo.getPath()) ||
"Keymaps/Emacs/D-BACK_QUOTE.shadow".equals(fo.getPath())
) {
// #46753
continue;
}
try {
DataObject obj = DataObject.find (fo);
DataShadow ds = obj.getLookup().lookup(DataShadow.class);
if (ds != null) {
Object o = ds.getOriginal();
if (o == null) {
errors.add("File " + fo + " has no original.");
}
}
else if ("shadow".equals(fo.getExt())) {
errors.add("File " + fo + " is not a valid DataShadow.");
}
} catch (Exception ex) {
ex.printStackTrace();
errors.add ("File " + fo + " threw " + ex);
}
}
assertNoErrors("Some shadow files in NetBeans profile are broken", errors);
if (ValidateLayerConsistencyTest.class.getClassLoader() == ClassLoader.getSystemClassLoader()) {
// do not check the count as this probably means we are running
// plain Unit test and not inside the IDE mode
return;
}
if (cnt == 0) {
fail("No file objects on system file system!");
}
}
@RandomlyFails
public void testContentCanBeRead () {
List<String> errors = new ArrayList<String>();
byte[] buffer = new byte[4096];
Enumeration<? extends FileObject> files = FileUtil.getConfigRoot().getChildren(true);
while (files.hasMoreElements()) {
FileObject fo = files.nextElement();
if (!fo.isData ()) {
continue;
}
long size = fo.getSize();
try {
long read = 0;
InputStream is = fo.getInputStream();
try {
for (;;) {
int len = is.read (buffer);
if (len == -1) {
break;
}
read += len;
}
} finally {
is.close ();
}
if (size != -1) {
assertEquals ("The amount of data in stream is the same as the length", size, read);
}
} catch (IOException ex) {
ex.printStackTrace();
errors.add ("File " + fo + " cannot be read: " + ex);
}
}
assertNoErrors("Some files are unreadable", errors);
}
public void testInstantiateAllInstances () {
List<String> errors = new ArrayList<String>();
Enumeration<? extends FileObject> files = FileUtil.getConfigRoot().getChildren(true);
while (files.hasMoreElements()) {
FileObject fo = files.nextElement();
if (skipFile(fo)) {
continue;
}
try {
DataObject obj = DataObject.find (fo);
InstanceCookie ic = obj.getLookup().lookup(InstanceCookie.class);
if (ic != null) {
Object o = ic.instanceCreate ();
if (fo.getPath().matches("Services/.+[.]instance")) {
String instanceOf = (String) fo.getAttribute("instanceOf");
if (instanceOf == null) {
errors.add("File " + fo.getPath() + " should declare instanceOf");
} else if (o != null) {
for (String piece : instanceOf.split(", ?")) {
if (!Class.forName(piece, true, Lookup.getDefault().lookup(ClassLoader.class)).isInstance(o)) {
errors.add("File " + fo.getPath() + " claims to be a " + piece + " but is not (instance of " + o.getClass() + ")");
}
}
}
} else if (fo.getPath().matches("Services/.+[.]settings")) {
if (!fo.asText().contains("<instanceof")) {
errors.add("File " + fo.getPath() + " should declare <instanceof class=\"...\"/>");
}
// XXX test assignability here too, perhaps (but only used in legacy code)
}
}
} catch (Exception ex) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
ex.printStackTrace(ps);
ps.flush();
errors.add(
"File " + fo.getPath() +
"\nRead from: " + Arrays.toString((Object[])fo.getAttribute("layers")) +
"\nthrew: " + baos);
}
}
assertNoErrors("Some instances cannot be created", errors);
}
public void testActionInstancesOnlyInActionsFolder() {
List<String> errors = new ArrayList<String>();
Enumeration<? extends FileObject> files = FileUtil.getConfigRoot().getChildren(true);
FILE: while (files.hasMoreElements()) {
FileObject fo = files.nextElement();
if (skipFile(fo)) {
continue;
}
try {
DataObject obj = DataObject.find (fo);
InstanceCookie ic = obj.getLookup().lookup(InstanceCookie.class);
if (ic == null) {
continue;
}
Object o;
try {
o = ic.instanceCreate();
} catch (ClassNotFoundException ok) {
// wrong instances are catched by another test
continue;
}
if (!(o instanceof Action)) {
continue;
}
if (fo.hasExt("xml")) {
continue;
}
if (fo.getPath().startsWith("Actions/")) {
continue;
}
if (fo.getPath().startsWith("Editors/")) {
// editor is a bit different world
continue;
}
if (fo.getPath().startsWith("Databases/Explorer/")) {
// db explorer actions shall not influence start
// => let them be for now.
continue;
}
if (fo.getPath().startsWith("WelcomePage/")) {
// welcome screen actions are not intended for end user
continue;
}
if (fo.getPath().startsWith("Projects/org-netbeans-modules-mobility-project/Actions/")) {
// I am not sure what mobility is doing, but
// I guess I do not need to care
continue;
}
if (fo.getPath().startsWith("NativeProjects/Actions/")) {
// XXX should perhaps be replaced
continue;
}
if (fo.getPath().startsWith("contextmenu/uml/")) {
// UML is not the most important thing to fix
continue;
}
if (fo.getPath().equals("Menu/Help/org-netbeans-modules-j2ee-blueprints-ShowBluePrintsAction.instance")) {
// action included in some binary blob
continue;
}
if (Boolean.TRUE.equals(fo.getAttribute("misplaced.action.allowed"))) {
// it seems necessary some actions to stay outside
// of the Actions folder
continue;
}
if (fo.hasExt("shadow")) {
o = fo.getAttribute("originalFile");
if (o instanceof String) {
String origF = o.toString().replaceFirst("\\/*", "");
if (origF.startsWith("Actions/")) {
continue;
}
if (origF.startsWith("Editors/")) {
continue;
}
}
}
errors.add("File " + fo.getPath() + " represents an action which is not in Actions/ subfolder. Provided by " + Arrays.toString((Object[])fo.getAttribute("layers")));
} catch (Exception ex) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
ex.printStackTrace(ps);
ps.flush();
errors.add ("File " + fo.getPath() + " threw: " + baos);
}
}
assertNoErrors(errors.size() + " actions is not registered properly", errors);
}
public void testLayerOverrides() throws Exception {
ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
assertNotNull ("In the IDE mode, there always should be a classloader", l);
class ContentAndAttrs {
final byte[] contents;
final Map<String,Object> attrs;
private final URL layerURL;
ContentAndAttrs(byte[] contents, Map<String,Object> attrs, URL layerURL) {
this.contents = contents;
this.attrs = attrs;
this.layerURL = layerURL;
}
public @Override String toString() {
return "ContentAndAttrs[contents=" + Arrays.toString(contents) + ",attrs=" + attrs + ";from=" + layerURL + "]";
}
public @Override int hashCode() {
return Arrays.hashCode(contents) ^ attrs.hashCode();
}
public @Override boolean equals(Object o) {
if (!(o instanceof ContentAndAttrs)) {
return false;
}
ContentAndAttrs caa = (ContentAndAttrs) o;
return Arrays.equals(contents, caa.contents) && attrs.equals(caa.attrs);
}
}
Map</* path */String,Map</* owner */String,ContentAndAttrs>> files = new TreeMap<String,Map<String,ContentAndAttrs>>();
Map</* path */String,Map</* attr name */String,Map</* module name */String,/* attr value */Object>>> folderAttributes =
new TreeMap<String,Map<String,Map<String,Object>>>();
Map<String,Set<String>> directDeps = new HashMap<String,Set<String>>();
StringBuffer sb = new StringBuffer();
Map<String,URL> hiddenFiles = new HashMap<String, URL>();
Set<String> allFiles = new HashSet<String>();
final String suffix = "_hidden";
Enumeration<URL> en = l.getResources("META-INF/MANIFEST.MF");
while (en.hasMoreElements ()) {
URL u = en.nextElement();
InputStream is = u.openStream();
Manifest mf;
try {
mf = new Manifest(is);
} finally {
is.close();
}
String module = mf.getMainAttributes ().getValue ("OpenIDE-Module");
if (module == null) {
continue;
}
String depsS = mf.getMainAttributes().getValue("OpenIDE-Module-Module-Dependencies");
if (depsS != null) {
Set<String> deps = new HashSet<String>();
for (Dependency d : Dependency.create(Dependency.TYPE_MODULE, depsS)) {
deps.add(d.getName().replaceFirst("/.+$", ""));
}
directDeps.put(module, deps);
}
for (boolean generated : new boolean[] {false, true}) {
String layer;
if (generated) {
layer = "META-INF/generated-layer.xml";
} else {
layer = mf.getMainAttributes ().getValue ("OpenIDE-Module-Layer");
if (layer == null) {
continue;
}
}
URL base = new URL(u, "../");
URL layerURL = new URL(base, layer);
URLConnection connect;
try {
connect = layerURL.openConnection();
connect.connect();
} catch (FileNotFoundException x) {
if (generated) {
continue;
} else {
throw x;
}
}
connect.setDefaultUseCaches (false);
FileSystem fs = new XMLFileSystem(layerURL);
Enumeration<? extends FileObject> all = fs.getRoot().getChildren(true);
while (all.hasMoreElements ()) {
FileObject fo = all.nextElement ();
String simplePath = fo.getPath();
if (simplePath.endsWith(suffix)) {
hiddenFiles.put(simplePath, layerURL);
} else {
allFiles.add(simplePath);
}
Number weight = (Number) fo.getAttribute("weight");
// XXX if weight != null, test that it is actually overriding something or being overridden
String weightedPath = weight == null ? simplePath : simplePath + "#" + weight;
Map<String,Object> attributes = getAttributes(fo, base);
if (fo.isFolder()) {
for (Map.Entry<String,Object> attr : attributes.entrySet()) {
Map<String,Map<String,Object>> m1 = folderAttributes.get(weightedPath);
if (m1 == null) {
m1 = new TreeMap<String,Map<String,Object>>();
folderAttributes.put(weightedPath, m1);
}
Map<String,Object> m2 = m1.get(attr.getKey());
if (m2 == null) {
m2 = new TreeMap<String,Object>();
m1.put(attr.getKey(), m2);
}
m2.put(module, attr.getValue());
}
continue;
}
Map<String,ContentAndAttrs> overrides = files.get(weightedPath);
if (overrides == null) {
overrides = new TreeMap<String,ContentAndAttrs>();
files.put(weightedPath, overrides);
}
try {
overrides.put(module, new ContentAndAttrs(fo.asBytes(), attributes, layerURL));
} catch (IOException ex) {
// will be reported by a different test
}
}
// make sure the filesystem closes the stream
connect.getInputStream ().close ();
}
}
assertFalse("At least one layer file is usually used", allFiles.isEmpty());
for (Map.Entry<String,Map<String,ContentAndAttrs>> e : files.entrySet()) {
Map<String,ContentAndAttrs> overrides = e.getValue();
if (overrides.size() == 1) {
continue;
}
Set<String> overriders = overrides.keySet();
String file = e.getKey();
if (new HashSet<ContentAndAttrs>(overrides.values()).size() == 1) {
// All the same. Check whether these are parallel declarations (e.g. CND debugger vs. Java debugger), or vertical.
for (String overrider : overriders) {
Set<String> deps = new HashSet<String>(directDeps.get(overrider));
deps.retainAll(overriders);
if (!deps.isEmpty()) {
sb.append(file).append(" is pointlessly overridden in ").append(overrider).
append(" relative to ").append(deps.iterator().next()).append('\n');
}
}
continue;
}
sb.append(file).append(" is provided by: ").append(overriders).append('\n');
for (Map.Entry<String,ContentAndAttrs> entry : overrides.entrySet()) {
ContentAndAttrs contentAttrs = entry.getValue();
sb.append(" ").append(entry.getKey()).append(": content = '").append(new String(contentAttrs.contents)).
append("', attributes = ").append(contentAttrs.attrs).append("\n");
}
}
for (Map.Entry<String,Map<String,Map<String,Object>>> entry1 : folderAttributes.entrySet()) {
for (Map.Entry<String,Map<String,Object>> entry2 : entry1.getValue().entrySet()) {
if (new HashSet<Object>(entry2.getValue().values()).size() > 1) {
sb.append("Some modules conflict on the definition of ").append(entry2.getKey()).append(" for ").
append(entry1.getKey()).append(": ").append(entry2.getValue()).append("\n");
}
}
}
if (sb.length () > 0) {
fail("Some modules override some files without using the weight attribute correctly\n" + sb);
}
for (Map.Entry<String, URL> e : hiddenFiles.entrySet()) {
String p = e.getKey().substring(0, e.getKey().length() - suffix.length());
if (allFiles.contains(p)) {
continue;
}
sb.append("file ").append(e.getKey()).append(" from ").append(e.getValue()).append(" does not hide any other file\n");
}
if (sb.length () > 0) {
fail ("There are some useless hidden files\n" + sb);
}
}
/* Too many failures to solve right now.
public void testLocalizingBundles() throws Exception {
StringBuilder sb = new StringBuilder();
for (URL u : NbCollections.iterable(Lookup.getDefault().lookup(ClassLoader.class).getResources("META-INF/MANIFEST.MF"))) {
String layer;
InputStream is = u.openStream();
try {
layer = new Manifest(is).getMainAttributes().getValue("OpenIDE-Module-Layer");
if (layer == null) {
continue;
}
} finally {
is.close();
}
URL base = new URL(u, "../");
URL layerURL = new URL(base, layer);
URLConnection connect = layerURL.openConnection();
connect.setDefaultUseCaches(false);
for (FileObject fo : NbCollections.iterable(new XMLFileSystem(layerURL).getRoot().getChildren(true))) {
Object v = getAttributes(fo, base).get(SFS_LB);
if (v instanceof Exception) {
sb.append(layerURL).append(": ").append(v).append("\n");
}
}
}
if (sb.length() > 0) {
fail("Some localizing bundle declarations are wrong\n" + sb);
}
}
*/
public void testNoWarningsFromLayerParsing() throws Exception {
ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
assertNotNull ("In the IDE mode, there always should be a classloader", l);
List<URL> urls = new ArrayList<URL>();
Enumeration<URL> en = l.getResources("META-INF/MANIFEST.MF");
while (en.hasMoreElements ()) {
URL u = en.nextElement();
InputStream is = u.openStream();
Manifest mf;
try {
mf = new Manifest(is);
} finally {
is.close();
}
String module = mf.getMainAttributes ().getValue ("OpenIDE-Module");
if (module == null) {
continue;
}
String layer = mf.getMainAttributes ().getValue ("OpenIDE-Module-Layer");
if (layer == null) {
continue;
}
URL layerURL = new URL(u, "../" + layer);
urls.add(layerURL);
}
File cacheDir;
File workDir = getWorkDir();
int i = 0;
do {
cacheDir = new File(workDir, "layercache"+i);
i++;
} while (!cacheDir.mkdir());
System.setProperty("netbeans.user", cacheDir.getPath());
LayerCacheManager bcm = LayerCacheManager.manager(true);
Logger err = Logger.getLogger("org.netbeans.core.projects.cache");
TestHandler h = new TestHandler();
err.addHandler(h);
ByteArrayOutputStream os = new ByteArrayOutputStream();
bcm.store(bcm.createEmptyFileSystem(), urls, os);
assertNoErrors("No errors or warnings during layer parsing", h.errors);
}
private static class TestHandler extends Handler {
List<String> errors = new ArrayList<String>();
TestHandler () {}
public @Override void publish(LogRecord rec) {
if (Level.WARNING.equals(rec.getLevel()) || Level.SEVERE.equals(rec.getLevel())) {
errors.add(MessageFormat.format(rec.getMessage(), rec.getParameters()));
}
}
List<String> errors() {
return errors;
}
public @Override void flush() {}
public @Override void close() throws SecurityException {}
}
public void testFolderOrdering() throws Exception {
TestHandler h = new TestHandler();
Logger.getLogger("org.openide.filesystems.Ordering").addHandler(h);
Set<List<String>> editorMultiFolders = new HashSet<List<String>>();
Pattern editorFolder = Pattern.compile("Editors/(application|text)/([^/]+)(/.+|$)");
Enumeration<? extends FileObject> files = FileUtil.getConfigRoot().getChildren(true);
while (files.hasMoreElements()) {
FileObject fo = files.nextElement();
if (fo.isFolder()) {
loadChildren(fo);
assertNull("OpenIDE-Folder-Order attr should not be used on " + fo, fo.getAttribute("OpenIDE-Folder-Order"));
assertNull("OpenIDE-Folder-SortMode attr should not be used on " + fo, fo.getAttribute("OpenIDE-Folder-SortMode"));
String path = fo.getPath();
Matcher m = editorFolder.matcher(path);
if (m.matches()) {
List<String> multiPath = new ArrayList<String>(3);
multiPath.add(path);
if (m.group(2).endsWith("+xml")) {
multiPath.add("Editors/" + m.group(1) + "/xml" + m.group(3));
}
multiPath.add("Editors" + m.group(3));
editorMultiFolders.add(multiPath);
}
}
}
assertNoErrors("No warnings relating to folder ordering; " +
"cf: http://deadlock.netbeans.org/job/nbms-and-javadoc/lastSuccessfulBuild/artifact/nbbuild/build/generated/layers.txt", h.errors());
if (false) {
// temporarily disable multi-level checking of order, see discussion on dev@ mailing list on how
// the situation with some MIME types requiring positions and some not should be solved.
for (List<String> multiPath : editorMultiFolders) {
List<FileSystem> layers = new ArrayList<FileSystem>(3);
for (final String path : multiPath) {
FileObject folder = FileUtil.getConfigFile(path);
if (folder != null) {
layers.add(new MultiFileSystem(folder.getFileSystem()) {
protected @Override FileObject findResourceOn(FileSystem fs, String res) {
FileObject f = fs.findResource(path + '/' + res);
return Boolean.TRUE.equals(f.getAttribute("hidden")) ? null : f;
}
});
}
}
loadChildren(new MultiFileSystem(layers.toArray(new FileSystem[layers.size()])).getRoot());
appendFailure("\nNo warnings relating to folder ordering in " + multiPath +
"; cf: http://deadlock.netbeans.org/job/nbms-and-javadoc/lastSuccessfulBuild/artifact/nbbuild/build/generated/layers.txt",
h.errors());
h.errors.clear();
}
}
assertNoFailures();
}
private static void loadChildren(FileObject folder) {
List<FileObject> kids = new ArrayList<FileObject>();
for (DataObject kid : DataFolder.findFolder(folder).getChildren()) {
kids.add(kid.getPrimaryFile());
}
FileUtil.getOrder(kids, true);
}
private static Map<String,Object> getAttributes(FileObject fo, URL base) {
Map<String,Object> attrs = new TreeMap<String,Object>();
Enumeration<String> en = fo.getAttributes();
while (en.hasMoreElements()) {
String attrName = en.nextElement();
if (isInstanceAttribute(attrName)) {
continue;
}
Object attr = fo.getAttribute(attrName);
if (attrName.equals(SFS_LB)) {
try {
String bundleName = (String) attr;
URL bundle = new URL(base, bundleName.replace('.', '/') + ".properties");
Properties p = new Properties();
InputStream is = bundle.openStream();
try {
p.load(is);
} finally {
is.close();
}
String path = fo.getPath();
attr = p.get(path);
if (attr == null) {
attr = new MissingResourceException("No such bundle entry " + path + " in " + bundleName, bundleName, path);
}
} catch (Exception x) {
attr = x;
}
}
attrs.put(attrName, attr);
}
return attrs;
}
private static final String[] SKIPPED = {
"Templates/GUIForms",
"Palette/Borders/javax-swing-border-",
"Palette/Layouts/javax-swing-BoxLayout",
"Templates/Beans/",
"PaletteUI/org-netbeans-modules-form-palette-CPComponent",
"Templates/Ant/CustomTask.java",
"Templates/Privileged/Main.shadow",
"Templates/Privileged/JFrame.shadow",
"Templates/Privileged/Class.shadow",
"Templates/Classes",
"Templates/JSP_Servlet",
"EnvironmentProviders/ProfileTypes/Execution/nb-j2ee-deployment.instance",
"Shortcuts/D-BACK_QUOTE.shadow",
"Windows2/Components/", // cannot be loaded with a headless toolkit, so we have to skip these for now
"Maven/actionTemplate.instance", // template
};
private boolean skipFile(FileObject fo) {
String s = fo.getPath();
if (s.startsWith ("Templates/") && !s.startsWith ("Templates/Services")) {
if (s.endsWith (".shadow") || s.endsWith (".java")) {
return true;
}
}
for (String skipped : SKIPPED) {
if (s.startsWith(skipped)) {
return true;
}
}
String iof = (String) fo.getAttribute("instanceOf");
if (iof != null) {
for (String clz : iof.split("[, ]+")) {
try {
Class<?> c = Lookup.getDefault().lookup(ClassLoader.class).loadClass(clz);
} catch (ClassNotFoundException x) {
// E.g. Services/Hidden/org-netbeans-lib-jsch-antlibrary.instance in ide cluster
// cannot be loaded (and would just be ignored) if running without java cluster
System.err.println("Warning: skipping " + fo.getPath() + " due to inaccessible interface " + clz);
return true;
}
}
}
return false;
}
public void testKeymapOverrides() throws Exception { // #170677
List<String> warnings = new ArrayList<String>();
FileObject keymapRoot = FileUtil.getConfigFile("Keymaps");
if (keymapRoot == null) {
return;
}
FileObject[] keymaps = keymapRoot.getChildren();
Map<String,Integer> definitionCountById = new HashMap<String,Integer>();
assertTrue("Too many keymaps for too little bitfield", keymaps.length < 31);
int keymapFlag = 1;
for (FileObject keymap : keymaps) {
for (FileObject shortcut : keymap.getChildren()) {
DataObject d = DataObject.find(shortcut);
if (d instanceof DataShadow) {
String id = ((DataShadow) d).getOriginal().getPrimaryFile().getPath();
Integer prior = definitionCountById.get(id);
// a single keymap may provide alternative shortcuts for a given action. Count just once
// per keymap.
definitionCountById.put(id, prior == null ? keymapFlag : prior | keymapFlag);
} else if (!d.getPrimaryFile().hasExt("shadow") && !d.getPrimaryFile().hasExt("removed")) {
warnings.add("Anomalous file " + d);
} // else #172453: BrokenDataShadow, OK
}
keymapFlag <<= 1;
}
int expected = (1 << keymaps.length) - 1;
for (FileObject shortcut : FileUtil.getConfigFile("Shortcuts").getChildren()) {
DataObject d = DataObject.find(shortcut);
if (d instanceof DataShadow) {
String id = ((DataShadow) d).getOriginal().getPrimaryFile().getPath();
if (!org.openide.util.Utilities.isMac() && // Would fail on Mac due to applemenu module
Integer.valueOf(expected).equals(definitionCountById.get(id)))
{
String layers = Arrays.toString((URL[]) d.getPrimaryFile().getAttribute("layers"));
warnings.add(d.getPrimaryFile().getPath() + " " + layers + " useless since " + id + " is bound (somehow) in all keymaps");
}
} else if (!d.getPrimaryFile().hasExt("shadow")) {
warnings.add("Anomalous file " + d);
}
}
// XXX consider also checking for bindings in Shortcuts/ which are overridden in all keymaps or at least NetBeans
// taking into consideration O- and D- virtual modifiers
// (this is likely to be more common, e.g. mysterious Shortcuts/D-A.shadow in uml.drawingarea)
// XXX check for shortcut conflict between Shortcuts and each keymap, e.g. Ctrl-R in Eclipse keymap
assertNoErrors("Some shortcuts were overridden by keymaps", warnings);
}
/* XXX too many failures for now, some spurious; use regex, or look for unloc files/folders with loc siblings?
public void testLocalizedFolderNames() throws Exception {
List<String> warnings = new ArrayList<String>();
for (String folder : new String[] {
"Actions", // many legit failures!
"OptionsDialog/Actions", // XXX #71280
"Menu",
"Toolbars",
"org-netbeans-modules-java-hints/rules/hints",
"Editors/FontsColors", // XXX exclude .../Defaults
"Keymaps",
"FormDesignerPalette", // XXX match any *Palette?
"HTMLPalette",
"XHTMLPalette",
"JSPPalette",
"SVGXMLPalette",
"OptionsExport",
// "Projects/.../Customizer",
"QuickSearch",
"Templates", // XXX exclude Privileged, Recent, Services
}) {
FileObject root = FileUtil.getConfigFile(folder);
if (root == null) {
continue;
}
for (FileObject d : NbCollections.iterable(root.getFolders(true))) {
if (d.getAttribute("displayName") == null && d.getAttribute("SystemFileSystem.localizingBundle") == null) {
warnings.add("No displayName for " + d.getPath());
}
}
}
assertNoErrors("Some folders need a localized display name", warnings);
}
*/
public void testTemplates() throws Exception { // #167205
List<String> warnings = new ArrayList<String>();
for (FileObject f : NbCollections.iterable(FileUtil.getConfigFile("Templates").getData(true))) {
if (!Boolean.TRUE.equals(f.getAttribute("template"))) {
continue; // will not appear in Template Manager
}
if (f.getSize() > 0) {
continue; // Open in Editor will be enabled
}
if (f.getAttribute("instantiatingIterator") != null) { // TemplateWizard.CUSTOM_ITERATOR
continue; // probably not designed to be edited as text
}
if (f.getAttribute("templateWizardIterator") != null) { // TemplateWizard.EA_ITERATOR
continue; // same
}
String path = f.getPath();
if (path.equals("Templates/Other/file") ||
path.equals("Templates/Other/group.group") ||
path.equals("Templates/Classes/Package")) {
// If there're more files like this, consider adding an API
// to mark them as intentionally non-editable
continue; // intentionally empty and uneditable
}
warnings.add(path + " is empty but has no iterator and will therefore not be editable");
}
assertNoErrors("Problems in templates", warnings);
}
}