| /* |
| * 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.netbeans.api.java.classpath; |
| |
| import java.beans.PropertyChangeListener; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.security.AllPermission; |
| import java.security.CodeSource; |
| import java.security.PermissionCollection; |
| import java.security.Permissions; |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.openide.filesystems.*; |
| import org.openide.util.WeakListeners; |
| |
| /** Classloader for the filesystem pool. Attaches itself as a listener to |
| * each file a class has been loaded from. If such a file is deleted, modified |
| * or renamed clears the global variable that holds "current" classloader, so |
| * on next request for current one new is created. |
| * |
| * @author Jaroslav Tulach |
| * @author Tomas Zezula |
| */ |
| final class ClassLoaderSupport extends URLClassLoader implements FileChangeListener, PropertyChangeListener { |
| |
| static ClassLoader create(ClassPath cp) { |
| return create (cp, ClassLoader.getSystemClassLoader()); |
| } |
| |
| static ClassLoader create (final ClassPath cp, final ClassLoader parentClassLoader) { |
| return new ClassLoaderSupport(cp, parentClassLoader); |
| } |
| |
| private static final PermissionCollection allPermission; |
| static { |
| allPermission = new Permissions(); |
| allPermission.add(new AllPermission()); |
| allPermission.setReadOnly(); |
| } |
| |
| /** |
| * The ClassPath to load classes from. |
| */ |
| private final ClassPath classPath; |
| private final FileChangeListener listener; |
| private final PropertyChangeListener propListener; |
| private final Object lock = new Object(); |
| private final Map<FileObject,Boolean> emittedFileObjects = new HashMap<>(); |
| private boolean detachedFromCp; |
| |
| /** contains AllPermission */ |
| |
| /** Constructor that attaches itself to the filesystem pool. |
| */ |
| @SuppressWarnings("LeakingThisInConstructor") |
| private ClassLoaderSupport (final ClassPath cp, final ClassLoader parentClassLoader) { |
| super(getRootURLs(cp), parentClassLoader); |
| this.classPath = cp; |
| listener = FileUtil.weakFileChangeListener(this, null); |
| propListener = WeakListeners.propertyChange (this, null); |
| cp.addPropertyChangeListener(propListener); |
| } |
| |
| /** |
| * Tries to locate the .class file on the ClassPath |
| * @param name |
| * @return |
| * @throws ClassNotFoundException |
| */ |
| @Override |
| protected Class findClass (String name) throws ClassNotFoundException { |
| Class c = super.findClass (name); |
| if (c != null) { |
| org.openide.filesystems.FileObject fo; |
| String resName = name.replace('.', '/') + ".class"; // NOI18N |
| fo = classPath.findResource(resName); |
| if (fo != null) { |
| // if the file is from the file system pool, |
| // register to catch its changes |
| addFileChangeListener(fo); |
| } |
| } |
| return c; |
| } |
| |
| /** |
| * Tries to locate the resource on the ClassPath |
| * @param name |
| * @return URL of the resource |
| */ |
| @Override |
| public URL findResource (String name) { |
| URL url = super.findResource (name); |
| if (url != null) { |
| FileObject fo = classPath.findResource(name); |
| if (fo != null) { |
| // if the file is from the file system pool, |
| // register to catch its changes |
| addFileChangeListener(fo); |
| } |
| } |
| return url; |
| } |
| |
| @Override |
| @NonNull |
| protected PermissionCollection getPermissions(final CodeSource codesource) { |
| return allPermission; |
| } |
| |
| /** Tests whether this object is current loader and if so, |
| * clears the loader. |
| * @param fo file object that initiated the action |
| */ |
| private void test (org.openide.filesystems.FileObject fo) { |
| classPath.resetClassLoader(this); |
| removeAllListeners(); //Detached from CP no need to listen |
| } |
| |
| /** Resets the loader, removes it from listneing on all known objects. |
| */ |
| private void reset () { |
| classPath.resetClassLoader(this); |
| removeAllListeners(); ////Detached from CP no need to listen |
| } |
| |
| /** If this object is not current classloader, removes it from |
| * listening on given file object. |
| */ |
| private void testRemove (org.openide.filesystems.FileObject fo) { |
| removeFileChangeListener(fo); |
| } |
| |
| /** Fired when a new folder has been created. This action can only be |
| * listened in folders containing the created file up to the root of |
| * file system. |
| * |
| * @param fe the event describing context where action has taken place |
| */ |
| @Override |
| public void fileFolderCreated (org.openide.filesystems.FileEvent fe) { |
| testRemove (fe.getFile ()); |
| } |
| |
| /** Fired when a new file has been created. This action can only be |
| * listened in folders containing the created file up to the root of |
| * file system. |
| * |
| * @param fe the event describing context where action has taken place |
| */ |
| @Override |
| public void fileDataCreated (org.openide.filesystems.FileEvent fe) { |
| testRemove (fe.getFile ()); |
| } |
| |
| /** Fired when a file has been changed. |
| * @param fe the event describing context where action has taken place |
| */ |
| @Override |
| public void fileChanged (org.openide.filesystems.FileEvent fe) { |
| test (fe.getFile ()); |
| } |
| |
| /** Fired when a file has been deleted. |
| * @param fe the event describing context where action has taken place |
| */ |
| @Override |
| public void fileDeleted (org.openide.filesystems.FileEvent fe) { |
| test (fe.getFile ()); |
| } |
| |
| /** Fired when a file has been renamed. |
| * @param fe the event describing context where action has taken place |
| * and the original name and extension. |
| */ |
| @Override |
| public void fileRenamed (org.openide.filesystems.FileRenameEvent fe) { |
| test (fe.getFile ()); |
| } |
| |
| /** Fired when a file attribute has been changed. |
| * @param fe the event describing context where action has taken place, |
| * the name of attribute and old and new value. |
| */ |
| @Override |
| public void fileAttributeChanged (org.openide.filesystems.FileAttributeEvent fe) { |
| testRemove (fe.getFile ()); |
| } |
| |
| /** |
| * This method gets called when a bound property is changed. |
| * @param evt A PropertyChangeEvent object describing the event source |
| * and the property that has changed. |
| */ |
| @Override |
| public void propertyChange(java.beans.PropertyChangeEvent evt) { |
| if (ClassPath.PROP_ROOTS.equals(evt.getPropertyName())) |
| reset(); |
| } |
| |
| private void addFileChangeListener(final FileObject fo) { |
| boolean add; |
| synchronized(lock) { |
| if (detachedFromCp) { |
| return; |
| } |
| add = emittedFileObjects.put(fo,Boolean.FALSE) == null; |
| } |
| if (add) { |
| fo.addFileChangeListener (listener); |
| synchronized(lock) { |
| if (!detachedFromCp) { |
| assert emittedFileObjects.get(fo) == Boolean.FALSE; |
| emittedFileObjects.put(fo,Boolean.TRUE); |
| } else { |
| emittedFileObjects.remove(fo); |
| add = false; |
| } |
| } |
| if (!add) { |
| fo.removeFileChangeListener(listener); |
| } |
| } |
| } |
| |
| private void removeFileChangeListener(final FileObject fo) { |
| boolean remove; |
| synchronized(lock) { |
| remove = emittedFileObjects.remove(fo) == Boolean.TRUE; |
| } |
| if (remove) { |
| fo.removeFileChangeListener(listener); |
| } |
| } |
| |
| private void removeAllListeners() { |
| Map.Entry[] removeListenerFrom; |
| synchronized(lock){ |
| detachedFromCp = true; //No need to add more listeners |
| if (emittedFileObjects.isEmpty()) { |
| return; |
| } |
| removeListenerFrom = emittedFileObjects.entrySet().toArray(new Map.Entry[emittedFileObjects.size()]); |
| emittedFileObjects.clear(); |
| } |
| for (Map.Entry e : removeListenerFrom) { |
| if (e.getValue() == Boolean.TRUE) { |
| ((FileObject)e.getKey()).removeFileChangeListener(listener); |
| } |
| } |
| } |
| |
| @NonNull |
| private static URL[] getRootURLs(@NonNull final ClassPath cp) { |
| final List<ClassPath.Entry> entries = cp.entries(); |
| final Deque<URL> res = new ArrayDeque<>(entries.size()); |
| for (ClassPath.Entry e : entries) { |
| res.offer(e.getURL()); |
| } |
| return res.toArray(new URL[res.size()]); |
| } |
| } |