All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.qt.internal.QtJambiResources Maven / Gradle / Ivy

The newest version!
/****************************************************************************
**
** Copyright (C) 1992-2009 Nokia. All rights reserved.
** Copyright (C) 2009-2022 Dr. Peter Droste, Omix Visualization GmbH & Co. KG. All rights reserved.
**
** This file is part of Qt Jambi.
**
** ** $BEGIN_LICENSE$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
** 
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** 
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
** 
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
** 
** If you are unsure which license is appropriate for your use, please
** contact the sales department at [email protected].
** $END_LICENSE$

**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
package io.qt.internal;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;

import io.qt.NativeAccess;
import io.qt.core.QDate;
import io.qt.core.QDateTime;
import io.qt.core.QDir;
import io.qt.core.QTime;

public final class QtJambiResources {
	
	static {
		QtJambi_LibraryUtilities.initialize();
	}
	
	private QtJambiResources() {
	}

    private static class URLAlias {
        URLAlias(URL url, File file) {
			super();
			this.url = url;
			this.file = file;
		}
		final URL url;
		final File file;
    }

	private static JarCache cache;
    private static String currentDirectory;

	public static void addSearchPath(String path) {
		initialize();
		URL url = makeUrl(path);
		if (url != null) {
			cache.addPath(url);
		}
	}

	public static void removeSearchPath(String path) {
		initialize();
		URL url = makeUrl(path);
		if (url != null) {
			cache.removePath(url);
		}
	}

	private static synchronized void initialize() {
		if (cache == null) {
			cache = new JarCache();

			List cpUrls = new ArrayList();

			try {
				Set modules = new HashSet<>();
				RetroHelper.findModules(modules);
				for (URL url : modules) {
					if(!cpUrls.contains(url)) {
						cache.addPath(url);
						cpUrls.add(url);
					}
				}
				for(ClassLoader loader : RetroHelper.classLoaders()) {
					if(loader instanceof URLClassLoader) {
						for(URL url : ((URLClassLoader) loader).getURLs()) {
							if(!cpUrls.contains(url)) {
								cache.addPath(url);
								cpUrls.add(url);				
							}
						}
					}else {
						Enumeration urls = loader.getResources("META-INF/MANIFEST.MF");
						while (urls.hasMoreElements()) {
							URL url = urls.nextElement();
							if ("jar".equals(url.getProtocol())) {
								try {
									String f = url.getPath();
									int bang = f.indexOf("!");
									if (bang >= 0)
										f = f.substring(0, bang);
		
									if (f.trim().length() > 0) {
										URL fileUrl = new URL(f);
										if(!cpUrls.contains(fileUrl)) {
											cache.addPath(fileUrl);
											cpUrls.add(fileUrl);
										}
									}
								} catch (Exception e) {
									java.util.logging.Logger.getLogger("io.qt.internal.fileengine")
											.log(java.util.logging.Level.SEVERE, "", e);
								}
							}
						}
					}
				}
			} catch (Exception e) {
				java.util.logging.Logger.getLogger("io.qt.internal.fileengine").log(java.util.logging.Level.SEVERE, "",
						e);
			}

			String javaClassPath = System.getProperty("java.class.path");
			if (javaClassPath == null)
				javaClassPath = ""; // gets ignored below
			String javaModulePath = System.getProperty("jdk.module.path");
			if (javaModulePath != null)
				javaClassPath = javaModulePath + File.pathSeparator + javaClassPath; // gets ignored below
			String paths[] = javaClassPath.split("\\" + File.pathSeparator);

			// Only add the .jar files that are not already added...
			int k = 0;
			for (String p : paths) {
				if (p.trim().length() > 0) {
					k++; // count all paths, invalid and valid

					URL url = makeUrl(p);
					boolean match = false;

					if(url!=null){
						cache.addPath(url);
					}else{
						continue;
					}

					MyJarFile myJarFile2 = null;
					MyJarFile myJarFile1 = null;
					try {
						myJarFile2 = resolveUrlToMyJarFile(url);
						if (myJarFile2 != null) {
							for (URL otherURL : cpUrls) {
								myJarFile1 = resolveUrlToMyJarFile(otherURL);
								if (myJarFile1 != null) {
									File file1 = new File(myJarFile1.getName());
									File file2 = new File(myJarFile2.getName());
									if (file1.getCanonicalPath().equals(file2.getCanonicalPath())) {
										match = true;
										break;
									}
								}
							}
						}
					} catch (Exception e) { // This should probably just be IOException
						java.util.logging.Logger.getLogger("io.qt.internal.fileengine")
								.log(java.util.logging.Level.SEVERE, "", e); // this has been so useful in finding many
																				// bugs/issues
					} finally {
						if (myJarFile2 != null) {
							myJarFile2.put();
							myJarFile2 = null;
						}
						if (myJarFile1 != null) {
							myJarFile1.put();
							myJarFile1 = null;
						}
					}

					if (!match)
						cache.addPath(url);
				}
			}

			// If there are no paths set in java.class.path, we do what Java does and
			// add the current directory; at least ask Java what the current directory
			// is and not Qt.
			if (k == 0) {
				// FIXME: Use JVM cwd notion
				try {
					cache.addPath(new URL("file:" + QDir.currentPath()));
				} catch (MalformedURLException e) {
					java.util.logging.Logger.getLogger("io.qt.internal.fileengine")
					.log(java.util.logging.Level.SEVERE, "", e);
				}
			}
		}
	}

	@NativeAccess
	private static URL makeUrl(String path) {
		URL result = null;
		if (path != null) {
			File file = new File(path);
			if(file.exists()) {
				try {
					result = file.toURI().toURL();
				} catch (Exception e) {
				}
			}
			if(result==null) {
				try {
					result = new URL(path);
				} catch (Exception e) {
				}
			}
			if(result==null) {
				final int pathLength = path.length();
		
				boolean skipTryAsis = false; // attempt to not use exceptions for common situations
				if (pathLength > 0) {
					char firstChar = path.charAt(0);
					// Both a "/" and "\\" are illegal characters in the scheme/protocol.
					if (firstChar == File.separatorChar) {
						skipTryAsis = true;
					} else if (pathLength == 1) {
						if (firstChar == '.') {
							// FIXME: ../../foo/bar
							// Special case for current directory
							String tmpPath = resolveCurrentDirectory();
							if (tmpPath != null) {
								path = tmpPath;
								skipTryAsis = true;
							}
						}
						// ELSE it is a relative path and will be picked up below
					} else if (pathLength > 1) {
						char secondChar = path.charAt(1);
						if (firstChar == '.' && secondChar == File.separatorChar) {
							// ./foo/bar case
							String tmpPath = resolveCurrentDirectory();
							if (tmpPath != null) {
								// FIXME: This is better resolved later via path canonicalization (to fix the
								// ./././foo case)
								path = tmpPath + File.separatorChar + path.substring(2);
								skipTryAsis = true;
							}
						} else if (pathLength > 2) {
							// Windows "C:\\..." for which "\\" is incorrect for URLs
							char thirdChar = path.charAt(2);
							if ((firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z')) {
								// We don't check for '/' since that might be a real URL "a://host:port/path?qs"
								// and would be invalid for windows using java.io.File API anyway.
								if (secondChar == ':' && thirdChar == '\\')
									skipTryAsis = true;
								if (secondChar == ':' && thirdChar == '/') // "C:/dir1/dir2/file.dat" is seen when processing
																			// paths from QFileInfo
									skipTryAsis = true;
							} else if (pathLength > 3) {
								// Eclipse "/C:/..."
								char fourthChar = path.charAt(3);
								if (firstChar == '/' && (secondChar >= 'A' && secondChar <= 'Z')
										|| (secondChar >= 'a' && secondChar <= 'z')) {
									if (thirdChar == ':' && fourthChar == '/')
										skipTryAsis = true; // we prefix it with file:// below
								}
							}
						}
					}
					if (skipTryAsis == false) {
						// If skipTryAsis==true then we found an absolute path (or converted
						// what is there already to an absolute path)
						boolean prefix = true;
						// Eek... temporary hack, some users of this method makeUrl() seem to be
						// already passing a valid URL as input. This is one point to fix in a
						// later review. For now be blacklist known prefixes from being treated
						// as relative paths. FIXME
						if (path.startsWith("file:") || path.startsWith("jar:") || path.startsWith("http:")
								|| path.startsWith("https:"))
							prefix = false;
						int colon = path.indexOf(':');
						if(colon>0) {
							String protocol = path.substring(0, colon);
							try {
								new URL(protocol+"://");
								prefix = false;
							} catch (MalformedURLException e) {
							}
						}
						if (prefix) {
							String tmpPath = resolveCurrentDirectory();
							if (tmpPath != null) {
								String _path = tmpPath + File.separatorChar + path;
								if(new File(_path).exists()) {
									path = _path;
									skipTryAsis = true;
								}
							}
						}
					}
				}
		
				if (result == null) {
					try {
						// Validate the URL we build is well-formed
						// FIXME: file://
						String xPath = path.replace('\\', '/');
						String xPrefix;
						if (path.length() > 0 && xPath.charAt(0) != '/')
							xPrefix = "file:///";
						else
							xPrefix = "file://";
						String newTmpPath = xPrefix;
						if (File.separatorChar == '\\')
							newTmpPath += path.replace('\\', '/'); // windows
						else
							newTmpPath += path;
						result = new URL(newTmpPath);
					} catch (Exception e) {
					}
				}
			}
	
			InputStream inStream = null;
			try {
				URLAlias urlAlias = checkNeedWorkaround(result);
				if (urlAlias.file != null) { // Due to workaround
					if (urlAlias.file.isFile()) // skip dirs
						inStream = new FileInputStream(urlAlias.file);
				} else if(!urlAlias.url.toString().endsWith("/")){
					URLConnection urlConn = urlAlias.url.openConnection();
					inStream = urlConn.getInputStream();
				}
			} catch (Exception e) {
				if(!result.getProtocol().equals("jrt"))
					java.util.logging.Logger.getLogger("io.qt.internal.fileengine").log(java.util.logging.Level.SEVERE, ""+result, e);
			} finally {
				if (inStream != null) {
					try {
						inStream.close();
					} catch (IOException eat) {
					}
				}
			}
		}
		return result;
	}
	
	@NativeAccess
	private static Collection pathToJarFiles(String entry) {
		initialize();
		return cache.pathToJarFiles(entry);
	}
	
	@NativeAccess
	private static boolean isDirectory(MyJarFile myJarFile, String fileName) {
		boolean isDirectory = false;
        JarEntry fileInJar = myJarFile.getJarEntry(fileName);

        // If the entry exists in the given file, look it up and
        // check if its a dir or not
        if (fileInJar != null) {
            isDirectory = fileInJar.isDirectory();
            if (!isDirectory) {
                boolean tmpIsDirectory = checkIsDirectory(myJarFile, fileInJar);
                isDirectory = tmpIsDirectory;
            } else {
            }
        }

        if (!isDirectory) {
            // Otherwise, look if the directory exists in the
            // cache...
        	Collection pathToJarFiles = pathToJarFiles(fileName);
            String jarFileName = myJarFile.getName();
            if (pathToJarFiles != null) {
                for (String thisPathToJar : pathToJarFiles) {
                    if (thisPathToJar.equals(jarFileName)) {
                        isDirectory = true;
                        break;
                    }
                }
            }

            // Nasty fallback... Iterate through the .jar file and try to check if
            // fileName is the prefix (hence directory) of any of the entries...
            if (!isDirectory) {
                String fileNameWithSlash = fileName + "/";
                Enumeration entries = myJarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String entryName = entry.getName();
                    if (entryName.startsWith(fileNameWithSlash)) {
                        isDirectory = true;
                        break;
                    }
                }
            }
        }
        return isDirectory;
	}

    /**
     * The JarEntry.isDirectory() method in Java returns false
     * even for directories, so we need this extra check
     * which tries to read a byte from the entry in order
     * to trigger an exception when the entry is a directory.
     */
	static boolean checkIsDirectory(MyJarFile myJarFile, JarEntry fileInJar) {
        InputStream inStream = null;
        try {
            // CHECKME is this hack/trick/kludge maybe somewhat problematic
            //  for connection handler based JarFile handles ?
            inStream = myJarFile.getInputStream(fileInJar);
            if(inStream == null)
                return true;	// avoid NPE
            inStream.read();
        } catch(IOException e) {
            return true;
        } finally {
            if(inStream != null) {
                try {
                    inStream.close();
                } catch(IOException eat) {
                }
                inStream = null;
            }
        }

        return false;
    }

	@NativeAccess
	private static MyJarFile resolveUrlToMyJarFile(URL url) throws IOException, ZipException {
        MyJarFile myJarFile = null;
        URLAlias urlAlias = checkNeedWorkaround(url);
        try {
            if(urlAlias.file != null) {  // Due to workaround
                if(urlAlias.file.isFile()) // skip dirs
                    myJarFile = new MyJarFile(urlAlias.file);
            } else if(!"jrt".equals(urlAlias.url.getProtocol())){
                 myJarFile = new MyJarFile(urlAlias.url);
            }
        } catch(ZipException e) {
            // This often fails with "java.util.zip.ZipException: error in opening zip file" but never discloses the filename
            throw new ZipException(e.getMessage() + ": " + url);
        }
        return myJarFile;
    }
    


    private static URLAlias checkNeedWorkaround(URL url) {
        final String SEPARATOR = "!/";
        File file = null;
        // Sun/Oracle bugid#7050028  "IllegalStateException: zip file closed" from JarURLConnection
        // Workarounds 1) Do not turn off URLConnection caches.
        //             2) Use file:/x.jar URLs rather than jar:file:x.jar!/ URLs for URLClassLoader
        if("jar".equals(url.getProtocol())) {
            // Replace "^jar:file:" with "file:" only if it also ends with "!/"
            final String PREFIX_JAR_FILE = "jar:file:";
            String externalForm = url.toExternalForm();
            if(externalForm.startsWith(PREFIX_JAR_FILE)) {
                // Don't use URL#toExternalForm() as this results in conversions in path to URL encoded
                //  such as s/ /%20/ this will break java.io.File API usage on the resulting path.
                String path = url.getPath();  // this has "file:///" prefix already and "!/" suffix
                int index = externalForm.indexOf(SEPARATOR);
                int externalFormLength = externalForm.length();
                // check the first "!/" is also the last and at the end of the string
                if(index == externalFormLength - 2) {
                    // Ok we implement the workaround (start at 4 as we need to keep the "file:"
                    //externalForm = externalForm.substring(4, externalFormLength - SEPARATOR.length());
                    // this approach caused %20 to appear which broke things later on.
                    String urlString = stripUrlFileColonPrefix(path, path);
                    urlString = stripSuffix(urlString, SEPARATOR, urlString);
                    file = new File(urlString);
                    try {
                        url = new URL(path);
                    } catch(MalformedURLException eat) {
                        url = null;
                    }
                }
            }
        } else if("file".equals(url.getProtocol())) {
            String path = url.getPath();
            String urlString = stripUrlFileColonPrefix(path, path);
            urlString = stripSuffix(urlString, SEPARATOR, urlString);
            file = new File(urlString);
        }
        return new URLAlias(url, file);
    }

    private static synchronized String resolveCurrentDirectory() {
        String tmpCurrentDirectory = currentDirectory;
        if(tmpCurrentDirectory != null)
            return tmpCurrentDirectory;

        tmpCurrentDirectory = currentDirectory;
        // retest
        if(tmpCurrentDirectory != null)
            return tmpCurrentDirectory;

        // FIXME: Maybe user.dir should be priority here ?

        File fileCurDir = new File(".");
        if(fileCurDir.isDirectory()) {
            // getParentFile() does not do the trick for us (it will/can be null in this circumstance)
            //  this method ensures it does not end with "/." or "\\." which is unwanted
            tmpCurrentDirectory = fileCurDir.getAbsolutePath();
            String removeDelimAndDot = File.separator + ".";  // "/." on unix, "\\." on windows
            if(tmpCurrentDirectory.endsWith(removeDelimAndDot))
                tmpCurrentDirectory = tmpCurrentDirectory.substring(0, tmpCurrentDirectory.length() - removeDelimAndDot.length());
        } else {
            tmpCurrentDirectory = System.getProperty("user.dir");
        }

        currentDirectory = tmpCurrentDirectory;
        return tmpCurrentDirectory;
    }

    private static String stripSuffix(String s, String suffix, String defaultValue) {
        if(s.endsWith(suffix)) {
            int endIndex = s.length() - suffix.length();
            return s.substring(0, endIndex);
        }
        return defaultValue;
    }

    private static String stripUrlFileColonPrefix(String urlString, String defaultValue) {
        final int len = urlString.length();
        final String PREFIX_FILE = "file:";
        if(urlString.startsWith(PREFIX_FILE)) {  // efficiency?  f**K that for now
            int skipCount = PREFIX_FILE.length();
            while(len > skipCount) {  // remove multiple "/" prefixes
                char c = urlString.charAt(skipCount);
                if(c != '/') {
                    if(skipCount > 0)
                        skipCount--;  // put at least one back
                    break;
                }
                skipCount++;
            }
            if(skipCount > 0)
                urlString = urlString.substring(skipCount);
            return urlString;
        }
        return defaultValue;
    }
    
    static class MyJarFile{
        private URLConnection urlConnection;  // do we need to keep this around ?
        private File fileToJarFile;    // we store this object type to differentiate between URLs and direct File IO.
        private URL urlToJarFile;  // we save this to allow for close/reopen based on just this handle
        private JarFile jarFile;
        private int refCount;

        MyJarFile(File fileToJarFile) throws IOException {
            this.fileToJarFile = fileToJarFile;
            openInternal();
        }

        MyJarFile(URL urlToJarFile) throws IOException {
            this.urlToJarFile = urlToJarFile;
            openInternal();
        }

        private void openInternal() throws IOException {
            if(fileToJarFile != null) {  // Direct File I/O Jar file
                jarFile = new JarFile(fileToJarFile);
            } else {
                urlConnection = urlToJarFile.openConnection();
                if(urlConnection instanceof JarURLConnection) {
                    JarURLConnection jarUrlConnection = (JarURLConnection) urlConnection;
                    jarFile = jarUrlConnection.getJarFile();
                }else {
                    IOException thr = new IOException("not a JarURLConnection: " + urlConnection.getClass().getName());
                    urlConnection = null;  // we only keep handle when we have active Jar open
                    throw thr;
                }
            }
            refCount = 1;
        }

        // This method may never throw an exception
        void get() {
            synchronized(this) {
                refCount++;
            }
        }

        // This method must cause a double increment on the reopen() case
        // Returns the previous refCount, so 0 means we just reopened, non-zero means we did get()
        int getOrReopen() throws IOException {
            int oldRefCount;
            synchronized (this) {
                oldRefCount = refCount;
                if(refCount <= 0)
                    reopen();
                get();
            }
            return oldRefCount;
        }

        // This method may never throw an exception
        void put() {
            JarFile closeJarFile = null;
            synchronized(this) {
                refCount--;
                if(refCount == 0) {
                    closeJarFile = jarFile;
                    jarFile = null;
                }
            }
            if(closeJarFile != null) {
                try {
                    closeJarFile.close();
                } catch(IOException eat) {
                }
                if(urlConnection != null) {
                    urlConnection = null;
                }
            }
        }

        void reopen() throws IOException {
            if(jarFile != null)
                throw new IOException("jarFile already open");
            openInternal();
        }

        String getName() {
            return jarFile.getName();
        }

        Enumeration entries() {
            return jarFile.entries();
        }

        @NativeAccess
        JarEntry getJarEntry(String name) {
            return jarFile.getJarEntry(name);
        }

        @NativeAccess
        InputStream getInputStream(ZipEntry ze) throws IOException {
            return jarFile.getInputStream(ze);
        }
        
        @NativeAccess
        private void entryList(List result, QDir.Filters filters, Collection filterNames, String mentryName){
            if (!mentryName.endsWith("/") && mentryName.length() > 0)
                mentryName = mentryName + "/";

            Enumeration entries = entries();

            HashSet used = new HashSet();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();

                String entryName = entry.getName();

                // Must be inside this directory
                if (entryName.length() <= mentryName.length() || !mentryName.equals(entryName.substring(0, mentryName.length())) || mentryName.equals(entryName))
                    continue;

                // Only one level
                boolean isDir;
                int pos = entryName.indexOf("/", mentryName.length());
                if (pos > 0) {
                    entryName = entryName.substring(0, pos);
                    isDir = true;
                } else {
                    isDir = entry.isDirectory();
                    if (!isDir)
                        isDir = QtJambiResources.checkIsDirectory(this, entry);
                }

                if (!filters.isSet(QDir.Filter.Readable))
                    continue ;

                if (!filters.isSet(QDir.Filter.Dirs) && isDir)
                    continue ;

                if (!filters.isSet(QDir.Filter.Files) && !isDir)
                    continue ;

                if (filterNames.size() > 0) {
                    if ((!isDir || !filters.isSet(QDir.Filter.AllDirs))
                        && (!QDir.match(filterNames, entryName.substring(mentryName.length())))) {
                        continue;
                    }
                }

                if (entryName.endsWith("/") && entryName.length() > 1)
                    entryName = entryName.substring(0, entryName.length() - 1);

                entryName = entryName.substring(mentryName.length());

                if (!used.contains(entryName)) {
                    used.add(entryName);
                    result.add(entryName);
                }
            }
        }
        
        @NativeAccess
        private static QDateTime fileTime(ZipEntry ze) {
        	long tm = ze.getTime();
            if (tm == -1)
                return new QDateTime();  // the current time

            Calendar calendar = new GregorianCalendar();
            calendar.setTime(new Date(tm));

            return new QDateTime(new QDate(calendar.get(Calendar.YEAR),
                                           calendar.get(Calendar.MONTH) + 1,
                                           calendar.get(Calendar.DAY_OF_MONTH)),
                                 new QTime(calendar.get(Calendar.HOUR_OF_DAY),
                                           calendar.get(Calendar.MINUTE),
                                           calendar.get(Calendar.SECOND),
                                           calendar.get(Calendar.MILLISECOND)));
        }
    }
    
    @NativeAccess
    private static Collection classPathDirs() {
    	return cache.classPathDirs();
    }
    
    static class JarCache {
    	
    	private final Function> newSet = s -> new HashSet<>();

        synchronized Collection pathToJarFiles(String entry) {
        	Set result = cache.get(entry);
            return result==null? Collections.emptyList() : Collections.unmodifiableSet(result);
        }

        synchronized Collection classPathDirs() {
            return Collections.unmodifiableSet(classPathDirs);
        }

        private final Map> cache = new HashMap<>();
        private final Set classPathDirs = new HashSet();

    	synchronized void addPath(URL url) {
    		MyJarFile myJarFile = null;
            try {
                // 
                if(url.getProtocol().equals("file")) {
                    File fileDir = new File(url.toURI());
                    if(fileDir.isDirectory()) {
                        classPathDirs.add(fileDir.getAbsolutePath());
                        return;
                    } else if(fileDir.isFile()) {
                        try {
                            myJarFile = new MyJarFile(fileDir);
                        } catch(IOException eat) {
                        }
                    }
                }else if(url.toString().endsWith("/")) {
                	classPathDirs.add(url.toString());
                    return;
                }
                if(myJarFile == null) {
                    try {
                        myJarFile = new MyJarFile(url);
                    } catch(ZipException e) {
                        // This often fails with "java.util.zip.ZipException: error in opening zip file" but never discloses the filename
                        throw new ZipException(e.getMessage() + ": " + url);
                    }
                }

                String jarFileNameX = myJarFile.getName();  // "/foo/my.jar", "C:\foo\my.jar"

                Set seenSet = new HashSet();

                Enumeration entries = myJarFile.entries();
                // FIXME: Special case for empty directory
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();

                    String dirName = null;
                    boolean isToplevelFile = false;

                    String entryName = entry.getName();

                    // Remove potentially initial '/'
                    while (entryName.startsWith("/"))
                        entryName = entryName.substring(1);

                    if (entry.isDirectory()) {
                        if (entryName.endsWith("/"))
                            dirName = entryName.substring(0, entryName.length() - 1);  // canonicalize
                        else
                            dirName = entryName;
                    } else {
                        int slashPos = entryName.lastIndexOf("/");
                        if (slashPos > 0)
                            dirName = entryName.substring(0, slashPos);  // isolate directory part
                        else
                            isToplevelFile = true;  // dirName will be null; there is no directory part
                    }


                    // Add all parent directories "foo/bar/dir1/dir2", "foo/bar/dir1", "foo/bar", "foo"
                    while (dirName != null) {
                        // optimization: if we saw the long nested path (then we already processed its parents as well)
                        if (seenSet.contains(dirName))
                            break;
                        seenSet.add(dirName);
                        cache.computeIfAbsent(dirName, newSet).add(jarFileNameX);
                        int slashPos = dirName.lastIndexOf("/");
                        if (slashPos > 0)
                            dirName = dirName.substring(0, slashPos);
                        else
                            dirName = null;
                    }

                    if (isToplevelFile) {
                        if (seenSet.contains("") == false) {
                            seenSet.add("");
                            cache.computeIfAbsent("", newSet).add(jarFileNameX);
                        }
                    }
                }

                // Add root dir for all jar files (even empty ones)
                if (seenSet.contains("") == false) {
                    seenSet.add("");
                    // Add root dir for all jar files (even empty ones)
                    cache.computeIfAbsent("", newSet).add(jarFileNameX);
                }
            } catch (Exception e) {
                // Expected as directories will fail when doing openConnection.getJarFile()
                // Note that ZipFile throws different types of run time exceptions on different
                // platforms (ZipException on Linux and FileNotFoundException on Windows)
            } finally {
                if (myJarFile != null) {
                    myJarFile.put();
                    myJarFile = null;
                }
            }
    	}

    	synchronized void removePath(URL url) {
    		MyJarFile myJarFile = null;
            try {
                // 
                if(url.getProtocol().equals("file")) {
                    File fileDir = new File(url.toURI());
                    if(fileDir.isDirectory()) {
                    	removeImpl(fileDir.getAbsolutePath());
                        return;
                    } else if(fileDir.isFile()) {
                        try {
                            myJarFile = new MyJarFile(fileDir);
                        } catch(IOException eat) {
                        }
                    }
                }else if(url.toString().endsWith("/")) {
                	removeImpl(url.toString());
                    return;
                }
                if(myJarFile == null) {
                    try {
                        myJarFile = new MyJarFile(url);
                    } catch(ZipException e) {
                    }
                }
                if(myJarFile != null) {
                	removeImpl(myJarFile.getName());  // "/foo/my.jar", "C:\foo\my.jar"
                }else {
                	removeImpl(url.toString());
                }
            } catch (Exception e) {
                // Expected as directories will fail when doing openConnection.getJarFile()
                // Note that ZipFile throws different types of run time exceptions on different
                // platforms (ZipException on Linux and FileNotFoundException on Windows)
            	removeImpl(url.toString());
            } finally {
                if (myJarFile != null) {
                    myJarFile.put();
                    myJarFile = null;
                }
            }
    	}
    	
    	private void removeImpl(String jarFileName) {
    		for(String key : new ArrayList<>(cache.keySet())) {
    			Set entries = cache.get(key);
    			if(entries!=null) {
    				entries.remove(jarFileName);
    				if(entries.isEmpty()) {
    					cache.remove(key);
    				}
    			}else {
    				cache.remove(key);
    			}
    		}
    	}
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy