diff --git a/java.j2seplatform/nbproject/project.xml b/java.j2seplatform/nbproject/project.xml
--- a/java.j2seplatform/nbproject/project.xml
+++ b/java.j2seplatform/nbproject/project.xml
@@ -228,7 +228,7 @@
- 9.0
+ 9.6
@@ -256,14 +256,6 @@
- org.openide.util.ui
-
-
-
- 9.3
-
-
-
org.openide.util
@@ -279,6 +271,14 @@
8.0
+
+ org.openide.util.ui
+
+
+
+ 9.3
+
+
diff --git a/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/platformdefinition/jrtfs/NBJRTArchiveRootProvider.java b/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/platformdefinition/jrtfs/NBJRTArchiveRootProvider.java
new file mode 100644
--- /dev/null
+++ b/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/platformdefinition/jrtfs/NBJRTArchiveRootProvider.java
@@ -0,0 +1,128 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2015 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.j2seplatform.platformdefinition.jrtfs;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.filesystems.spi.ArchiveRootProvider;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Tomas Zezula
+ */
+@ServiceProvider(service=ArchiveRootProvider.class, position = 1000)
+public class NBJRTArchiveRootProvider implements ArchiveRootProvider {
+
+ private static final Logger LOG = Logger.getLogger(NBJRTArchiveRootProvider.class.getName());
+
+ @Override
+ public boolean isArchiveFile(@NonNull final URL url, final boolean strict) {
+ final FileObject fo = URLMapper.findFileObject(url);
+ return fo == null ? false : isArchiveFile(fo, strict);
+ }
+
+ @Override
+ public boolean isArchiveFile(FileObject fo, boolean strict) {
+ if (!fo.isFolder()) {
+ return false;
+ }
+ final File file = FileUtil.toFile(fo);
+ return file == null ? false : NBJRTUtil.getNIOProvider(file) != null;
+ }
+
+ @Override
+ public boolean isArchiveRoot(@NonNull final URL url) {
+ return NBJRTUtil.PROTOCOL.equals(url.getProtocol());
+ }
+
+ @Override
+ @CheckForNull
+ public URL getArchiveFile(URL url) {
+ if (isArchiveRoot(url)) {
+ final String path = url.getPath();
+ int index = path.indexOf("!/"); //NOI18N
+ if (index >= 0) {
+ String jdkPath = null;
+ try {
+ jdkPath = path.substring(0, index);
+ if (jdkPath.indexOf("file://") > -1 && jdkPath.indexOf("file:////") == -1) { //NOI18N
+ /* Replace because JDK application classloader wrongly recognizes UNC paths. */
+ jdkPath = jdkPath.replaceFirst("file://", "file:////"); //NOI18N
+ }
+ return new URL(jdkPath);
+
+ } catch (MalformedURLException mue) {
+ LOG.log(
+ Level.WARNING,
+ "Invalid URL ({0}): {1}, jdkHome: {2}", //NOI18N
+ new Object[] {
+ mue.getMessage(),
+ url.toExternalForm(),
+ jdkPath
+ });
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public URL getArchiveRoot(@NonNull final URL url) {
+ try {
+ return new URL(String.format("%s:%s!/", //NOI18N
+ NBJRTUtil.PROTOCOL,
+ url));
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/openide.filesystems/apichanges.xml b/openide.filesystems/apichanges.xml
--- a/openide.filesystems/apichanges.xml
+++ b/openide.filesystems/apichanges.xml
@@ -49,6 +49,23 @@
Filesystems API
+
+
+ Pluggable archive files support for FileUtil
+
+
+
+
+
+ To support JDK 9 image file as an archive the FileUtil's method
+ getArchiveFile
,getArchiveRoot
and isArchiveFile
+ are pluggable using a new SPI ArchiveFileProvider
.
+ In addition to these methods a new method isArchiveRoot
was added.
+ This method can be used if given URL
points into an archive.
+
+
+
+
Support for multi-user environments
diff --git a/openide.filesystems/nbproject/project.properties b/openide.filesystems/nbproject/project.properties
--- a/openide.filesystems/nbproject/project.properties
+++ b/openide.filesystems/nbproject/project.properties
@@ -41,7 +41,7 @@
# made subject to such option by the copyright holder.
javac.compilerargs=-Xlint -Xlint:-serial
-javac.source=1.7
+javac.source=1.8
module.jar.dir=core
javadoc.main.page=org/openide/filesystems/doc-files/api.html
javadoc.arch=${basedir}/arch.xml
diff --git a/openide.filesystems/src/META-INF/upgrade/FileUtil.hint b/openide.filesystems/src/META-INF/upgrade/FileUtil.hint
--- a/openide.filesystems/src/META-INF/upgrade/FileUtil.hint
+++ b/openide.filesystems/src/META-INF/upgrade/FileUtil.hint
@@ -8,3 +8,23 @@
=>
$c.setCurrentDirectory($d)
;;
+
+//"jar".equals($url.getProtocol()) :: $url instanceof java.net.URL
+//=>
+//org.openide.filesystems.FileUtil.isArchiveRoot($url)
+//;;
+
+//$url.getProtocol().equals("jar") :: $url instanceof java.net.URL
+//=>
+//org.openide.filesystems.FileUtil.isArchiveRoot($url)
+//;;
+
+//"jar".equals($uri.getScheme()) :: $uri instanceof java.net.URI
+//=>
+//org.openide.filesystems.FileUtil.isArchiveRoot($uri.toURL())
+//;;
+
+//$uri.getScheme().equals("jar") :: $uri instanceof java.net.URI
+//=>
+//org.openide.filesystems.FileUtil.isArchiveRoot($uri.toURL())
+//;;
diff --git a/openide.filesystems/src/org/openide/filesystems/FileUtil.java b/openide.filesystems/src/org/openide/filesystems/FileUtil.java
--- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java
+++ b/openide.filesystems/src/org/openide/filesystems/FileUtil.java
@@ -59,7 +59,6 @@
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
@@ -71,22 +70,26 @@
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
-import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl;
import org.openide.filesystems.FileSystem.AtomicAction;
+import org.openide.filesystems.spi.ArchiveRootProvider;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;
import org.openide.util.RequestProcessor;
import org.openide.util.BaseUtilities;
+import org.openide.util.Lookup;
import org.openide.util.WeakListeners;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
import org.openide.util.lookup.implspi.NamedServicesProvider;
/** Common utilities for handling files.
@@ -99,11 +102,6 @@
private static final Logger LOG = Logger.getLogger(FileUtil.class.getName());
- /** Normal header for ZIP files. */
- private static byte[] ZIP_HEADER_1 = {0x50, 0x4b, 0x03, 0x04};
- /** Also seems to be used at least in apisupport/project/test/unit/data/example-external-projects/suite3/nbplatform/random/modules/ext/stuff.jar; not known why */
- private static byte[] ZIP_HEADER_2 = {0x50, 0x4b, 0x05, 0x06};
-
/** transient attributes which should not be copied
* of type Set
*/
@@ -125,8 +123,6 @@
transientAttributes.add(MultiFileObject.WEIGHT_ATTRIBUTE); // NOI18N
}
- /** Cache for {@link #isArchiveFile(FileObject)}. */
- private static final Map archiveFileCache = new WeakHashMap();
private static FileSystem diskFileSystem;
static String toDebugString(File file) {
@@ -1828,35 +1824,41 @@
* Returns a FileObject representing the root folder of an archive.
* Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
* if the file object refers to an archive file.
- * @param fo a ZIP- (or JAR-) format archive file
+ * @param fo a java archive file, by default ZIP and JAR are supported
* @return a virtual archive root folder, or null if the file is not actually an archive
* @since 4.48
*/
public static FileObject getArchiveRoot(FileObject fo) {
- URL archiveURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
-
- if (archiveURL == null) {
- return null;
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveFile(fo, false)) {
+ final FileObject root = provider.getArchiveRoot(fo);
+ if (root != null) {
+ return root;
+ }
+ }
}
-
- return URLMapper.findFileObject(getArchiveRoot(archiveURL));
+ return null;
}
/**
* Returns a URL representing the root of an archive.
* Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
* refers to an archive file.
- * @param url of a ZIP- (or JAR-) format archive file
- * @return the jar
-protocol URL of the root of the archive
+ * @param url of a java archive file, by default ZIP and JAR are supported
+ * @return the archive (eg. jar
) protocol URL of the root of the archive.
* @since 4.48
*/
public static URL getArchiveRoot(URL url) {
- try {
- // XXX TBD whether the url should ever be escaped...
- return new URL("jar:" + url + "!/"); // NOI18N
- } catch (MalformedURLException e) {
- throw new AssertionError(e);
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveFile(url, false)) {
+ final URL root = provider.getArchiveRoot(url);
+ if (root != null) {
+ return root;
+ }
+ }
}
+ //For compatibility reason never return null but return the jar URL.
+ return getArchiveRootProviders().iterator().next().getArchiveRoot(url);
}
/**
@@ -1864,161 +1866,107 @@
* FileObject given by the parameter.
* Remember that any path within the archive is discarded
* so you may need to check for non-root entries.
- * @param fo a file in a JAR filesystem
+ * @param fo a file in an archive filesystem
* @return the file corresponding to the archive itself,
* or null if fo
is not an archive entry
* @since 4.48
*/
public static FileObject getArchiveFile(FileObject fo) {
Parameters.notNull("fo", fo); //NOI18N
- try {
- FileSystem fs = fo.getFileSystem();
-
- if (fs instanceof JarFileSystem) {
- File jarFile = ((JarFileSystem) fs).getJarFile();
-
- return toFileObject(jarFile);
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveRoot(fo)) {
+ final FileObject file = provider.getArchiveFile(fo);
+ if (file != null) {
+ return file;
+ }
}
- } catch (FileStateInvalidException e) {
- Exceptions.printStackTrace(e);
}
-
return null;
}
/**
* Returns the URL of the archive file containing the file
- * referred to by a jar
-protocol URL.
+ * referred to by an archive (eg. jar
) protocol URL.
* Remember that any path within the archive is discarded
* so you may need to check for non-root entries.
* @param url a URL
- * @return the embedded archive URL, or null if the URL is not a
- * jar
-protocol URL containing !/
+ * @return the embedded archive URL, or null if the URL is not an
+ * archive protocol URL containing !/
* @since 4.48
*/
public static URL getArchiveFile(URL url) {
- String protocol = url.getProtocol();
-
- if ("jar".equals(protocol)) { //NOI18N
-
- String path = url.getPath();
- int index = path.indexOf("!/"); //NOI18N
-
- if (index >= 0) {
- String jarPath = null;
- try {
- jarPath = path.substring(0, index);
- if (jarPath.indexOf("file://") > -1 && jarPath.indexOf("file:////") == -1) { //NOI18N
- /* Replace because JDK application classloader wrongly recognizes UNC paths. */
- jarPath = jarPath.replaceFirst("file://", "file:////"); //NOI18N
- }
- return new URL(jarPath);
-
- } catch (MalformedURLException mue) {
- LOG.log(
- Level.WARNING,
- "Invalid URL ({0}): {1}, jarPath: {2}", //NOI18N
- new Object[] {
- mue.getMessage(),
- url.toExternalForm(),
- jarPath
- });
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveRoot(url)) {
+ final URL file = provider.getArchiveFile(url);
+ if (file != null) {
+ return file;
}
}
}
-
return null;
}
/**
- * Tests if a file represents a JAR or ZIP archive.
+ * Tests if a file represents a java archive.
+ * By default the JAR or ZIP archives are supported.
* @param fo the file to be tested
- * @return true if the file looks like a ZIP-format archive
+ * @return true if the file looks like a java archive
* @since 4.48
*/
public static boolean isArchiveFile(FileObject fo) {
Parameters.notNull("fileObject", fo); //NOI18N
-
- if (!fo.isValid()) {
- return isArchiveFile(fo.getPath());
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveFile(fo, true)) {
+ return true;
+ }
}
- // XXX Special handling of virtual file objects: try to determine it using its name, but don't cache the
- // result; when the file is checked out the more correct method can be used
- if (fo.isVirtual()) {
- return isArchiveFile(fo.getPath());
- }
-
- if (fo.isFolder()) {
- return false;
- }
-
- // First check the cache.
- Boolean b = archiveFileCache.get(fo);
-
- if (b == null) {
- // Need to check it.
- try {
- InputStream in = fo.getInputStream();
-
- try {
- byte[] buffer = new byte[4];
- int len = in.read(buffer, 0, 4);
-
- if (len == 4) {
- // Got a header, see if it is a ZIP file.
- b = Boolean.valueOf(Arrays.equals(ZIP_HEADER_1, buffer) || Arrays.equals(ZIP_HEADER_2, buffer));
- } else {
- //If the length is less than 4, it can be either
- //broken (empty) archive file or other empty file.
- //Return false and don't cache it, when the archive
- //file will be written and closed its length will change
- return false;
- }
- } finally {
- in.close();
- }
- } catch (IOException ioe) {
- // #160507 - ignore exception (e.g. permission denied)
- LOG.log(Level.FINE, null, ioe);
- }
-
- if (b == null) {
- b = isArchiveFile(fo.getPath());
- }
-
- archiveFileCache.put(fo, b);
- }
-
- return b.booleanValue();
+ return false;
}
/**
- * Tests if a URL represents a JAR or ZIP archive.
+ * Tests if a URL represents a java archive.
+ * By default the JAR or ZIP archives are supported.
* If there is no such file object, the test is done by heuristic: any URL with an extension is
* treated as an archive.
* @param url a URL to a file
- * @return true if the URL seems to represent a ZIP-format archive
+ * @return true if the URL seems to represent a java archive
* @since 4.48
*/
public static boolean isArchiveFile(URL url) {
Parameters.notNull("url", url); //NOI18N
+ return isArchiveFileImpl(url, true);
+ }
- if ("jar".equals(url.getProtocol())) { //NOI18N
+ /**
+ * Tests if an file is inside an archive.
+ * @param fo the file to be tested
+ * @return true if the file is inside an archive
+ * @since 9.6
+ */
+ public static boolean isArchiveRoot(FileObject fo) {
+ Parameters.notNull("fileObject", fo); //NOI18N
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveRoot(fo)) {
+ return true;
+ }
+ }
+ return false;
+ }
- //Already inside archive, return false
- return false;
+ /**
+ * Tests if an {@link URL} denotes a file inside an archive.
+ * @param url the url to be tested
+ * @return true if the url points inside an archive
+ * @since 9.6
+ */
+ public static boolean isArchiveRoot(URL url) {
+ Parameters.notNull("url", url); //NOI18N
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveRoot(url)) {
+ return true;
+ }
}
-
- FileObject fo = URLMapper.findFileObject(url);
-
- if ((fo != null) && !fo.isVirtual()) {
- if (LOG.isLoggable(Level.FINEST)) {
- LOG.log(Level.FINEST, "isArchiveFile_FILE_RESOLVED", fo); //NOI18N, used by FileUtilTest.testIsArchiveFileRace
- }
- return isArchiveFile(fo);
- } else {
- return isArchiveFile(url.getPath());
- }
+ return false;
}
/**
@@ -2041,7 +1989,7 @@
u = BaseUtilities.toURI(entry).toURL();
isDir = entry.isDirectory();
} while (wasDir ^ isDir);
- if (isArchiveFile(u) || entry.isFile() && entry.length() < 4) {
+ if (isArchiveFileImpl(u, false)) {
return getArchiveRoot(u);
} else if (isDir) {
assert u.toExternalForm().endsWith("/"); //NOI18N
@@ -2354,13 +2302,27 @@
}
}
- /**
- * Tests if a non existent path represents a file.
- * @param path to be tested, separated by '/'.
- * @return true if the file has '.' after last '/'.
- */
- private static boolean isArchiveFile (final String path) {
- int index = path.lastIndexOf('.'); //NOI18N
- return (index != -1) && (index > path.lastIndexOf('/') + 1); //NOI18N
+ private static boolean isArchiveFileImpl(final URL url, final boolean strict) {
+ for (ArchiveRootProvider provider : getArchiveRootProviders()) {
+ if (provider.isArchiveFile(url, strict)) {
+ return true;
+ }
+ }
+ return false;
}
+
+ private static Iterable extends ArchiveRootProvider> getArchiveRootProviders() {
+ Lookup.Result res = archiveRootProviders.get();
+ if (res == null) {
+ res = new ProxyLookup(
+ Lookups.singleton(new JarArchiveRootProvider()),
+ Lookup.getDefault()).lookupResult(ArchiveRootProvider.class);
+ if (!archiveRootProviders.compareAndSet(null, res)) {
+ res = archiveRootProviders.get();
+ }
+ }
+ return res.allInstances();
+ }
+
+ private static final AtomicReference> archiveRootProviders = new AtomicReference<>();
}
diff --git a/openide.filesystems/src/org/openide/filesystems/JarArchiveRootProvider.java b/openide.filesystems/src/org/openide/filesystems/JarArchiveRootProvider.java
new file mode 100644
--- /dev/null
+++ b/openide.filesystems/src/org/openide/filesystems/JarArchiveRootProvider.java
@@ -0,0 +1,214 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2015 Sun Microsystems, Inc.
+ */
+package org.openide.filesystems;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.openide.filesystems.spi.ArchiveRootProvider;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Tomas Zezula
+ */
+final class JarArchiveRootProvider implements ArchiveRootProvider {
+ private static final String PROTOCOL = "jar"; //NOI18N
+ /** Normal header for ZIP files. */
+ private static byte[] ZIP_HEADER_1 = {0x50, 0x4b, 0x03, 0x04};
+ /** Also seems to be used at least in apisupport/project/test/unit/data/example-external-projects/suite3/nbplatform/random/modules/ext/stuff.jar; not known why */
+ private static byte[] ZIP_HEADER_2 = {0x50, 0x4b, 0x05, 0x06};
+ private static final Logger LOG = Logger.getLogger(JarArchiveRootProvider.class.getName());
+ /** Cache for {@link #isArchiveFile(FileObject)}. */
+ private static final Map*@GuardedBy("archiveFileCache")*/FileObject, Boolean> archiveFileCache = Collections.synchronizedMap(new WeakHashMap());
+
+ @Override
+ public boolean isArchiveFile(URL url, boolean strict) {
+ if (PROTOCOL.equals(url.getProtocol())) { //NOI18N
+ //Already inside archive, return false
+ return false;
+ }
+ FileObject fo = URLMapper.findFileObject(url);
+ if ((fo != null) && !fo.isVirtual()) {
+ if (LOG.isLoggable(Level.FINEST)) {
+ LOG.log(Level.FINEST, "isArchiveFile_FILE_RESOLVED", fo); //NOI18N, used by FileUtilTest.testIsArchiveFileRace
+ }
+ return isArchiveFile(fo, strict);
+ } else {
+ return isArchiveFile(url.getPath());
+ }
+ }
+
+ @Override
+ public boolean isArchiveFile(FileObject fo, boolean strict) {
+ if (!fo.isValid()) {
+ return isArchiveFile(fo.getPath());
+ }
+ // XXX Special handling of virtual file objects: try to determine it using its name, but don't cache the
+ // result; when the file is checked out the more correct method can be used
+ if (fo.isVirtual()) {
+ return isArchiveFile(fo.getPath());
+ }
+
+ if (fo.isFolder()) {
+ return false;
+ }
+
+ // First check the cache.
+ Boolean b = archiveFileCache.get(fo);
+ if (b == null) {
+ // Need to check it.
+ try {
+ InputStream in = fo.getInputStream();
+
+ try {
+ byte[] buffer = new byte[4];
+ int len = in.read(buffer, 0, 4);
+
+ if (len == 4) {
+ // Got a header, see if it is a ZIP file.
+ b = Boolean.valueOf(Arrays.equals(ZIP_HEADER_1, buffer) || Arrays.equals(ZIP_HEADER_2, buffer));
+ } else {
+ //If the length is less than 4, it can be either
+ //broken (empty) archive file or other empty file.
+ //Return false and don't cache it, when the archive
+ //file will be written and closed its length will change
+ return !strict;
+ }
+ } finally {
+ in.close();
+ }
+ } catch (IOException ioe) {
+ // #160507 - ignore exception (e.g. permission denied)
+ LOG.log(Level.FINE, null, ioe);
+ }
+ if (b == null) {
+ b = isArchiveFile(fo.getPath());
+ }
+ archiveFileCache.put(fo, b);
+ }
+ return b.booleanValue();
+ }
+
+
+ @Override
+ public boolean isArchiveRoot(URL url) {
+ return PROTOCOL.equals(url.getProtocol());
+ }
+
+ @Override
+ public URL getArchiveFile(URL url) {
+ String protocol = url.getProtocol();
+
+ if (PROTOCOL.equals(protocol)) { //NOI18N
+
+ String path = url.getPath();
+ int index = path.indexOf("!/"); //NOI18N
+
+ if (index >= 0) {
+ String jarPath = null;
+ try {
+ jarPath = path.substring(0, index);
+ if (jarPath.indexOf("file://") > -1 && jarPath.indexOf("file:////") == -1) { //NOI18N
+ /* Replace because JDK application classloader wrongly recognizes UNC paths. */
+ jarPath = jarPath.replaceFirst("file://", "file:////"); //NOI18N
+ }
+ return new URL(jarPath);
+
+ } catch (MalformedURLException mue) {
+ LOG.log(
+ Level.WARNING,
+ "Invalid URL ({0}): {1}, jarPath: {2}", //NOI18N
+ new Object[] {
+ mue.getMessage(),
+ url.toExternalForm(),
+ jarPath
+ });
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public FileObject getArchiveFile(FileObject fo) {
+ try {
+ final FileSystem fs = fo.getFileSystem();
+ if (fs instanceof JarFileSystem) {
+ final File jarFile = ((JarFileSystem) fs).getJarFile();
+ return FileUtil.toFileObject(jarFile);
+ }
+ } catch (FileStateInvalidException e) {
+ Exceptions.printStackTrace(e);
+ }
+ return null;
+ }
+
+ @Override
+ public URL getArchiveRoot(final URL url) {
+ try {
+ // XXX TBD whether the url should ever be escaped...
+ return new URL("jar:" + url + "!/"); // NOI18N
+ } catch (MalformedURLException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Tests if a non existent path represents a file.
+ * @param path to be tested, separated by '/'.
+ * @return true if the file has '.' after last '/'.
+ */
+ private static boolean isArchiveFile (final String path) {
+ int index = path.lastIndexOf('.'); //NOI18N
+ return (index != -1) && (index > path.lastIndexOf('/') + 1); //NOI18N
+ }
+
+}
diff --git a/openide.filesystems/src/org/openide/filesystems/spi/ArchiveRootProvider.java b/openide.filesystems/src/org/openide/filesystems/spi/ArchiveRootProvider.java
new file mode 100644
--- /dev/null
+++ b/openide.filesystems/src/org/openide/filesystems/spi/ArchiveRootProvider.java
@@ -0,0 +1,156 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2015 Sun Microsystems, Inc.
+ */
+package org.openide.filesystems.spi;
+
+import java.net.URL;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+
+/**
+ * A possibility to plug a support for java archives into FileUtil.
+ * The interface is used by {@link FileUtil.isArchiveRoot}, {@link FileUtil.isArchiveFile},
+ * {@link FileUtil.getArchiveRoot}, {@link FileUtil.getArchiveFile}.
+ * The implementations are registered in global lookup.
+ * @author Tomas Zezula
+ * @since 9.6
+ */
+public interface ArchiveRootProvider {
+
+ /**
+ * Tests if a file represents an java archive.
+ * @param url the file to be tested
+ * @param strict when false the detection may not be precise, for example
+ * an empty archive missing the archive header is treated as an archive
+ * @return true if the file looks like an archive
+ */
+ boolean isArchiveFile(URL url, boolean strict);
+
+ /**
+ * Tests if a file represents an java archive.
+ * The default implementation delegates to {@link ArchiveRootProvider#isArchiveFile(URL, boolean)},
+ * it can be overridden by an implementation in more efficient way.
+ * @param fo the file to be tested
+ * @param strict when false the detection may not be precise, for example
+ * an empty archive missing the archive header is treated as an archive
+ * @return true if the file looks like an archive
+ */
+ default boolean isArchiveFile(FileObject fo, boolean strict) {
+ final URL url = URLMapper.findURL(fo, URLMapper.EXTERNAL);
+ return url == null ? false : isArchiveFile(url, strict);
+ }
+
+ /**
+ * Tests if an {@link URL} denotes a file inside an archive.
+ * @param url the url to be tested
+ * @return true if the url points inside an archive
+ */
+ boolean isArchiveRoot(URL url);
+
+ /**
+ * Tests if an file is inside an archive.
+ * The default implementation delegates to {@link ArchiveRootProvider#isArchiveRoot(URL)},
+ * it can be overridden by an implementation in more efficient way.
+ * @param fo the file to be tested
+ * @return true if the file is inside an archive
+ */
+ default boolean isArchiveRoot(FileObject fo) {
+ final URL url = URLMapper.findURL(fo, URLMapper.EXTERNAL);
+ return url == null ? false : isArchiveRoot(url);
+ }
+
+ /**
+ * Returns the URL of the archive file containing the file
+ * referred to by a archive-protocol URL.
+ * Remember that any path within the archive is discarded
+ * so you may need to check for non-root entries.
+ * @param url a URL
+ * @return the embedded archive URL, or null if the URL is not an
+ * archive-protocol URL containing !/
+ */
+ URL getArchiveFile(URL url);
+
+ /**
+ * Returns a FileObject representing an archive file containing the
+ * FileObject given by the parameter.
+ * Remember that any path within the archive is discarded
+ * so you may need to check for non-root entries.
+ * The default implementation delegates to {@link ArchiveRootProvider#getArchiveFile(URL)},
+ * it can be overridden by an implementation in more efficient way.
+ * @param fo a file in a archive filesystem
+ * @return the file corresponding to the archive itself,
+ * or null if fo
is not an archive entry
+ */
+ default FileObject getArchiveFile(FileObject fo) {
+ final URL rootURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
+ if (rootURL == null) {
+ return null;
+ }
+ return URLMapper.findFileObject(FileUtil.getArchiveFile(rootURL));
+ }
+
+ /**
+ * Returns an URL representing the root of an archive.
+ * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
+ * refers to an archive file.
+ * @param url of an java archive file
+ * @return the archive-protocol URL of the root of the archive
+ */
+ URL getArchiveRoot(URL url);
+
+ /**
+ * Returns a FileObject representing the root folder of an archive.
+ * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
+ * if the file object refers to an archive file.
+ * The default implementation delegates to {@link ArchiveRootProvider#getArchiveRoot(URL)},
+ * it can be overridden by an implementation in more efficient way.
+ * @param fo an java archive file
+ * @return a virtual archive root folder, or null if the file is not actually an archive
+ */
+ default FileObject getArchiveRoot(FileObject fo) {
+ final URL archiveURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
+ if (archiveURL == null) {
+ return null;
+ }
+ return URLMapper.findFileObject(FileUtil.getArchiveRoot(archiveURL));
+ }
+}
diff --git a/openide.filesystems/test/unit/src/org/openide/filesystems/FileUtilTest.java b/openide.filesystems/test/unit/src/org/openide/filesystems/FileUtilTest.java
--- a/openide.filesystems/test/unit/src/org/openide/filesystems/FileUtilTest.java
+++ b/openide.filesystems/test/unit/src/org/openide/filesystems/FileUtilTest.java
@@ -276,7 +276,7 @@
final File testFile = new File (wd,"test.jar"); //NOI18N
FileUtil.createData(testFile);
- final Logger log = Logger.getLogger(FileUtil.class.getName());
+ final Logger log = Logger.getLogger(JarArchiveRootProvider.class.getName());
log.setLevel(Level.FINEST);
final Handler handler = new Handler() {
@Override
diff --git a/projectapi/nbproject/project.properties b/projectapi/nbproject/project.properties
--- a/projectapi/nbproject/project.properties
+++ b/projectapi/nbproject/project.properties
@@ -43,7 +43,7 @@
is.autoload=true
javac.compilerargs=-Xlint -Xlint:-serial
-javac.source=1.6
+javac.source=1.7
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/projectapi/nbproject/project.xml b/projectapi/nbproject/project.xml
--- a/projectapi/nbproject/project.xml
+++ b/projectapi/nbproject/project.xml
@@ -72,7 +72,7 @@
- 9.0
+ 9.6
diff --git a/projectapi/src/org/netbeans/api/project/FileOwnerQuery.java b/projectapi/src/org/netbeans/api/project/FileOwnerQuery.java
--- a/projectapi/src/org/netbeans/api/project/FileOwnerQuery.java
+++ b/projectapi/src/org/netbeans/api/project/FileOwnerQuery.java
@@ -44,7 +44,9 @@
package org.netbeans.api.project;
+import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -130,19 +132,29 @@
* @throws IllegalArgumentException if the URI is relative or opaque
*/
public static Project getOwner(URI uri) {
- if ("jar".equals(uri.getScheme())) {
- try {
- URL jar = FileUtil.getArchiveFile(uri.toURL());
- if (jar != null) {
- uri = jar.toURI();
+// if ("jar".equals(uri.getScheme())) {
+// try {
+// URL jar = FileUtil.getArchiveFile(uri.toURL());
+// if (jar != null) {
+// uri = jar.toURI();
+// }
+// } catch (Exception x) {
+// LOG.log(Level.INFO, null, x);
+// }
+// }
+ try {
+ URL url = uri.toURL();
+ if (FileUtil.isArchiveRoot(url)) {
+ url = FileUtil.getArchiveFile(url);
+ if (url != null) {
+ uri = url.toURI();
}
- } catch (Exception x) {
- LOG.log(Level.INFO, null, x);
}
+ } catch (MalformedURLException | URISyntaxException e) {
+ LOG.log(Level.INFO, null, e);
}
if (!uri.isAbsolute() || uri.isOpaque()) {
- //throw new IllegalArgumentException("Bad URI: " + uri); // NOI18N
- return null;
+ throw new IllegalArgumentException("Bad URI: " + uri); // NOI18N
}
for (FileOwnerQueryImplementation q : getInstances()) {
Project p = q.getOwner(uri);