Package org.springframework.ide.eclipse.core.java

Source Code of org.springframework.ide.eclipse.core.java.ProjectClassLoaderCache$SourceAndOutputLocationResourceChangeListener$SourceAndOutputLocationResourceVisitor

/*******************************************************************************
* Copyright (c) 2009, 2013 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.core.java;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.xbean.classloader.NonLockingJarFileClassLoader;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IPathVariableManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.springframework.ide.eclipse.core.SpringCore;

/**
* Internal cache of classpath urls and corresponding classloaders.
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.2.5
*/
@SuppressWarnings("deprecation")
public class ProjectClassLoaderCache {

  private static final String FILE_SCHEME = "file";
  private static final int CACHE_SIZE = 12;
  private static final Enumeration<URL> EMPTY_ENUMERATION = Collections.enumeration(new ArrayList<URL>());
  private static final List<ClassLoaderCacheEntry> CLASSLOADER_CACHE = new ArrayList<ClassLoaderCacheEntry>(CACHE_SIZE);

  private static final String DEBUG_OPTION = SpringCore.PLUGIN_ID + "/java/classloader/debug";
  private static final boolean DEBUG_CLASSLOADER = SpringCore.isDebug(DEBUG_OPTION);

  private static ClassLoader cachedParentClassLoader = null;
  private static IPropertyChangeListener propertyChangeListener = null;
  private static IResourceChangeListener resourceChangeListener = null;

  private static ClassLoader addClassLoaderToCache(IProject project, List<URL> urls, ClassLoader parentClassLoader) {
    synchronized (CLASSLOADER_CACHE) {
      int nEntries = CLASSLOADER_CACHE.size();
      if (nEntries >= CACHE_SIZE) {
        // find obsolete entries or remove entry that was least recently accessed
        ClassLoaderCacheEntry oldest = null;
        List<ClassLoaderCacheEntry> obsoleteClassLoaders = new ArrayList<ClassLoaderCacheEntry>(CACHE_SIZE);
        for (int i = 0; i < nEntries; i++) {
          ClassLoaderCacheEntry entry = (ClassLoaderCacheEntry) CLASSLOADER_CACHE.get(i);
          IProject curr = entry.getProject();
          if (!curr.exists() || !curr.isAccessible() || !curr.isOpen()) {
            obsoleteClassLoaders.add(entry);
          }
          else {
            if (oldest == null || entry.getLastAccess() < oldest.getLastAccess()) {
              oldest = entry;
            }
          }
        }
        if (!obsoleteClassLoaders.isEmpty()) {
          for (int i = 0; i < obsoleteClassLoaders.size(); i++) {
            removeClassLoaderEntryFromCache((ClassLoaderCacheEntry) obsoleteClassLoaders.get(i));
          }
        }
        else if (oldest != null) {
          removeClassLoaderEntryFromCache(oldest);
        }
      }
      ClassLoaderCacheEntry newEntry = new ClassLoaderCacheEntry(project, urls, parentClassLoader);
      CLASSLOADER_CACHE.add(newEntry);
      return newEntry.getClassLoader();
    }
  }

  /**
   * Add {@link URL}s to the given set of <code>paths</code>.
   */
  private static void addClassPathUrls(IProject project, List<URL> paths, Set<IProject> resolvedProjects) {

    IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

    // add project to local cache to prevent adding its classpaths multiple times
    if (resolvedProjects.contains(project)) {
      return;
    }
    else {
      resolvedProjects.add(project);
    }

    try {
      if (JdtUtils.isJavaProject(project)) {
        IJavaProject jp = JavaCore.create(project);
        // configured classpath
        IClasspathEntry[] classpath = jp.getResolvedClasspath(true);

        // add class path entries
        for (int i = 0; i < classpath.length; i++) {
          IClasspathEntry path = classpath[i];
          if (path.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
            IPath entryPath = path.getPath();
            File file = entryPath.toFile();
            if (file.exists()) {
              paths.add(file.toURI().toURL());
            }
            else {
              // case for project relative links
              String projectName = entryPath.segment(0);
              IProject pathProject = root.getProject(projectName);
              covertPathToUrl(pathProject, paths, entryPath);
            }
          }
          else if (path.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
            // add source path as well for non java resources
            IPath sourcePath = path.getPath();
            covertPathToUrl(project, paths, sourcePath);
            // add source output locations for different source
            // folders
            IPath sourceOutputPath = path.getOutputLocation();
            covertPathToUrl(project, paths, sourceOutputPath);
          }
        }
        // add all depending java projects
        for (IJavaProject p : JdtUtils.getAllDependingJavaProjects(jp)) {
          addClassPathUrls(p.getProject(), paths, resolvedProjects);
        }

        // get default output directory
        IPath outputPath = jp.getOutputLocation();
        covertPathToUrl(project, paths, outputPath);
      }
      else {
        for (IProject p : project.getReferencedProjects()) {
          addClassPathUrls(p, paths, resolvedProjects);
        }
      }
    }
    catch (Exception e) {
      // ignore
    }
  }

  private static void addUri(List<URL> paths, URI uri) throws MalformedURLException {
    File file = new File(uri);
    // If we keep the following check, non-existing output folders will never be used for the lifetime of
    // a classloader. this causes issues with clean projects with not-yet existing output folders.
    // if (file.exists()) {
    if (file.isDirectory()) {
      paths.add(new URL(uri.toString() + File.separator));
    }
    else {
      paths.add(uri.toURL());
    }
    // }
  }

  private static void covertPathToUrl(IProject project, List<URL> paths, IPath path) throws MalformedURLException {
    if (path != null && project != null && path.removeFirstSegments(1) != null
        && project.findMember(path.removeFirstSegments(1)) != null) {

      URI uri = project.findMember(path.removeFirstSegments(1)).getRawLocationURI();

      if (uri != null) {
        String scheme = uri.getScheme();
        if (FILE_SCHEME.equalsIgnoreCase(scheme)) {
          addUri(paths, uri);
        }
        else if ("sourcecontrol".equals(scheme)) {
          // special case of Rational Team Concert
          IPath sourceControlPath = project.findMember(path.removeFirstSegments(1)).getLocation();
          File sourceControlFile = sourceControlPath.toFile();
          if (sourceControlFile.exists()) {
            addUri(paths, sourceControlFile.toURI());
          }
        }
        else {
          IPathVariableManager variableManager = ResourcesPlugin.getWorkspace().getPathVariableManager();
          addUri(paths, variableManager.resolveURI(uri));
        }
      }
    }
  }

  private static ClassLoader findClassLoaderInCache(IProject project, ClassLoader parentClassLoader) {
    synchronized (CLASSLOADER_CACHE) {
      for (int i = CLASSLOADER_CACHE.size() - 1; i >= 0; i--) {
        ClassLoaderCacheEntry entry = (ClassLoaderCacheEntry) CLASSLOADER_CACHE.get(i);
        IProject curr = entry.getProject();
        if (curr == null || !curr.exists() || !curr.isAccessible() || !curr.isOpen()) {
          removeClassLoaderEntryFromCache(entry);
          if (DEBUG_CLASSLOADER) {
            System.out.println(String.format("> removing classloader for '%s' : total %s",
                entry.getProject(), CLASSLOADER_CACHE.size()));
          }
        }
        else {
          if (entry.matches(project, parentClassLoader)) {
            entry.markAsAccessed();
            return entry.getClassLoader();
          }
        }
      }
    }
    return null;
  }

  /**
   * Iterates all class path entries of the given <code>project</code> and all depending projects.
   * <p>
   * Note: if <code>useParentClassLoader</code> is true, the Spring, AspectJ, Commons Logging and ASM bundles are
   * automatically added to the paths.
   * @param project the {@link IProject}
   * @param useParentClassLoader use the OSGi classloader as parent
   * @return a set of {@link URL}s that can be used to construct a {@link URLClassLoader}
   */
  public static List<URL> getClassPathUrls(IProject project, ClassLoader parentClassLoader) {

    // needs to be linked to preserve ordering
    List<URL> paths = new ArrayList<URL>();
    Set<IProject> resolvedProjects = new HashSet<IProject>();
    addClassPathUrls(project, paths, resolvedProjects);

    return paths;
  }

  /**
   * Registers internal listeners that listen to changes relevant to clear out stale cache entries.
   */
  private static void registerListenersIfRequired() {
    if (propertyChangeListener == null) {
      propertyChangeListener = new EnablementPropertyChangeListener();
      SpringCore.getDefault().getPluginPreferences().addPropertyChangeListener(propertyChangeListener);
    }
    if (resourceChangeListener == null) {
      resourceChangeListener = new SourceAndOutputLocationResourceChangeListener();
      ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener);
    }
  }

  /**
   * Removes the given {@link ClassLoaderCacheEntry} from the internal cache.
   * @param entry the entry to remove
   */
  private static void removeClassLoaderEntryFromCache(ClassLoaderCacheEntry entry) {
    synchronized (CLASSLOADER_CACHE) {
      if (DEBUG_CLASSLOADER) {
        System.out.println(String.format("> removing classloader for '%s' : total %s", entry.getProject()
            .getName(), CLASSLOADER_CACHE.size()));
      }
      entry.dispose();
      CLASSLOADER_CACHE.remove(entry);
    }
  }

  public static boolean shouldFilter(String name) {
    if ("commons-logging.properties".equals(name)) return true;
    if (name != null && name.startsWith("META-INF/services/")) {
      return (name.indexOf('/', 18) == -1);
    }
    return false;
  }

  private static boolean useNonLockingClassLoader() {
    return SpringCore.getDefault().getPluginPreferences().getBoolean(SpringCore.USE_NON_LOCKING_CLASSLOADER);
  }

  /**
   * Returns a {@link ClassLoader} for the given project.
   */
  protected static ClassLoader getClassLoader(IProject project, ClassLoader parentClassLoader) {
    synchronized (ProjectClassLoaderCache.class) {
      // Setup the root class loader to be used when no explicit parent class loader is given
      if (parentClassLoader == null && cachedParentClassLoader == null) {
        List<URL> paths = new ArrayList<URL>();
        Enumeration<String> libs = SpringCore.getDefault().getBundle().getEntryPaths("/lib/");
        while (libs.hasMoreElements()) {
          String lib = libs.nextElement();
          // Don't add the non locking classloader jar
          if (!lib.contains("xbean-nonlocking-classloader")) {
            paths.add(SpringCore.getDefault().getBundle().getEntry(lib));
          }
        }
        paths.addAll(JdtUtils.getBundleClassPath("org.aspectj.runtime"));
        paths.addAll(JdtUtils.getBundleClassPath("org.aspectj.weaver"));
        paths.addAll(JdtUtils.getBundleClassPath("org.objectweb.asm"));
        paths.addAll(JdtUtils.getBundleClassPath("org.aopalliance"));
        cachedParentClassLoader = new URLClassLoader(paths.toArray(new URL[paths.size()]));
      }

      if (project == null) {
        return cachedParentClassLoader;
      }

      registerListenersIfRequired();
    }

    ClassLoader classLoader = findClassLoaderInCache(project, parentClassLoader);
    if (classLoader == null) {
      List<URL> urls = getClassPathUrls(project, parentClassLoader);
      classLoader = addClassLoaderToCache(project, urls, parentClassLoader);
      if (DEBUG_CLASSLOADER) {
        System.out.println(String.format("> creating new classloader for '%s' with parent '%s' : total %s",
            project.getName(), parentClassLoader, CLASSLOADER_CACHE.size()));
      }
    }
    return classLoader;
  }
 
  /**
   * Removes any cached {@link ClassLoaderCacheEntry} for the given {@link IProject}.
   * @param project the project to remove {@link ClassLoaderCacheEntry} for
   */
  protected static void removeClassLoaderEntryFromCache(IProject project) {
    synchronized (CLASSLOADER_CACHE) {
      if (DEBUG_CLASSLOADER) {
        System.out.println(String.format("> removing classloader for '%s' : total %s", project.getName(),
            CLASSLOADER_CACHE.size()));
      }
      for (ClassLoaderCacheEntry entry : new ArrayList<ClassLoaderCacheEntry>(CLASSLOADER_CACHE)) {
        if (project.equals(entry.getProject())) {
          entry.dispose();
          CLASSLOADER_CACHE.remove(entry);
        }
      }
    }
  }
 
  /**
   * Internal cache entry
   */
  static class ClassLoaderCacheEntry implements IElementChangedListener {

    private URL[] directories;

    private ClassLoader jarClassLoader;

    private long lastAccess;

    private ClassLoader parentClassLoader;

    private IProject project;

    private URL[] urls;

    public ClassLoaderCacheEntry(IProject project, List<URL> urls, ClassLoader parentClassLoader) {
      this.project = project;
      this.urls = urls.toArray(new URL[urls.size()]);
      this.parentClassLoader = parentClassLoader;
      markAsAccessed();
      JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE);
    }

    public void dispose() {
      JavaCore.removeElementChangedListener(this);
      this.urls = null;
      this.jarClassLoader = null;
    }

    public void elementChanged(ElementChangedEvent event) {
      IJavaProject javaProject = JdtUtils.getJavaProject(project);
      if (javaProject != null) {
        for (IJavaElementDelta delta : event.getDelta().getAffectedChildren()) {
          if ((delta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0
              || (delta.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0) {
            if (javaProject.equals(delta.getElement()) || javaProject.isOnClasspath(delta.getElement())) {
              removeClassLoaderEntryFromCache(this);
            }
          }
        }
      }
    }

    public ClassLoader getClassLoader() {
      ClassLoader parent = getJarClassLoader();
      if (useNonLockingClassLoader()) {
        return new FilteringNonLockingJarFileClassLoader(String.format("ClassLoader for '%s'", project.getName()),
            directories, parent);
      }
      else {
        return new FilteringURLClassLoader(directories, parent);
      }
    }

    public long getLastAccess() {
      return lastAccess;
    }

    public IProject getProject() {
      return this.project;
    }

    public void markAsAccessed() {
      lastAccess = System.currentTimeMillis();
    }

    public boolean matches(IProject project, ClassLoader parentClassLoader) {
      return this.project.equals(project)
          && ((parentClassLoader == null && this.parentClassLoader == null) || (parentClassLoader != null && parentClassLoader
              .equals(this.parentClassLoader)));
    }

    private synchronized ClassLoader getJarClassLoader() {
      if (jarClassLoader == null) {
        Set<URL> jars = new LinkedHashSet<URL>();
        List<URL> dirs = new ArrayList<URL>();
        for (URL url : urls) {
          if (shouldLoadFromParent(url)) {
            jars.add(url);
          }
          else {
            dirs.add(url);
          }
        }
        if (parentClassLoader != null) {
          // We use the parent class loader of the org.springframework.ide.eclipse.beans.core bundle
          if (useNonLockingClassLoader()) {
            jarClassLoader = new FilteringNonLockingJarFileClassLoader(String.format("ClassLoader for '%s'",
                project.getName()), (URL[]) jars.toArray(new URL[jars.size()]), parentClassLoader);
          }
          else {
            jarClassLoader = new FilteringURLClassLoader((URL[]) jars.toArray(new URL[jars.size()]),
                parentClassLoader);
          }
        }
        else {
          if (useNonLockingClassLoader()) {
            jarClassLoader = new FilteringNonLockingJarFileClassLoader(String.format("ClassLoader for '%s'",
                project.getName()), (URL[]) jars.toArray(new URL[jars.size()]), cachedParentClassLoader);
          }
          else {
            jarClassLoader = new FilteringURLClassLoader((URL[]) jars.toArray(new URL[jars.size()]),
                cachedParentClassLoader);
          }
        }
        directories = dirs.toArray(new URL[dirs.size()]);
      }
      return jarClassLoader;
    }

    private boolean shouldLoadFromParent(URL url) {
      String path = url.getPath();
      if (path.endsWith(".jar") || path.endsWith(".zip")) {
        return true;
      }
      else if (path.contains("/org.eclipse.osgi/bundles/")) {
        return true;
      }
      return false;
    }
  }
 
  /**
   * {@link IPropertyChangeListener} to clear the cache whenever the setting is changed.
   * @since 2.5.0
   */
  static class EnablementPropertyChangeListener implements IPropertyChangeListener {

    /**
     * {@inheritDoc}
     */
    public void propertyChange(PropertyChangeEvent event) {
      if (SpringCore.USE_NON_LOCKING_CLASSLOADER.equals(event.getProperty())) {
        synchronized (CLASSLOADER_CACHE) {
          CLASSLOADER_CACHE.clear();
        }
      }
    }
  }
 
  /**
   * Extension to {@link NonLockingJarFileClassLoader} that filters resource loading attempts by
   * calling {@link ProjectClassLoaderCache#shouldFilter(String)} before delegating to the super
   * implementation.
   * @since 2.7.0
   */
  static class FilteringNonLockingJarFileClassLoader extends NonLockingJarFileClassLoader {

    public FilteringNonLockingJarFileClassLoader(String name, URL[] urls, ClassLoader parent) {
      super(name, urls, parent);
    }
   
    @Override
    public URL findResource(String resourceName) {
      if (shouldFilter(resourceName)) return null;
      return super.findResource(resourceName);
    }
   
    @Override
    public Enumeration<URL> findResources(String resourceName) throws IOException {
      if (shouldFilter(resourceName)) return EMPTY_ENUMERATION;
      return super.findResources(resourceName);
    }
   
    @Override
    public URL getResource(String name) {
      if (shouldFilter(name)) return null;
      return super.getResource(name);
    }
   
    @Override
    public InputStream getResourceAsStream(String name) {
      if (shouldFilter(name)) return null;
      return super.getResourceAsStream(name);
    }
   
    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
      if (shouldFilter(name)) return EMPTY_ENUMERATION;
      return super.getResources(name);
    }
  }

  /**
   * Extension to {@link URLClassLoader} that filters resource loading attempts by
   * calling {@link ProjectClassLoaderCache#shouldFilter(String)} before delegating to the super
   * implementation.
   * @since 2.7.0
   */
  static class FilteringURLClassLoader extends URLClassLoader {
   
    public FilteringURLClassLoader(URL[] urls, ClassLoader parent) {
      super(urls, parent);
    }
   
    @Override
    public URL findResource(String resourceName) {
      if (shouldFilter(resourceName)) return null;
      return super.findResource(resourceName);
    }
   
    @Override
    public Enumeration<URL> findResources(String resourceName) throws IOException {
      if (shouldFilter(resourceName)) return EMPTY_ENUMERATION;
      return super.findResources(resourceName);
    }
   
    @Override
    public URL getResource(String name) {
      if (shouldFilter(name)) return null;
      return super.getResource(name);
    }
   
    @Override
    public InputStream getResourceAsStream(String name) {
      if (shouldFilter(name)) return null;
      return super.getResourceAsStream(name);
    }
   
    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
      if (shouldFilter(name)) return EMPTY_ENUMERATION;
      return super.getResources(name);
    }
  }

  /**
   * {@link IResourceChangeListener} to clear the cache whenever new source or output folders are being added.
   * @since 2.5.2
   */
  static class SourceAndOutputLocationResourceChangeListener implements IResourceChangeListener {

    private static final int VISITOR_FLAGS = IResourceDelta.ADDED | IResourceDelta.CHANGED;

    /**
     * {@inheritDoc}
     */
    public void resourceChanged(IResourceChangeEvent event) {
      if (event.getSource() instanceof IWorkspace) {
        int eventType = event.getType();
        switch (eventType) {
        case IResourceChangeEvent.POST_CHANGE:
          IResourceDelta delta = event.getDelta();
          if (delta != null) {
            try {
              delta.accept(getVisitor(), VISITOR_FLAGS);
            }
            catch (CoreException e) {
              SpringCore.log("Error while traversing resource change delta", e);
            }
          }
          break;
        }
      }
      else if (event.getSource() instanceof IProject) {
        int eventType = event.getType();
        switch (eventType) {
        case IResourceChangeEvent.POST_CHANGE:
          IResourceDelta delta = event.getDelta();
          if (delta != null) {
            try {
              delta.accept(getVisitor(), VISITOR_FLAGS);
            }
            catch (CoreException e) {
              SpringCore.log("Error while traversing resource change delta", e);
            }
          }
          break;
        }
      }

    }

    protected IResourceDeltaVisitor getVisitor() {
      return new SourceAndOutputLocationResourceVisitor();
    }

    /**
     * Internal resource delta visitor.
     */
    protected class SourceAndOutputLocationResourceVisitor implements IResourceDeltaVisitor {

      public final boolean visit(IResourceDelta delta) throws CoreException {
        IResource resource = delta.getResource();
        switch (delta.getKind()) {
        case IResourceDelta.ADDED:
          return resourceAdded(resource);
        }
        return true;
      }

      protected boolean resourceAdded(IResource resource) {
        if (resource instanceof IFolder && JdtUtils.isJavaProject(resource)) {
          try {
            IJavaProject javaProject = JdtUtils.getJavaProject(resource);
            // Safe guard once again
            if (javaProject == null) {
              return false;
            }

            // Check the default output location
            if (javaProject.getOutputLocation() != null
                && javaProject.getOutputLocation().equals(resource.getFullPath())) {
              removeClassLoaderEntryFromCache(resource.getProject());
              return false;
            }

            // Check any source and output folder location
            for (IClasspathEntry entry : javaProject.getRawClasspath()) {
              if (resource.getFullPath() != null && resource.getFullPath().equals(entry.getPath())) {
                removeClassLoaderEntryFromCache(resource.getProject());
                return false;
              }
              else if (resource.getFullPath() != null
                  && resource.getFullPath().equals(entry.getOutputLocation())) {
                removeClassLoaderEntryFromCache(resource.getProject());
                return false;
              }
            }
          }
          catch (JavaModelException e) {
            SpringCore.log("Error traversing resource change delta", e);
          }
        }
        return true;
      }
    }
  }

}
TOP

Related Classes of org.springframework.ide.eclipse.core.java.ProjectClassLoaderCache$SourceAndOutputLocationResourceChangeListener$SourceAndOutputLocationResourceVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.