8129632: (fs) Files.probeContentType returns null on Mac OS X
authorbpb
Tue, 30 Jun 2015 10:47:54 -0700
changeset 31450 35fe554da3fb
parent 31449 6b42ca2b2dca
child 31467 bdd4f4e915f3
8129632: (fs) Files.probeContentType returns null on Mac OS X Summary: Append a Uniform Type Identifier-based FileType Detector for Mac OS X Reviewed-by: alanb
jdk/make/lib/NioLibraries.gmk
jdk/src/java.base/macosx/classes/sun/nio/fs/MacOSXFileSystemProvider.java
jdk/src/java.base/macosx/classes/sun/nio/fs/UTIFileTypeDetector.java
jdk/src/java.base/macosx/native/libnio/fs/UTIFileTypeDetector.c
jdk/src/java.base/share/classes/sun/nio/fs/AbstractFileTypeDetector.java
jdk/src/java.base/unix/classes/sun/nio/fs/MimeTypesFileTypeDetector.java
jdk/test/java/nio/file/Files/probeContentType/Basic.java
--- a/jdk/make/lib/NioLibraries.gmk	Tue Jun 30 08:51:16 2015 -0700
+++ b/jdk/make/lib/NioLibraries.gmk	Tue Jun 30 10:47:54 2015 -0700
@@ -81,7 +81,8 @@
     LDFLAGS_SUFFIX_windows := jvm.lib ws2_32.lib $(WIN_JAVA_LIB) \
         $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libnet/net.lib \
         advapi32.lib, \
-    LDFLAGS_SUFFIX_macosx := -ljava -lnet -pthread -framework CoreFoundation, \
+    LDFLAGS_SUFFIX_macosx := -ljava -lnet -pthread \
+        -framework CoreFoundation -framework CoreServices, \
     LDFLAGS_SUFFIX :=, \
     VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
     RC_FLAGS := $(RC_FLAGS) \
--- a/jdk/src/java.base/macosx/classes/sun/nio/fs/MacOSXFileSystemProvider.java	Tue Jun 30 08:51:16 2015 -0700
+++ b/jdk/src/java.base/macosx/classes/sun/nio/fs/MacOSXFileSystemProvider.java	Tue Jun 30 10:47:54 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -49,6 +49,8 @@
     FileTypeDetector getFileTypeDetector() {
         Path userMimeTypes = Paths.get(AccessController.doPrivileged(
             new GetPropertyAction("user.home")), ".mime.types");
-        return new MimeTypesFileTypeDetector(userMimeTypes);
+
+        return chain(new MimeTypesFileTypeDetector(userMimeTypes),
+                     new UTIFileTypeDetector());
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/macosx/classes/sun/nio/fs/UTIFileTypeDetector.java	Tue Jun 30 10:47:54 2015 -0700
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.nio.fs;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * File type detector that uses a file extension to look up its MIME type
+ * via the Apple Uniform Type Identifier interfaces.
+ */
+class UTIFileTypeDetector extends AbstractFileTypeDetector {
+    UTIFileTypeDetector() {
+        super();
+    }
+
+    private native String probe0(String fileExtension) throws IOException;
+
+    @Override
+    protected String implProbeContentType(Path path) throws IOException {
+        Path fn = path.getFileName();
+        if (fn == null)
+            return null;  // no file name
+
+        String ext = getExtension(fn.toString());
+        if (ext.isEmpty())
+            return null;  // no extension
+
+        return probe0(ext);
+    }
+
+    static {
+        AccessController.doPrivileged(new PrivilegedAction<>() {
+            @Override
+            public Void run() {
+                System.loadLibrary("nio");
+                return null;
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/macosx/native/libnio/fs/UTIFileTypeDetector.c	Tue Jun 30 10:47:54 2015 -0700
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jni_util.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+
+/**
+ * Creates a CF string from the given Java string.
+ * If javaString is NULL, NULL is returned.
+ * If a memory error occurs, and OutOfMemoryError is thrown and
+ * NULL is returned.
+ */
+static CFStringRef toCFString(JNIEnv *env, jstring javaString)
+{
+    if (javaString == NULL) {
+        return NULL;
+    } else {
+        CFStringRef result = NULL;
+        jsize length = (*env)->GetStringLength(env, javaString);
+        const jchar *chars = (*env)->GetStringChars(env, javaString, NULL);
+        if (chars == NULL) {
+            JNU_ThrowOutOfMemoryError(env, "toCFString failed");
+            return NULL;
+        }
+        result = CFStringCreateWithCharacters(NULL, (const UniChar *)chars,
+                                              length);
+        (*env)->ReleaseStringChars(env, javaString, chars);
+        if (result == NULL) {
+            JNU_ThrowOutOfMemoryError(env, "toCFString failed");
+            return NULL;
+        }
+        return result;
+    }
+}
+
+/**
+ * Creates a Java string from the given CF string.
+ * If cfString is NULL, NULL is returned.
+ * If a memory error occurs, and OutOfMemoryError is thrown and
+ * NULL is returned.
+ */
+static jstring toJavaString(JNIEnv *env, CFStringRef cfString)
+{
+    if (cfString == NULL) {
+        return NULL;
+    } else {
+        jstring javaString = NULL;
+
+        CFIndex length = CFStringGetLength(cfString);
+        const UniChar *constchars = CFStringGetCharactersPtr(cfString);
+        if (constchars) {
+            javaString = (*env)->NewString(env, constchars, length);
+        } else {
+            UniChar *chars = malloc(length * sizeof(UniChar));
+            if (chars == NULL) {
+                JNU_ThrowOutOfMemoryError(env, "toJavaString failed");
+                return NULL;
+            }
+            CFStringGetCharacters(cfString, CFRangeMake(0, length), chars);
+            javaString = (*env)->NewString(env, chars, length);
+            free(chars);
+        }
+        return javaString;
+    }
+}
+
+/**
+ * Returns the content type corresponding to the supplied file extension.
+ * The mapping is determined using Uniform Type Identifiers (UTIs).  If
+ * the file extension parameter is NULL, a CFString cannot be created
+ * from the file extension parameter, there is no UTI corresponding to
+ * the file extension, the UTI cannot supply a MIME type for the file
+ * extension, or a Java string cannot be created, then NULL is returned;
+ * otherwise the MIME type string is returned.
+ */
+JNIEXPORT jstring JNICALL
+Java_sun_nio_fs_UTIFileTypeDetector_probe0(JNIEnv* env, jobject ftd,
+                                           jstring ext)
+{
+    jstring result = NULL;
+
+    CFStringRef extension = toCFString(env, ext);
+    if (extension != NULL) {
+        CFStringRef uti =
+            UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
+                                                  extension, NULL);
+        CFRelease(extension);
+
+        if (uti != NULL) {
+            CFStringRef mimeType =
+                UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
+            CFRelease(uti);
+
+            if (mimeType != NULL) {
+                result = toJavaString(env, mimeType);
+                CFRelease(mimeType);
+            }
+        }
+    }
+
+    return result;
+}
--- a/jdk/src/java.base/share/classes/sun/nio/fs/AbstractFileTypeDetector.java	Tue Jun 30 08:51:16 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/nio/fs/AbstractFileTypeDetector.java	Tue Jun 30 10:47:54 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -42,6 +42,27 @@
     }
 
     /**
+     * Returns the extension of a file name, specifically the portion of the
+     * parameter string after the first dot. If the parameter is {@code null},
+     * empty, does not contain a dot, or the dot is the last character, then an
+     * empty string is returned, otherwise the characters after the dot are
+     * returned.
+     *
+     * @param name A file name
+     * @return The characters after the first dot or an empty string.
+     */
+    protected final String getExtension(String name) {
+        String ext = "";
+        if (name != null && !name.isEmpty()) {
+            int dot = name.indexOf('.');
+            if ((dot >= 0) && (dot < name.length() - 1)) {
+                ext = name.substring(dot + 1);
+            }
+        }
+        return ext;
+    }
+
+    /**
      * Invokes the appropriate probe method to guess a file's content type,
      * and checks that the content type's syntax is valid.
      */
--- a/jdk/src/java.base/unix/classes/sun/nio/fs/MimeTypesFileTypeDetector.java	Tue Jun 30 08:51:16 2015 -0700
+++ b/jdk/src/java.base/unix/classes/sun/nio/fs/MimeTypesFileTypeDetector.java	Tue Jun 30 10:47:54 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -83,18 +83,6 @@
         return mimeType;
     }
 
-    // Get the extension of a file name.
-    private static String getExtension(String name) {
-        String ext = "";
-        if (name != null && !name.isEmpty()) {
-            int dot = name.indexOf('.');
-            if ((dot >= 0) && (dot < name.length() - 1)) {
-                ext = name.substring(dot + 1);
-            }
-        }
-        return ext;
-    }
-
     /**
      * Parse the mime types file, and store the type-extension mappings into
      * mimeTypeMap. The mime types file is not loaded until the first probe
--- a/jdk/test/java/nio/file/Files/probeContentType/Basic.java	Tue Jun 30 08:51:16 2015 -0700
+++ b/jdk/test/java/nio/file/Files/probeContentType/Basic.java	Tue Jun 30 10:47:54 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,7 +22,7 @@
  */
 
 /* @test
- * @bug 4313887
+ * @bug 4313887 8129632
  * @summary Unit test for probeContentType method
  * @library ../..
  * @build Basic SimpleFileTypeDetector
@@ -33,9 +33,9 @@
 import java.io.*;
 
 /**
- * Uses Files.probeContentType to probe html file and custom file type.
+ * Uses Files.probeContentType to probe html file, custom file type, and minimal
+ * set of file extension to content type mappings.
  */
-
 public class Basic {
 
     static Path createHtmlFile() throws IOException {
@@ -51,6 +51,39 @@
         return Files.createTempFile("red", ".grape");
     }
 
+    static void checkContentTypes(String[] extensions, String[] expectedTypes)
+        throws IOException {
+        if (extensions.length != expectedTypes.length) {
+            throw new IllegalArgumentException("Parameter array lengths differ");
+        }
+
+        int failures = 0;
+        for (int i = 0; i < extensions.length; i++) {
+            String extension = extensions[i];
+            Path file = Files.createTempFile("foo", "." + extension);
+            try {
+                String type = Files.probeContentType(file);
+                if (type == null) {
+                    System.err.println("Content type of " + extension
+                            + " cannot be determined");
+                    failures++;
+                } else {
+                    if (!type.equals(expectedTypes[i])) {
+                        System.err.println("Content type: " + type
+                                + "; expected: " + expectedTypes[i]);
+                        failures++;
+                    }
+                }
+            } finally {
+                Files.delete(file);
+            }
+        }
+
+        if (failures > 0) {
+            throw new RuntimeException("Test failed!");
+        }
+    }
+
     public static void main(String[] args) throws IOException {
 
         // exercise default file type detector
@@ -79,5 +112,17 @@
             Files.delete(file);
         }
 
+        // Verify that common file extensions are mapped to the correct content
+        // types on Mac OS X only which has consistent Uniform Type Identifiers.
+        if (System.getProperty("os.name").contains("OS X")) {
+            String[] extensions = new String[]{
+                "jpg", "mp3", "mp4", "pdf", "png"
+            };
+            String[] expectedTypes = new String[]{
+                "image/jpeg", "audio/mpeg", "video/mp4", "application/pdf",
+                "image/png"
+            };
+            checkContentTypes(extensions, expectedTypes);
+        }
     }
 }