/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* 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.
*/
package org.openide.filesystems;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.netbeans.modules.openide.filesystems.DefaultURLMapperProxy;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
/** Mapper from FileObject -> URL.
* Should be registered in default lookup. For details see {@link Lookup#getDefault()}.
* For all methods, if the passed-in file object is the root folder
* of some filesystem, then it is assumed that any valid file object
* in that filesystem may also have a URL constructed for it by means
* of appending the file object's resource path to the URL of the root
* folder. If this cannot work for all file objects on the filesystem,
* the root folder must not be assigned a URL of that type. nbfs: URLs
* of course always work correctly in this regard.
* @since 2.16
*/
public abstract class URLMapper {
/**
* URL which works inside this VM.
* Not guaranteed to work outside the VM (though it may).
*/
public static final int INTERNAL = 0;
/**
* URL which works inside this machine.
* Not guaranteed to work from other machines (though it may).
* <div class="nonnormative">
* Typical protocols used: <code>file</code> for disk files (see {@link File#toURI});
* <code>jar</code> to wrap other URLs (e.g. <samp>jar:file:/some/thing.jar!/some/entry</samp>).
* </div>
*/
public static final int EXTERNAL = 1;
/** URL which works from networked machines.*/
public static final int NETWORK = 2;
/** results with URLMapper instances*/
private static Lookup.Result<URLMapper> result;
private static final List<URLMapper> CACHE_JUST_COMPUTING = new ArrayList<URLMapper>();
private static final ThreadLocal<List<URLMapper>> threadCache = new ThreadLocal<List<URLMapper>>();
static {
DefaultURLMapperProxy.setDefault(new DefaultURLMapper());
result = Lookup.getDefault().lookupResult(URLMapper.class);
result.addLookupListener(
new LookupListener() {
public void resultChanged(LookupEvent ev) {
synchronized (URLMapper.class) {
cache = null;
}
}
}
);
}
/** Basic impl. for JarFileSystem, LocalFileSystem, MultiFileSystem */
private static URLMapper defMapper;
/** Cache of all available URLMapper instances. */
private static List<URLMapper> cache;
/** Find a good URL for this file object which works according to type:
* <ul>
* <li>inside this VM
* <li>inside this machine
* <li>from networked machines
* </ul>
* @return a suitable URL, or null
*/
public static URL findURL(FileObject fo, int type) {
/** secondly registered URLMappers are asked to resolve URL */
for (URLMapper mapper : getInstances()) {
URL retVal = mapper.getURL(fo, type);
if (retVal != null) {
return retVal;
}
}
// if not resolved yet then internal URL with nbfs protocol is returned
// XXX this would be better handled by making DefaultURLMapper just return nbfs for INTERNAL when necessary!
if (type == INTERNAL) {
try {
return FileURL.encodeFileObject(fo);
} catch (FileStateInvalidException iex) {
// ignore
}
}
return null;
}
/** Get a good URL for this file object which works according to type:
* <ul>
* <li>inside this VM
* <li>inside this machine
* <li>from networked machines
* </ul>
* The implementation can't use neither {@link FileUtil#toFile} nor {@link FileUtil#toFileObject}
* otherwise StackOverflowError maybe thrown.
* @return a suitable URL, or null
*/
public abstract URL getURL(FileObject fo, int type);
/** Find an array of FileObjects for this URL.
* Zero or more FOs may be returned.
*
* For each returned FO, it must be true that FO -> URL gives the
* exact URL which was passed in, but depends on appropriate type
* <code> findURL(FileObject fo, int type) </code>.
* @param url to wanted FileObjects
* @return a suitable array of FileObjects, or empty array if not successful
* @since 2.22
* @deprecated Use {@link #findFileObject} instead.
*/
@Deprecated
public static FileObject[] findFileObjects(URL url) {
Set<FileObject> retSet = new LinkedHashSet<FileObject>();
for (URLMapper mapper: getInstances()) {
FileObject[] retVal = mapper.getFileObjects(url);
if (retVal != null) {
retSet.addAll(Arrays.asList(retVal));
}
}
return retSet.toArray(new FileObject[retSet.size()]);
}
/** Find an appropriate instance of FileObject that addresses this URL
*
* @param url URL to be converted to file object
* @return file object corresponding to URL or null if no one was found
* @since 4.29
*/
public static FileObject findFileObject(URL url) {
if (url == null) {
throw new NullPointerException("Cannot pass null URL to URLMapper.findFileObject"); // NOI18N
}
/** first basic implementation */
FileObject[] results = null;
Iterator<URLMapper> instances = getInstances().iterator();
while (instances.hasNext() && ((results == null) || (results.length == 0))) {
URLMapper mapper = instances.next();
results = mapper.getFileObjects(url);
}
return ((results != null) && (results.length > 0)) ? results[0] : null;
}
/**
* Get an array of FileObjects for this URL.
* There is no reason to return array
* with size greater than one because method {@link #findFileObject findFileObject}
* uses just the first element (subsequent elements will not be accepted anyway).
* The implementation cannot use either {@link FileUtil#toFile} nor {@link FileUtil#toFileObject}
* or StackOverflowError may be thrown.
* <p class="nonnormative">The only reason to return an array here
* is for backward compatibility.</p>
* @param url to wanted FileObjects
* @return an array of FileObjects with size no greater than one, or null
* @since 2.22
*/
public abstract FileObject[] getFileObjects(URL url);
/** Returns all available instances of URLMapper.
* @return list of URLMapper instances
*/
private static List<URLMapper> getInstances() {
synchronized (URLMapper.class) {
if (cache != null) {
if ((cache != CACHE_JUST_COMPUTING) || (threadCache.get() == CACHE_JUST_COMPUTING)) {
return cache;
}
}
// Set cache to empty array here to prevent infinite loop.
// See issue #41358, #43359
cache = CACHE_JUST_COMPUTING;
threadCache.set(CACHE_JUST_COMPUTING);
}
ArrayList<URLMapper> res = null;
try {
res = new ArrayList<URLMapper>(result.allInstances());
{
// XXX hack to put default last, since we cannot easily adjust META-INF/services/o.o.f.URLM order to our tastes
// (would need to ask *all other* impls to be earlier somewhere)
URLMapper def = null;
Iterator<URLMapper> it = res.iterator();
while (it.hasNext()) {
URLMapper m = it.next();
if (m instanceof DefaultURLMapperProxy) {
def = m;
it.remove();
break;
}
}
if (def != null) {
res.add(def);
}
}
return res;
} finally {
synchronized (URLMapper.class) {
if (cache == CACHE_JUST_COMPUTING) {
cache = res;
}
threadCache.set(null);
}
}
}
/*** Basic impl. for JarFileSystem, LocalFileSystem, MultiFileSystem */
private static class DefaultURLMapper extends URLMapper {
DefaultURLMapper() {
}
// implements URLMapper.getFileObjects(URL url)
public FileObject[] getFileObjects(URL url) {
String prot = url.getProtocol();
if (prot.equals("nbfs")) { //// NOI18N
FileObject retVal = FileURL.decodeURL(url);
return (retVal == null) ? null : new FileObject[] { retVal };
}
if (prot.equals("jar")) { //// NOI18N
return getFileObjectsForJarProtocol(url);
}
if (prot.equals("file")) { //// NOI18N
File f = toFile(url);
if (f != null) {
FileObject[] foRes = findFileObjectsInRepository(f);
if ((foRes != null) && (foRes.length > 0)) {
return foRes;
}
}
}
return null;
}
private FileObject[] findFileObjectsInRepository(File f) {
if (!f.equals(FileUtil.normalizeFile(f))) {
throw new IllegalArgumentException(
"Parameter file was not " + // NOI18N
"normalized. Was " + f + " instead of " + FileUtil.normalizeFile(f)
); // NOI18N
}
@SuppressWarnings("deprecation") // keep for backward compatibility w/ NB 3.x
Enumeration<? extends FileSystem> en = Repository.getDefault().getFileSystems();
LinkedList<FileObject> list = new LinkedList<FileObject>();
String fileName = f.getAbsolutePath();
while (en.hasMoreElements()) {
FileSystem fs = en.nextElement();
String rootName = null;
FileObject fsRoot = fs.getRoot();
File root = findFileInRepository(fsRoot);
if (root == null) {
Object rootPath = fsRoot.getAttribute("FileSystem.rootPath"); //NOI18N
if ((rootPath != null) && (rootPath instanceof String)) {
rootName = (String) rootPath;
} else {
continue;
}
}
if (rootName == null) {
rootName = root.getAbsolutePath();
}
/**root is parent of file*/
if (fileName.indexOf(rootName) == 0) {
String res = fileName.substring(rootName.length()).replace(File.separatorChar, '/');
FileObject fo = fs.findResource(res);
File file2Fo = (fo != null) ? findFileInRepository(fo) : null;
if ((fo != null) && (file2Fo != null) && f.equals(file2Fo)) {
if (fo.getClass().toString().indexOf("org.netbeans.modules.masterfs.MasterFileObject") != -1) { //NOI18N
list.addFirst(fo);
} else {
list.addLast(fo);
}
}
}
}
FileObject[] results = new FileObject[list.size()];
list.toArray(results);
return results;
}
// implements URLMapper.getURL(FileObject fo, int type)
public URL getURL(FileObject fo, int type) {
if (fo == null) {
return null;
}
if (type == NETWORK) {
return null;
}
if (fo instanceof MultiFileObject && (type == INTERNAL)) {
// Stick to nbfs protocol, otherwise URL calculations
// get messed up. See #39613.
return null;
}
File fFile = findFileInRepository(fo);
if (fFile != null) {
try {
return toURL(fFile, fo);
} catch (MalformedURLException mfx) {
assert false : mfx;
return null;
}
}
URL retURL = null;
FileSystem fs = null;
try {
fs = fo.getFileSystem();
} catch (FileStateInvalidException fsex) {
return null;
}
if (fs instanceof JarFileSystem) {
JarFileSystem jfs = (JarFileSystem) fs;
File f = jfs.getJarFile();
if (f == null) {
return null;
}
try {
String toReplace = "__EXCLAMATION_REPLACEMENT__";//NOI18N
retURL = new URL(
"jar:" + new File(f,toReplace + fo.getPath()).toURI().toString().replaceFirst("/"+toReplace,"!/") + // NOI18N
((fo.isFolder() && !fo.isRoot()) ? "/" : "")
); // NOI18N
} catch (MalformedURLException mfx) {
mfx.printStackTrace();
return null;
}
} else if (fs instanceof XMLFileSystem) {
URL retVal = null;
try {
retVal = ((XMLFileSystem) fs).getURL(fo.getPath());
if (retVal == null) {
return null;
}
if (type == INTERNAL) {
return retVal;
}
boolean isInternal = retVal.getProtocol().startsWith("nbres"); //NOI18N
if ((type == EXTERNAL) && !isInternal) {
return retVal;
}
return null;
} catch (FileNotFoundException fnx) {
return null;
}
}
return retURL;
}
private static URL toURL(File fFile, FileObject fo)
throws MalformedURLException {
URL retVal = null;
if (fo.isFolder() && !fo.isValid()) {
String urlDef = fFile.toURI().toURL().toExternalForm();
String pathSeparator = "/"; //NOI18N
if (!urlDef.endsWith(pathSeparator)) {
retVal = new URL(urlDef + pathSeparator);
}
}
return (retVal == null) ? fFile.toURI().toURL() : retVal;
}
private static File findFileInRepository(FileObject fo) {
File f = (File) fo.getAttribute("java.io.File"); // NOI18N
return (f != null) ? FileUtil.normalizeFile(f) : null;
}
private static FileObject[] getFileObjectsForJarProtocol(URL url) {
FileObject retVal = null;
JarURLParser jarUrlParser = new JarURLParser(url);
File file = jarUrlParser.getJarFile();
String entryName = jarUrlParser.getEntryName();
if (file != null) {
JarFileSystem fs = findJarFileSystem(file);
if (fs != null) {
if (entryName == null) {
entryName = ""; // #39190: root of JAR
}
retVal = fs.findResource(entryName);
}
}
return (retVal == null) ? null : new FileObject[] { retVal };
}
private static JarFileSystem findJarFileSystem(File jarFile) {
JarFileSystem retVal = null;
@SuppressWarnings("deprecation") // keep for backward compatibility w/ NB 3.x
Enumeration<? extends FileSystem> en = Repository.getDefault().getFileSystems();
while (en.hasMoreElements()) {
FileSystem fs = en.nextElement();
if (fs instanceof JarFileSystem) {
File fsJarFile = ((JarFileSystem) fs).getJarFile();
if (fsJarFile.equals(jarFile)) {
retVal = (JarFileSystem) fs;
break;
}
}
}
return retVal;
}
private static File toFile(URL u) {
if (u == null) {
throw new NullPointerException();
}
try {
URI uri = new URI(u.toExternalForm());
return FileUtil.normalizeFile(new File(uri));
} catch (URISyntaxException use) {
// malformed URL
return null;
} catch (IllegalArgumentException iae) {
// not a file: URL
return null;
}
}
private static class JarURLParser {
private File jarFile;
private String entryName;
JarURLParser(URL originalURL) {
parse(originalURL);
}
/** copy & pasted from JarURLConnection.parse*/
void parse(URL originalURL) {
String spec = originalURL.getFile();
int separator = spec.indexOf('!');
if (separator != -1) {
try {
jarFile = toFile(new URL(spec.substring(0, separator++)));
entryName = null;
} catch (MalformedURLException e) {
return;
}
/* if ! is the last letter of the innerURL, entryName is null */
if (++separator != spec.length()) {
try {
entryName = URLDecoder.decode(spec.substring(separator, spec.length()),"UTF-8");
} catch (UnsupportedEncodingException ex) {
return;
}
}
}
}
File getJarFile() {
return jarFile;
}
String getEntryName() {
return entryName;
}
}
}
}
|