org.openide.filesystems.URLMapper Maven / Gradle / Ivy
/*
* 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.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;
import org.openide.util.BaseUtilities;
/** 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).
*
* Typical protocols used: file
for disk files (see {@link File#toURI});
* jar
to wrap other URLs (e.g. jar:file:/some/thing.jar!/some/entry).
*
*/
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 result;
private static final List CACHE_JUST_COMPUTING = new ArrayList();
private static final ThreadLocal> threadCache = new ThreadLocal>();
static {
DefaultURLMapperProxy.setDefault(new DefaultURLMapper());
reset();
}
/** Cache of all available URLMapper instances. */
private static List cache;
/** Reset cache, for use from unit tests. */
static void reset() {
cache = null;
result = Lookup.getDefault().lookupResult(URLMapper.class);
result.addLookupListener(
new LookupListener() {
public void resultChanged(LookupEvent ev) {
synchronized (URLMapper.class) {
cache = null;
}
}
}
);
}
/** Find a good URL for this file object which works according to type:
*
* - inside this VM
*
- inside this machine
*
- from networked machines
*
* @return a suitable URL, or (only if not {@link #INTERNAL}) 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) {
return FileURL.encodeFileObject(fo);
}
return null;
}
/** Get a good URL for this file object which works according to type:
*
* - inside this VM
*
- inside this machine
*
- from networked machines
*
* 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
* findURL(FileObject fo, int type)
.
* @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 retSet = new LinkedHashSet();
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 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.
* The only reason to return an array here
* is for backward compatibility.
* @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 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 res = null;
try {
res = new ArrayList(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 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(FileURL.PROTOCOL)) { //// 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 list = new LinkedList();
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 {
// XXX clumsy; see ArchiveURLMapper for possible cleaner style
String toReplace = "__EXCLAMATION_REPLACEMENT__";//NOI18N
retURL = new URL(
"jar:" + BaseUtilities.toURI(new File(f,toReplace + fo.getPath())).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 = BaseUtilities.toURI(fFile).toURL();
if (retVal != null && fo.isFolder()) {
// #155742,160333 - URL for folder must always end with slash
final String urlDef = retVal.toExternalForm();
final String pathSeparator = "/";//NOI18N
if (!urlDef.endsWith(pathSeparator)) {
retVal = new URL(urlDef + pathSeparator);
}
}
return 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(BaseUtilities.toFile(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 {
// XXX new URI("substring").getPath() might be better?
entryName = URLDecoder.decode(spec.substring(separator, spec.length()),"UTF-8");
} catch (UnsupportedEncodingException ex) {
return;
}
}
}
}
File getJarFile() {
return jarFile;
}
String getEntryName() {
return entryName;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy