diff --git a/openide.filesystems/apichanges.xml b/openide.filesystems/apichanges.xml
--- a/openide.filesystems/apichanges.xml
+++ b/openide.filesystems/apichanges.xml
@@ -46,6 +46,21 @@
Filesystems API
+
+
+ Added new elements to MIME resolver DTD
+
+
+
+
+
+
+ It is now possible to write declarive MIME resolvers checking
+ file names (element name) and file content (element pattern).
+
+
+
+
Support for annotation processors generating XML layer fragments
diff --git a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/MIMEResolverImpl.java b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/MIMEResolverImpl.java
--- a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/MIMEResolverImpl.java
+++ b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/MIMEResolverImpl.java
@@ -46,7 +46,9 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.filesystems.FileChangeAdapter;
@@ -212,8 +214,9 @@
}
/** For debug purposes. */
+ @Override
public String toString() {
- return "MIMEResolverImpl.Impl[" + data + ", " + smell + "]"; // NOI18N
+ return "MIMEResolverImpl.Impl[" + data.getPath() + "]"; // NOI18N
}
@@ -236,6 +239,10 @@
// references active resolver component
private MIMEComponent component = null;
private String componentDelimiter = null;
+ // holds level of pattern element
+ private int patternLevel = 0;
+ // used to prohibit more pattern elements on the same level
+ Set patternLevelSet;
DescParser(FileObject fo) {
@@ -247,6 +254,7 @@
private static final short IN_FILE = 2;
private static final short IN_RESOLVER = 3;
private static final short IN_COMPONENT = 4;
+ private static final short IN_PATTERN = 5;
// second state dimension
private static final short IN_EXIT = INIT + 1;
@@ -259,13 +267,19 @@
private static final String RESOLVER = "resolver"; // NOI18N
private static final String FATTR = "fattr"; // NOI18N
private static final String NAME = "name"; // NOI18N
+ private static final String PATTERN = "pattern"; // NOI18N
+ private static final String VALUE = "value"; // NOI18N
+ private static final String RANGE = "range"; // NOI18N
+ private static final String IGNORE_CASE = "ignorecase"; // NOI18N
+ private static final String SUBSTRING = "substring"; // NOI18N
private static final String MAGIC = "magic"; // NOI18N
private static final String HEX = "hex"; // NOI18N
private static final String MASK = "mask"; // NOI18N
- private static final String VALUE = "text"; // NOI18N
+ private static final String TEXT = "text"; // NOI18N
private static final String EXIT = "exit"; // NOI18N
private static final String XML_RULE_COMPONENT = "xml-rule"; // NOI18N
+ @Override
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
String s;
@@ -337,14 +351,56 @@
} else if (FATTR.equals(qName)) {
s = atts.getValue(NAME); if (s == null) error();
- String val = atts.getValue(VALUE);
+ String val = atts.getValue(TEXT);
template[0].fileCheck.addAttr(s, val);
+
+ } else if (PATTERN.equals(qName)) {
+
+ s = atts.getValue(VALUE); if (s == null) error();
+ int range = Integer.valueOf(atts.getValue(RANGE));
+ boolean ignoreCase = Type.FilePattern.DEFAULT_IGNORE_CASE;
+ String ignoreCaseAttr = atts.getValue(IGNORE_CASE);
+ if (ignoreCaseAttr != null) {
+ ignoreCase = Boolean.valueOf(ignoreCaseAttr);
+ }
+ if (file_state == IN_PATTERN) {
+ if (patternLevelSet == null) {
+ patternLevelSet = new HashSet();
+ }
+ if (!patternLevelSet.add(patternLevel)) {
+ error("Second pattern element on the same level not allowed"); //NOI18N
+ }
+ template[0].fileCheck.addInnerPattern(s, range, ignoreCase);
+ } else {
+ template[0].fileCheck.addPattern(s, range, ignoreCase);
+ file_state = IN_PATTERN;
+ }
+ patternLevel++;
+ break;
+
+ } else if (NAME.equals(qName)) {
+
+ s = atts.getValue(NAME); if (s == null) error();
+ String substringAttr = atts.getValue(SUBSTRING);
+ boolean substring = Type.FileName.DEFAULT_SUBSTRING;
+ if (substringAttr != null) {
+ substring = Boolean.valueOf(substringAttr);
+ }
+ boolean ignoreCase = Type.FileName.DEFAULT_IGNORE_CASE;
+ String ignoreCaseAttr = atts.getValue(IGNORE_CASE);
+ if (ignoreCaseAttr != null) {
+ ignoreCase = Boolean.valueOf(ignoreCaseAttr);
+ }
+ template[0].fileCheck.addName(s, substring, ignoreCase);
+ break;
} else if (RESOLVER.equals(qName)) {
if (template[0].fileCheck.exts == null
&& template[0].fileCheck.mimes == null
- && template[0].fileCheck.fatts == null
+ && template[0].fileCheck.fatts == null
+ && template[0].fileCheck.patterns == null
+ && template[0].fileCheck.names == null
&& template[0].fileCheck.magic == null) {
error(); // at least one must be specified
}
@@ -387,7 +443,7 @@
component.startElement(namespaceURI, localName, qName, atts);
break;
-
+
default:
}
@@ -402,12 +458,19 @@
state = IN_COMPONENT;
}
+ @Override
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
switch (state) {
case IN_FILE:
if (FILE.equals(qName)) {
state = IN_ROOT;
file_state = INIT;
+ }
+ if (PATTERN.equals(qName)) {
+ if (--patternLevel == 0) {
+ patternLevelSet = null;
+ file_state = INIT;
+ }
}
break;
@@ -427,6 +490,7 @@
}
}
+ @Override
public void characters(char[] data, int offset, int len) throws SAXException {
if (state == IN_COMPONENT) component.characters(data, offset, len);
}
@@ -475,6 +539,7 @@
/**
* For debug puroses only.
*/
+ @Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("FileElement(");
@@ -497,16 +562,176 @@
private static class Type {
Type() {}
private String[] exts;
+ private static final String EMPTY_EXTENSION = "";
private String[] mimes;
private String[] fatts;
+ private List patterns;
+ private FilePattern lastAddedPattern = null;
+ private List names;
private String[] vals; // contains null or value of attribute at the same index
private byte[] magic;
private byte[] mask;
-
+ /** Used to search in the file for given pattern in given range. If there is an inner
+ * pattern element, it is used only if outer is fulfilled. Searching starts
+ * always from the beginning of the file. For example:
+ *
+ * Pattern <?php in first 255 bytes
+ *
+ * <pattern value="<?php" range="255"/>
+ *
+ *
+ *
+ * Pattern <HTML>> or <html> in first 255 bytes and pattern <?php in first 4000 bytes.
+ *
+ * <pattern value="<HTML>" range="255" ignorecase="true">
+ * <pattern value="<?php" range="4000"/>
+ * </pattern>
+ *
+ *
+ */
+ private class FilePattern {
+ // case sensitive by default
+ private static final boolean DEFAULT_IGNORE_CASE = false;
+ private final String value;
+ private final int range;
+ private final boolean ignoreCase;
+ private FilePattern inner;
+ private int pointer = 0;
+ private final byte[] bytes;
+ private final int valueLength;
+
+ public FilePattern(String value, int range, boolean ignoreCase) {
+ this.value = value;
+ this.valueLength = value.length();
+ if (ignoreCase) {
+ this.bytes = value.toLowerCase().getBytes();
+ } else {
+ this.bytes = value.getBytes();
+ }
+ this.range = range;
+ this.ignoreCase = ignoreCase;
+ }
+
+ public void setInner(FilePattern inner) {
+ this.inner = inner;
+ }
+
+ private boolean match(byte b) {
+ if (pointer < valueLength) {
+ if (b == bytes[pointer]) {
+ pointer++;
+ } else {
+ pointer = 0;
+ return false;
+ }
+ }
+ return pointer >= valueLength;
+ }
+
+ /** Read from given file and compare byte-by-byte if pattern
+ * appers in given range.
+ */
+ public boolean match(FileObject fo) throws IOException {
+ pointer = 0;
+ InputStream is = null;
+ boolean matched = false;
+ try {
+ is = fo.getInputStream(); // it is CachedInputStream, so you can call getInputStream and read more times without performance penalty
+ byte[] byteRange = new byte[range];
+ int read = is.read(byteRange);
+ for (int i = 0; i < read; i++) {
+ byte b = byteRange[i];
+ if (ignoreCase) {
+ b = (byte) Character.toLowerCase(b);
+ }
+ if (match(b)) {
+ matched = true;
+ break;
+ }
+ }
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException ioe) {
+ // already closed
+ }
+ }
+ if (matched) {
+ if (inner == null) {
+ return true;
+ } else {
+ return inner.match(fo);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + value + ", " + range + ", " + ignoreCase + (inner != null ? ", " + inner : "") + "]";
+ }
+ }
+
+ /** Used to compare filename with given name.
+ * For example:
+ *
+ * Filename matches makefile, Makefile, MaKeFiLe, mymakefile, gnumakefile, makefile1, ....
+ *
+ * <name name="makefile" substring="true"/>
+ *
+ *
+ *
+ * Filename exactly matches rakefile or Rakefile.
+ *
+ * <name name="rakefile" ignorecase="false"/>
+ * <name name="Rakefile" ignorecase="false"/>
+ *
+ *
+ */
+ private class FileName {
+
+ // case insensitive by default
+ private static final boolean DEFAULT_IGNORE_CASE = true;
+ private static final boolean DEFAULT_SUBSTRING = false;
+ private final String name;
+ private final boolean substring;
+ private final boolean ignoreCase;
+
+ public FileName(String name, boolean substring, boolean ignoreCase) {
+ if (ignoreCase) {
+ this.name = name.toLowerCase();
+ } else {
+ this.name = name;
+ }
+ this.substring = substring;
+ this.ignoreCase = ignoreCase;
+ }
+
+ public boolean match(FileObject fo) {
+ String nameAndExt = fo.getNameExt();
+ if (ignoreCase) {
+ nameAndExt = nameAndExt.toLowerCase();
+ }
+ if (substring) {
+ return nameAndExt.indexOf(name) != -1;
+ } else {
+ return nameAndExt.equals(name);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[" + name + ", " + substring + ", " + ignoreCase + "]";
+ }
+ }
+
/**
* For debug purposes only.
*/
+ @Override
public String toString() {
int i = 0;
StringBuffer buf = new StringBuffer();
@@ -529,6 +754,20 @@
buf.append("file-attributes:");
for (i = 0; i();
+ }
+ lastAddedPattern = new FilePattern(value, range, ignoreCase);
+ patterns.add(lastAddedPattern);
+ }
+
+ private void addInnerPattern(String value, int range, boolean ignoreCase) {
+ FilePattern inner = new FilePattern(value, range, ignoreCase);
+ lastAddedPattern.setInner(inner);
+ lastAddedPattern = inner;
+ }
+
+ private void addName(String name, boolean substring, boolean ignoreCase) {
+ if (names == null) {
+ names = new ArrayList();
+ }
+ names.add(new FileName(name, substring, ignoreCase));
+ }
+
private boolean setMagic(byte[] magic, byte[] mask) {
if (magic == null) return true;
if (mask != null && magic.length != mask.length) return false;
@@ -574,11 +834,26 @@
private static String getMIMEType(String extension) {
return FileUtil.getMIMEType(extension);
}
+
+ /** #26521, 114976 - ignore not readable and windows' locked files. */
+ private static void handleIOException(FileObject fo, IOException ioe) throws IOException {
+ if (fo.canRead()) {
+ if (!Utilities.isWindows() || !(ioe instanceof FileNotFoundException) || !fo.isValid() || fo.getName().toLowerCase().indexOf("ntuser") == -1) {//NOI18N
+ throw ioe;
+ }
+ }
+ }
+
private boolean accept(FileObject fo) throws IOException {
// check for resource extension
if (exts != null) {
- if (fo.getExt() == null) return false;
- if (!Util.contains(exts, fo.getExt(), CASE_INSENSITIVE)) return false;
+ String ext = fo.getExt();
+ if (ext == null) {
+ ext = EMPTY_EXTENSION;
+ }
+ if (!Util.contains(exts, ext, CASE_INSENSITIVE)) {
+ return false;
+ }
}
// check for resource mime type
@@ -613,63 +888,36 @@
if (magic != null) {
byte[] header = new byte[magic.length];
-// System.err.println("FO" + fo);
-
-// String m = mask == null ? "" : " mask " + XMLUtil.toHex(mask, 0, mask.length);
-// System.err.println("Magic test " + XMLUtil.toHex(magic, 0, magic.length) + m);
-
// fetch header
InputStream in = null;
- boolean unexpectedEnd = false;
try {
in = fo.getInputStream();
- for (int i = 0; i escape loop, otherwise continue
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ return false;
+ }
+ } catch (IOException ioe) {
+ handleIOException(fo, ioe);
+ return false;
+ }
+ }
+
+ // check file name
+ if (names != null) {
+ boolean matched = false;
+ for (FileName name : names) {
+ if(name.match(fo)) {
+ // at least one matched => escape loop, otherwise continue
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ return false;
+ }
+ }
+
// all templates matched
return true;
}
-
}
}
diff --git a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/resolver.dtd b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/resolver.dtd
--- a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/resolver.dtd
+++ b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/declmime/resolver.dtd
@@ -27,7 +27,7 @@
Contributor(s):
The Original Software is NetBeans. The Initial Developer of the Original
-Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
+Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
Microsystems, Inc. All Rights Reserved.
If you wish your version of this file to be governed by only the CDDL
@@ -85,10 +85,11 @@
so there is a danger of recursion if these call this.getMIMEType()
.
-->
-
+
@@ -123,6 +124,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
diff --git a/openide.filesystems/test/unit/src/org/netbeans/modules/openide/filesystems/declmime/data-fs.xml b/openide.filesystems/test/unit/src/org/netbeans/modules/openide/filesystems/declmime/data-fs.xml
--- a/openide.filesystems/test/unit/src/org/netbeans/modules/openide/filesystems/declmime/data-fs.xml
+++ b/openide.filesystems/test/unit/src/org/netbeans/modules/openide/filesystems/declmime/data-fs.xml
@@ -37,5 +37,44 @@
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
]]>
+
+
+
+
+
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+