blob: fcba0defeb9852f74b4f7c3231d15f614ff1198a [file] [log] [blame]
/*
* 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.modules.debugger.jpda.projects;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.netbeans.api.debugger.Properties;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.ClassPath.Entry;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.classpath.GlobalPathRegistryEvent;
import org.netbeans.api.java.classpath.GlobalPathRegistryListener;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.platform.JavaPlatformManager;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.source.BuildArtifactMapper;
import org.netbeans.api.java.source.BuildArtifactMapper.ArtifactsUpdated;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.spi.debugger.ContextProvider;
import org.netbeans.spi.debugger.jpda.SourcePathProvider;
import org.netbeans.spi.java.classpath.PathResourceImplementation;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.JarFileSystem;
import org.openide.filesystems.URLMapper;
import org.openide.util.BaseUtilities;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
/**
*
* @author Jan Jancura
*/
@SourcePathProvider.Registration(path="netbeans-JPDASession")
public class SourcePathProviderImpl extends SourcePathProvider {
private static final boolean verbose =
System.getProperty ("netbeans.debugger.sourcepathproviderimpl") != null;
private static final Logger logger = Logger.getLogger("org.netbeans.modules.debugger.jpda.projects");
private static final Pattern thisDirectoryPattern = Pattern.compile("(/|\\A)\\./");
private static final Pattern parentDirectoryPattern = Pattern.compile("(/|\\A)([^/]+?)/\\.\\./");
/** Contains all known source paths + jdk source path for JPDAStart task + {@link #additionalSourceRoots} */
private ClassPath originalSourcePath;
/** Contains the additional source roots, added at a later time to the original roots. */
private Set<String> additionalSourceRoots;
/** Contains platform (JDK) source roots. */
private Set<String> platformSourceRoots;
/** Contains just the source paths from {@link #originalSourcePath} which are selected for debugging. */
private ClassPath smartSteppingSourcePath;
/** Roots of {@link #originalSourcePath} */
private String[] projectSourceRoots;
/** {@link #originalSourcePath} in the original order. */
private ClassPath unorderedOriginalSourcePath;
/** Permutation that creates {@link #originalSourcePath} from {@link #unorderedOriginalSourcePath}. */
private int[] sourcePathPermutation;
private PropertyChangeSupport pcs;
private PathRegistryListener pathRegistryListener;
private File baseDir;
private final Map<String, String> urlCache = new URLCacheMap();
private final Map<String, String> urlCacheGlobal = new URLCacheMap();
public SourcePathProviderImpl () {
pcs = new PropertyChangeSupport (this);
}
public SourcePathProviderImpl (ContextProvider contextProvider) {
pcs = new PropertyChangeSupport (this);
//this.session = (Session) contextProvider.lookupFirst
// (null, Session.class);
JPDADebugger debugger = (JPDADebugger) contextProvider.lookupFirst(null, JPDADebugger.class);
Map properties = contextProvider.lookupFirst(null, Map.class);
Set<FileObject> srcRootsToListenForArtifactsUpdates = null;
// 2) get default allSourceRoots of source roots used for stepping
if (logger.isLoggable(Level.FINE)) logger.fine("Have properties = "+properties);
if (properties != null) {
baseDir = (File) properties.get("baseDir");
smartSteppingSourcePath = (ClassPath) properties.get ("sourcepath");
ClassPath jdkCP = (ClassPath) properties.get ("jdksources");
if ( (jdkCP == null) && (JavaPlatform.getDefault () != null) ) {
jdkCP = JavaPlatform.getDefault ().getSourceFolders ();
}
platformSourceRoots = getSourceRootsSet(jdkCP);
ClassPath additionalClassPath;
if (baseDir != null) {
additionalClassPath = getAdditionalClassPath(baseDir);
} else {
additionalClassPath = null;
Exceptions.printStackTrace(new NullPointerException("No base directory is defined. Properties = "+properties));
}
if (additionalClassPath != null) {
smartSteppingSourcePath = ClassPathSupport.createProxyClassPath (
new ClassPath[] {
smartSteppingSourcePath,
additionalClassPath
});
}
smartSteppingSourcePath = jdkCP == null ?
smartSteppingSourcePath :
ClassPathSupport.createProxyClassPath (
new ClassPath[] {
jdkCP,
smartSteppingSourcePath,
}
);
unorderedOriginalSourcePath = smartSteppingSourcePath;
Map<String, Integer> orderIndexes = getSourceRootsOrder(baseDir);
String[] unorderedOriginalRoots = getSourceRoots(unorderedOriginalSourcePath);
String[] sortedOriginalRoots = new String[unorderedOriginalRoots.length];
sourcePathPermutation = createPermutation(unorderedOriginalRoots,
orderIndexes,
sortedOriginalRoots);
smartSteppingSourcePath = createClassPath(sortedOriginalRoots);
originalSourcePath = smartSteppingSourcePath;
Set<String> disabledRoots;
if (baseDir != null) {
disabledRoots = getDisabledSourceRoots(baseDir);
} else {
disabledRoots = null;
}
if (disabledRoots != null && !disabledRoots.isEmpty()) {
List<FileObject> enabledSourcePath = new ArrayList<FileObject>(
Arrays.asList(smartSteppingSourcePath.getRoots()));
for (FileObject fo : new HashSet<FileObject>(enabledSourcePath)) {
if (disabledRoots.contains(getRoot(fo))) {
enabledSourcePath.remove(fo);
}
}
smartSteppingSourcePath = createClassPath(
enabledSourcePath.toArray(new FileObject[0]));
}
projectSourceRoots = getSourceRoots(originalSourcePath);
//Set<FileObject> preferredRoots = new HashSet<FileObject>();
//preferredRoots.addAll(Arrays.asList(originalSourcePath.getRoots()));
/*
Set<FileObject> globalRoots = new TreeSet<FileObject>(new FileObjectComparator());
globalRoots.addAll(GlobalPathRegistry.getDefault().getSourceRoots());
globalRoots.removeAll(preferredRoots);
ClassPath globalCP = createClassPath(globalRoots.toArray(new FileObject[0]));
originalSourcePath = ClassPathSupport.createProxyClassPath(
originalSourcePath,
globalCP
);
*/
String listeningCP = (String) properties.get("listeningCP");
if (listeningCP != null) {
boolean isSourcepath = false;
if ("sourcepath".equalsIgnoreCase(listeningCP)) {
listeningCP = ((ClassPath) properties.get ("sourcepath")).toString(ClassPath.PathConversionMode.SKIP);
isSourcepath = true;
}
srcRootsToListenForArtifactsUpdates = new HashSet<FileObject>();
for (String cp : listeningCP.split(File.pathSeparator)) {
logger.log(Level.FINE, "Listening cp = ''{0}''", cp);
File f = new File(cp);
f = FileUtil.normalizeFile(f);
URL entry = FileUtil.urlForArchiveOrDir(f);
if (entry != null) {
if (isSourcepath) {
FileObject src = URLMapper.findFileObject(entry);
if (src != null) {
srcRootsToListenForArtifactsUpdates.add(src);
}
}
for (FileObject src : SourceForBinaryQuery.findSourceRoots(entry).getRoots()) {
srcRootsToListenForArtifactsUpdates.add(src);
}
}
}
if (srcRootsToListenForArtifactsUpdates.isEmpty()) {
srcRootsToListenForArtifactsUpdates = null;
}
}
} else {
pathRegistryListener = new PathRegistryListener();
GlobalPathRegistry.getDefault().addGlobalPathRegistryListener(
WeakListeners.create(GlobalPathRegistryListener.class,
pathRegistryListener,
GlobalPathRegistry.getDefault()));
JavaPlatformManager.getDefault ().addPropertyChangeListener(
WeakListeners.propertyChange(pathRegistryListener,
JavaPlatformManager.getDefault()));
List<FileObject> allSourceRoots = new ArrayList<FileObject>();
Set<FileObject> preferredRoots = new HashSet<FileObject>();
Set<FileObject> addedBinaryRoots = new HashSet<FileObject>();
Project mainProject = OpenProjects.getDefault().getMainProject();
platformSourceRoots = new HashSet<String>();
if (mainProject != null) {
SourceGroup[] sgs = ProjectUtils.getSources(mainProject).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
for (SourceGroup sg : sgs) {
ClassPath ecp = ClassPath.getClassPath(sg.getRootFolder(), ClassPath.EXECUTE);
if (ecp == null) {
ecp = ClassPath.getClassPath(sg.getRootFolder(), ClassPath.SOURCE);
}
if (ecp != null) {
FileObject[] binaryRoots = ecp.getRoots();
for (FileObject fo : binaryRoots) {
if (addedBinaryRoots.contains(fo)) {
continue;
}
addedBinaryRoots.add(fo);
FileObject[] roots = SourceForBinaryQuery.findSourceRoots(fo.toURL()).getRoots();
for (FileObject fr : roots) {
if (!preferredRoots.contains(fr)) {
allSourceRoots.add(fr);
preferredRoots.add(fr);
}
}
}
}
ecp = ClassPath.getClassPath(sg.getRootFolder(), ClassPath.BOOT);
if (ecp != null) {
platformSourceRoots.addAll(getSourceRootsSet(ecp));
}
}
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("SourcePathProviderImpl: preferred source roots = "+preferredRoots+")");
}
Set<FileObject> globalRoots = new TreeSet<FileObject>(new FileObjectComparator());
globalRoots.addAll(GlobalPathRegistry.getDefault().getSourceRoots());
for (FileObject fo : globalRoots) {
if (!preferredRoots.contains(fo)) {
allSourceRoots.add(fo);
}
}
// TODO: Add first main project's BOOT path, if not exist, then default platform and then the rest.
JavaPlatform[] platforms = JavaPlatformManager.getDefault().getInstalledPlatforms();
for (int i = 0; i < platforms.length; i++) {
FileObject[] roots = platforms[i].getSourceFolders().getRoots ();
int j, jj = roots.length;
for (j = 0; j < jj; j++) {
if (!allSourceRoots.contains(roots [j])) {
allSourceRoots.add(roots [j]);
}
}
platformSourceRoots.addAll(getSourceRootsSet(platforms[i].getSourceFolders()));
}
List<FileObject> additional = getAdditionalRemoteClassPath();
if (additional != null) {
allSourceRoots.addAll(additional);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("SourcePathProviderImpl: GlobalPathRegistry roots = "+GlobalPathRegistry.getDefault().getSourceRoots()+")");
logger.fine("Platform roots:");
for (int i = 0; i < platforms.length; i++) {
logger.fine(" "+Arrays.asList(platforms[i].getSourceFolders().getRoots ()).toString());
}
logger.fine("SourcePathProviderImpl: all source roots = "+allSourceRoots+")");
}
Set<String> disabledRoots = getRemoteDisabledSourceRoots();
synchronized (this) {
unorderedOriginalSourcePath = createClassPath (
allSourceRoots.toArray
(new FileObject [allSourceRoots.size()])
);
Map<String, Integer> orderIndexes = getRemoteSourceRootsOrder();
String[] unorderedOriginalRoots = getSourceRoots(unorderedOriginalSourcePath);
String[] sorterOriginalRoots = new String[unorderedOriginalRoots.length];
sourcePathPermutation = createPermutation(unorderedOriginalRoots,
orderIndexes,
sorterOriginalRoots);
originalSourcePath = createClassPath(sorterOriginalRoots);
projectSourceRoots = getSourceRoots(originalSourcePath);
srcRootsToListenForArtifactsUpdates = new HashSet<FileObject>(allSourceRoots);
smartSteppingSourcePath = originalSourcePath;
if (disabledRoots != null && !disabledRoots.isEmpty()) {
List<FileObject> enabledSourcePath = new ArrayList<FileObject>(
Arrays.asList(smartSteppingSourcePath.getRoots()));
for (FileObject fo : new HashSet<FileObject>(enabledSourcePath)) {
if (disabledRoots.contains(getRoot(fo))) {
enabledSourcePath.remove(fo);
}
}
smartSteppingSourcePath = createClassPath(
enabledSourcePath.toArray(new FileObject[0]));
}
}
}
if (verbose)
System.out.println
("SPPI: init originalSourcePath " + originalSourcePath);
if (verbose)
System.out.println (
"SPPI: init smartSteppingSourcePath " + smartSteppingSourcePath
);
if (logger.isLoggable(Level.FINE)) {
logger.fine("new SourcePathProviderImpl(): contextProvider = "+contextProvider+
", properties = "+properties+
", srcRootsToListenForArtifactsUpdates = "+srcRootsToListenForArtifactsUpdates);
}
if (srcRootsToListenForArtifactsUpdates != null) {
final Set<ArtifactsUpdatedImpl> artifactsListeners = new HashSet<ArtifactsUpdatedImpl>();
for (FileObject src : srcRootsToListenForArtifactsUpdates) {
artifactsListeners.add(addArtifactsUpdateListenerFor(debugger, src));
}
debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (JPDADebugger.STATE_DISCONNECTED == ((Integer) evt.getNewValue()).intValue()) {
for (ArtifactsUpdatedImpl al : artifactsListeners) {
BuildArtifactMapper.removeArtifactsUpdatedListener(al.getURL(), al);
}
}
}
});
}
}
private ClassPath getAdditionalClassPath(File baseDir) {
try {
String root = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
List<String> additionalSourceRoots = (List<String>) sourcesProperties.
getProperties("additional_source_roots").
getMap("project", Collections.emptyMap()).
get(root);
if (additionalSourceRoots == null || additionalSourceRoots.isEmpty()) {
return null;
}
List<FileObject> additionalSourcePath = new ArrayList<FileObject>(additionalSourceRoots.size());
for (String ar : additionalSourceRoots) {
FileObject fo = getFileObject(ar);
if (fo != null && fo.canRead()) {
additionalSourcePath.add(fo);
}
}
this.additionalSourceRoots = new LinkedHashSet<String>(additionalSourceRoots);
return createClassPath(
additionalSourcePath.toArray(new FileObject[0]));
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
private List<FileObject> getAdditionalRemoteClassPath() {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
List<String> additionalSourceRoots = (List<String>) sourcesProperties.
getProperties("additional_source_roots").
getCollection("src_roots", Collections.emptyList());
if (additionalSourceRoots == null || additionalSourceRoots.isEmpty()) {
return null;
}
List<FileObject> additionalSourcePath = new ArrayList<FileObject>(additionalSourceRoots.size());
for (String ar : additionalSourceRoots) {
FileObject fo = getFileObject(ar);
if (fo != null && fo.canRead()) {
additionalSourcePath.add(fo);
}
}
this.additionalSourceRoots = new LinkedHashSet<String>(additionalSourceRoots);
return additionalSourcePath;
//return ClassPathSupport.createClassPath(
// additionalSourcePath.toArray(new FileObject[0]));
}
private void storeAdditionalSourceRoots() {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
if (baseDir != null) {
String projectRoot;
try {
projectRoot = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return ;
}
Map map = sourcesProperties.getProperties("additional_source_roots").
getMap("project", new HashMap());
if (additionalSourceRoots != null) {
map.put(projectRoot, new ArrayList<String>(additionalSourceRoots));
} else {
map.remove(projectRoot);
}
sourcesProperties.getProperties("additional_source_roots").
setMap("project", map);
} else {
if (additionalSourceRoots != null) {
sourcesProperties.getProperties("additional_source_roots").
setCollection("src_roots", new ArrayList<String>(additionalSourceRoots));
} else {
sourcesProperties.getProperties("additional_source_roots").
setCollection("src_roots", null);
}
}
}
private Set<String> getDisabledSourceRoots(File baseDir) {
try {
String root = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
return (Set<String>) sourcesProperties.getProperties("source_roots").
getMap("project_disabled", Collections.emptyMap()).
get(root);
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
private Set<String> getRemoteDisabledSourceRoots() {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
return (Set<String>) sourcesProperties.getProperties("source_roots").
getCollection("remote_disabled", Collections.emptySet());
}
private void storeDisabledSourceRoots(Set<String> disabledSourceRoots) {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
if (baseDir != null) {
String projectRoot;
try {
projectRoot = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return ;
}
Map map = sourcesProperties.getProperties("source_roots").
getMap("project_disabled", new HashMap());
map.put(projectRoot, disabledSourceRoots);
sourcesProperties.getProperties("source_roots").
setMap("project_disabled", map);
} else {
sourcesProperties.getProperties("source_roots").
setCollection("remote_disabled", disabledSourceRoots);
}
}
private static Map<String, Integer> getSourceRootsOrder(File baseDir) {
try {
String root = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
return getSourceRootsOrder(root);
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
public static Map<String, Integer> getSourceRootsOrder(String root) {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
return (Map<String, Integer>) sourcesProperties.getProperties("source_roots").
getMap("project_order", Collections.emptyMap()).
get(root);
}
public static Map<String, Integer> getRemoteSourceRootsOrder() {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
return (Map<String, Integer>) sourcesProperties.getProperties("source_roots").
getMap("remote_order", Collections.emptyMap());
}
private static void storeSourceRootsOrder(File baseDir, String[] roots, int[] permutation) {
String projectRoot;
if (baseDir != null) {
try {
projectRoot = BaseUtilities.toURI(baseDir).toURL().toExternalForm();
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return ;
}
} else {
projectRoot = null;
}
storeSourceRootsOrder(projectRoot, roots, permutation);
}
public static void storeSourceRootsOrder(String projectRoot, String[] roots, int[] permutation) {
Map<String, Integer> sourceOrder = new HashMap<String, Integer>();
if (roots.length != permutation.length) {
throw new IllegalArgumentException("Incompatible array length: roots = "+roots.length+", permutation = "+permutation.length);
}
for (int i = 0; i < roots.length; i++) {
sourceOrder.put(roots[permutation[i]], i);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("SourcePathProviderImpl.storeSourceRootsOrder():");
logger.fine(" sourceOrder = "+sourceOrder);
}
storeSourceRootsOrder(projectRoot, sourceOrder);
}
private static void storeSourceRootsOrder(String projectRoot, Map<String, Integer> sourceOrder) {
Properties sourcesProperties = Properties.getDefault ().getProperties ("debugger").getProperties ("sources");
if (projectRoot != null) {
Map map = sourcesProperties.getProperties("source_roots").
getMap("project_order", new HashMap());
map.put(projectRoot, sourceOrder);
sourcesProperties.getProperties("source_roots").
setMap("project_order", map);
} else {
sourcesProperties.getProperties("source_roots").
setMap("remote_order", sourceOrder);
}
}
/**
* Translates a relative path ("java/lang/Thread.java") to url
* ("file:///C:/Sources/java/lang/Thread.java"). Uses GlobalPathRegistry
* if global == true.
*
* @param relativePath a relative path (java/lang/Thread.java)
* @param global true if global path should be used
* @return url or <code>null</code>
*/
@Override
public String getURL (String relativePath, boolean global) { if (verbose) System.out.println ("SPPI: getURL " + relativePath + " global " + global);
relativePath = normalize(relativePath);
if (global) {
synchronized (urlCacheGlobal) {
if (urlCacheGlobal.containsKey(relativePath)) {
if (verbose) System.out.println("Have cached global path for '"+relativePath+"' url = "+urlCacheGlobal.get(relativePath));
return urlCacheGlobal.get(relativePath); // URL or null
}
}
} else {
synchronized (urlCache) {
if (urlCache.containsKey(relativePath)) {
if (verbose) System.out.println("Have cached path for '"+relativePath+"' url = "+urlCache.get(relativePath));
return urlCache.get(relativePath); // URL or null
}
}
}
FileObject fo;
ClassPath ss = null;
ClassPath os = null;
synchronized (this) {
if (originalSourcePath != null) {
ss = smartSteppingSourcePath;
os = originalSourcePath;
}
}
if (ss != null && os != null) {
fo = ss.findResource(relativePath);
if (fo == null && global) {
fo = os.findResource(relativePath);
}
if (fo == null && global) {
fo = GlobalPathRegistry.getDefault().findResource(relativePath);
}
} else {
fo = GlobalPathRegistry.getDefault().findResource(relativePath);
}
if (fo == null && global) {
Set<ClassPath> cpaths = GlobalPathRegistry.getDefault().getPaths(ClassPath.COMPILE);
for (ClassPath cp : cpaths) {
fo = cp.findResource(relativePath);
if (fo != null) {
FileObject[] roots = cp.getRoots();
for (FileObject r : roots) {
if (FileUtil.isParentOf(r, fo)) {
addToSourcePath(r, false);
break;
}
}
break;
}
}
}
if (verbose) System.out.println ("SPPI: fo " + fo);
String url;
if (fo == null) {
url = null;
} else {
url = fo.toURL ().toString ();
}
if (global) {
synchronized (urlCacheGlobal) {
if (verbose) System.out.println("Storing path into global cache for '"+relativePath+"' url = "+url);
urlCacheGlobal.put(relativePath, url);
if (verbose) System.out.println(" Global cache ("+urlCacheGlobal.size()+") = "+urlCacheGlobal);
}
} else {
synchronized (urlCache) {
if (verbose) System.out.println("Storing path into cache for '"+relativePath+"' url = "+url);
urlCache.put(relativePath, url);
if (verbose) System.out.println(" Cache = ("+urlCache.size()+") "+urlCache);
}
}
return url;
}
private void addToSourcePath(FileObject sourceRoot, boolean clearURLCaches) {
URL newURL = sourceRoot.toURL();
synchronized (SourcePathProviderImpl.this) {
if (originalSourcePath == null) {
return ;
}
List<URL> sourcePaths = getURLRoots(originalSourcePath);
sourcePaths.add(newURL);
originalSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
sourcePaths = getURLRoots(smartSteppingSourcePath);
sourcePaths.add(newURL);
smartSteppingSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
}
if (clearURLCaches) {
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
}
pcs.firePropertyChange (PROP_SOURCE_ROOTS, null, null);
}
/**
* Translates a relative path to all possible URLs.
* Uses GlobalPathRegistry if global == true.
*
* @param relativePath a relative path (java/lang/Thread.java)
* @param global true if global path should be used
* @return url
*/
public String[] getAllURLs (String relativePath, boolean global) { if (verbose) System.out.println ("SPPI: getURL " + relativePath + " global " + global);
List<FileObject> fos;
relativePath = normalize(relativePath);
if (originalSourcePath == null) {
fos = new ArrayList<FileObject>();
for (ClassPath cp : GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE)) {
fos.addAll(cp.findAllResources(relativePath));
}
} else {
synchronized (this) {
if (!global) {
fos = smartSteppingSourcePath.findAllResources(relativePath);
if (verbose) System.out.println ("SPPI: fos " + fos);
} else {
fos = originalSourcePath.findAllResources(relativePath);
if (verbose) System.out.println ("SPPI: fos " + fos);
}
}
}
List<String> urls = new ArrayList<String>(fos.size());
for (FileObject fo : fos) {
urls.add(fo.toURL().toString());
}
return urls.toArray(new String[0]);
}
/**
* Returns relative path for given url.
*
* @param url a url of resource file
* @param directorySeparator a directory separator character
* @param includeExtension whether the file extension should be included
* in the result
*
* @return relative path
*/
@Override
public String getRelativePath (
String url,
char directorySeparator,
boolean includeExtension
) {
// 1) url -> FileObject
FileObject fo; if (verbose) System.out.println ("SPPI: getRelativePath " + url);
try {
fo = URLMapper.findFileObject (new URL (url)); if (verbose) System.out.println ("SPPI: fo " + fo);
} catch (MalformedURLException e) {
//e.printStackTrace ();
return null;
}
if (fo == null) {
return null;
}
String relativePath = smartSteppingSourcePath.getResourceName (
fo,
directorySeparator,
includeExtension
);
if (relativePath == null) {
// fallback to FileObject's class path
ClassPath cp = ClassPath.getClassPath (fo, ClassPath.SOURCE);
if (cp == null) {
cp = ClassPath.getClassPath (fo, ClassPath.COMPILE);
}
if (cp == null) {
return null;
}
relativePath = cp.getResourceName (
fo,
directorySeparator,
includeExtension
);
}
return relativePath;
}
/**
* Returns the source root (if any) for given url.
*
* @param url a url of resource file
*
* @return the source root or <code>null</code> when no source root was found.
*/
@Override
public synchronized String getSourceRoot(String url) {
FileObject fo;
try {
fo = URLMapper.findFileObject(new java.net.URL(url));
} catch (java.net.MalformedURLException ex) {
fo = null;
}
FileObject[] roots = null;
if (fo != null && fo.canRead()) {
ClassPath cp = ClassPath.getClassPath(fo, ClassPath.SOURCE);
if (cp != null) {
roots = cp.getRoots();
}
}
if (roots == null) {
roots = originalSourcePath.getRoots();
}
for (FileObject fileObject : roots) {
String rootURL = fileObject.toURL().toString();
if (url.startsWith(rootURL)) {
String root = getRoot(fileObject);
if (root != null) {
return root;
}
}
}
return null; // not found
}
private String[] getSourceRoots(ClassPath classPath) {
FileObject[] sourceRoots = classPath.getRoots();
List<String> roots = new ArrayList<String>(sourceRoots.length);
for (FileObject fo : sourceRoots) {
String root = getRoot(fo);
if (root != null) {
roots.add(root);
}
}
return roots.toArray(new String[0]);
}
private static Set<String> getSourceRootsSet(ClassPath classPath) {
FileObject[] sourceRoots = classPath.getRoots();
Set<String> roots = new HashSet<String>(sourceRoots.length);
for (FileObject fo : sourceRoots) {
String root = getRoot(fo);
if (root != null) {
roots.add(root);
}
}
return roots;
}
public synchronized Set<String> getPlatformSourceRoots() {
return Collections.unmodifiableSet(platformSourceRoots);
}
/**
* Returns allSourceRoots of original source roots.
*
* @return allSourceRoots of original source roots
*/
@Override
public synchronized String[] getOriginalSourceRoots () {
return getSourceRoots(originalSourcePath);
}
/**
* Returns array of source roots.
*
* @return array of source roots
*/
@Override
public synchronized String[] getSourceRoots () {
return getSourceRoots(smartSteppingSourcePath);
}
public synchronized Set<FileObject> getSourceRootsFO() {
return new HashSet<FileObject>(Arrays.asList(smartSteppingSourcePath.getRoots()));
}
/**
* Returns the project's source roots.
*
* @return array of source roots belonging to the project
*/
public String[] getProjectSourceRoots() {
return projectSourceRoots;
}
public synchronized String[] getAdditionalSourceRoots() {
return (additionalSourceRoots == null) ? new String[] {} : additionalSourceRoots.toArray(new String[]{});
}
public void reorderOriginalSourceRoots(int[] permutation) {
synchronized (this) {
String[] srcRoots = getOriginalSourceRoots();
if (permutation == null) {
// Restting the order to the original
for (int i = 0; i < sourcePathPermutation.length; i++) {
sourcePathPermutation[i] = i;
}
originalSourcePath = unorderedOriginalSourcePath;
srcRoots = getSourceRoots(unorderedOriginalSourcePath);
} else {
if (srcRoots.length != permutation.length) {
throw new IllegalArgumentException("Bad length of permutation: "+permutation.length+", have "+srcRoots.length+" source roots.");
}
int n = permutation.length;
String[] unorderedOriginalRoots = getSourceRoots(unorderedOriginalSourcePath);
String[] sortedOriginalRoots = new String[n];
// Adding the permutation
for (int i = 0; i < n; i++) {
permutation[i] = sourcePathPermutation[permutation[i]];
sortedOriginalRoots[i] = unorderedOriginalRoots[permutation[i]];
}
System.arraycopy(permutation, 0, sourcePathPermutation, 0, n);
originalSourcePath = createClassPath(sortedOriginalRoots);
srcRoots = unorderedOriginalRoots;
}
projectSourceRoots = getSourceRoots(originalSourcePath);
Set<String> smartSteppingRoots = new HashSet<String>(Arrays.asList(getSourceRoots(smartSteppingSourcePath)));
String[] orderedSmartSteppingRoots = new String[smartSteppingRoots.size()];
int i = 0;
for (String root : projectSourceRoots) {
if (smartSteppingRoots.contains(root)) {
orderedSmartSteppingRoots[i++] = root;
}
}
smartSteppingSourcePath = createClassPath(orderedSmartSteppingRoots);
storeSourceRootsOrder(baseDir, srcRoots, sourcePathPermutation);
}
// Clear caches so that the new order is taken into account
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
}
/**
* Sets array of source roots.
* {@link #setSourceRoots(java.lang.String[])} can not save disabled additionalSourceRoots, since it gets
* only *enabled* source roots
*
* @param sourceRoots a new array of sourceRoots
* @param additionalRoots complete list of additional source roots (including disabled ones)
*/
public void setSourceRoots (String[] sourceRoots, String[] additionalRoots) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("SourcePathProviderImpl.setSourceRoots("+java.util.Arrays.asList(sourceRoots)+", "+java.util.Arrays.asList(additionalRoots)+")");
}
/*if (sourceRootRename(sourceRoots)) {
return ;
}*/
Set<String> newRoots = new LinkedHashSet<String>(Arrays.asList(sourceRoots));
ClassPath[] oldCP_ptr = new ClassPath[] { null };
ClassPath[] newCP_ptr = new ClassPath[] { null };
synchronized (this) {
Set<String> allAdditionalSourceRoots = new LinkedHashSet<String>(Arrays.asList(additionalRoots));
int permLength = sourcePathPermutation.length;
Set<String> disabledSourceRoots = setSourceRoots(newRoots, oldCP_ptr, newCP_ptr, allAdditionalSourceRoots);
storeAdditionalSourceRoots();
storeDisabledSourceRoots(disabledSourceRoots);
if (permLength != sourcePathPermutation.length) {
storeSourceRootsOrder(baseDir, getSourceRoots(unorderedOriginalSourcePath), sourcePathPermutation);
}
}
// Clear caches so that the new source roots are taken into account
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
if (oldCP_ptr[0] != null) {
pcs.firePropertyChange (PROP_SOURCE_ROOTS, oldCP_ptr[0], newCP_ptr[0]);
}
}
/**
* Sets array of source roots.
*
* @param sourceRoots a new array of sourceRoots
*/
@Override
public void setSourceRoots (String[] sourceRoots) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("SourcePathProviderImpl.setSourceRoots("+java.util.Arrays.asList(sourceRoots)+")");
}
Set<String> newRoots = new LinkedHashSet<String>(Arrays.asList(sourceRoots));
ClassPath[] oldCP_ptr = new ClassPath[] { null };
ClassPath[] newCP_ptr = new ClassPath[] { null };
synchronized (this) {
int permLength = sourcePathPermutation.length;
Set<String> disabledSourceRoots = setSourceRoots(newRoots, oldCP_ptr, newCP_ptr, null);
storeAdditionalSourceRoots();
storeDisabledSourceRoots(disabledSourceRoots);
if (permLength != sourcePathPermutation.length) {
storeSourceRootsOrder(baseDir, getSourceRoots(unorderedOriginalSourcePath), sourcePathPermutation);
}
}
// Clear caches so that the new source roots are taken into account
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
if (oldCP_ptr[0] != null) {
pcs.firePropertyChange (PROP_SOURCE_ROOTS, oldCP_ptr[0], newCP_ptr[0]);
}
}
private synchronized Set<String> setSourceRoots(Set<String> newRoots,
ClassPath[] oldCP_ptr,
ClassPath[] newCP_ptr,
Set<String> allAdditionalSourceRoots) {
List<FileObject> sourcePath = new ArrayList<FileObject>(
Arrays.asList(smartSteppingSourcePath.getRoots()));
List<FileObject> sourcePathOriginal = new ArrayList<FileObject>(
Arrays.asList(originalSourcePath.getRoots()));
List<FileObject> unorderedSourcePathOriginal = new ArrayList<FileObject>(
Arrays.asList(unorderedOriginalSourcePath.getRoots()));
// First check whether there are some new source roots
Set<String> newOriginalRoots = new LinkedHashSet<String>(newRoots);
for (FileObject fo : sourcePathOriginal) {
newOriginalRoots.remove(getRoot(fo));
}
if (!newOriginalRoots.isEmpty()) {
// There are new additional source roots added. We need to update
// unorderedOriginalSourcePath, originalSourcePath, projectSourceRoots,
// smartSteppingSourcePath, sourcePathPermutation and additionalSourceRoots
Set<String> addedOriginalRoots = new LinkedHashSet<String>(newOriginalRoots.size());
for (String root : newOriginalRoots) {
FileObject fo = getFileObject(root);
if (fo != null && fo.canRead()) {
sourcePathOriginal.add(fo);
unorderedSourcePathOriginal.add(fo);
addedOriginalRoots.add(root);
}
}
newOriginalRoots = addedOriginalRoots;
if (!newOriginalRoots.isEmpty()) {
if (additionalSourceRoots == null) {
additionalSourceRoots = new LinkedHashSet<String>();
}
additionalSourceRoots.addAll(newOriginalRoots);
}
}
// Then correct the smart-stepping path
Set<String> newSteppingRoots = new LinkedHashSet<String>(newRoots);
for (FileObject fo : sourcePath) {
newSteppingRoots.remove(getRoot(fo));
}
Set<FileObject> removedSteppingRoots = new HashSet<FileObject>();
Set<FileObject> removedOriginalRoots = new HashSet<FileObject>();
for (FileObject fo : sourcePath) {
String spr = getRoot(fo);
if (!newRoots.contains(spr)) {
removedSteppingRoots.add(fo);
if (additionalSourceRoots != null && additionalSourceRoots.contains(spr) &&
!(allAdditionalSourceRoots != null && allAdditionalSourceRoots.contains(spr))) {
// Remove it only if it's not among all additional source roots
removedOriginalRoots.add(fo);
additionalSourceRoots.remove(spr);
if (additionalSourceRoots.isEmpty()) {
additionalSourceRoots = null;
}
}
}
}
if (!removedOriginalRoots.isEmpty()) {
sourcePathOriginal.removeAll(removedOriginalRoots);
}
if (!newOriginalRoots.isEmpty() || !removedOriginalRoots.isEmpty()) {
for (FileObject fo : removedOriginalRoots) {
int index = unorderedSourcePathOriginal.indexOf(fo);
unorderedSourcePathOriginal.remove(index);
int pi = sourcePathPermutation[index];
for (int i = 0; i < sourcePathPermutation.length; i++) {
if (sourcePathPermutation[i] > pi) {
sourcePathPermutation[i]--;
}
}
for (int i = index; i < (sourcePathPermutation.length - 1); i++) {
sourcePathPermutation[i] = sourcePathPermutation[i+1];
}
}
int n = sourcePathPermutation.length - removedOriginalRoots.size() + newOriginalRoots.size();
int[] newSourcePathPermutation = new int[n];
System.arraycopy(sourcePathPermutation, 0, newSourcePathPermutation, 0, sourcePathPermutation.length - removedOriginalRoots.size());
for (int i = sourcePathPermutation.length - removedOriginalRoots.size(); i < n; i++) {
newSourcePathPermutation[i] = i;
}
sourcePathPermutation = newSourcePathPermutation;
originalSourcePath =
createClassPath(
sourcePathOriginal.toArray(new FileObject[0]));
unorderedOriginalSourcePath =
createClassPath(
unorderedSourcePathOriginal.toArray(new FileObject[0]));
projectSourceRoots = getSourceRoots(originalSourcePath);
}
if (newSteppingRoots.size() > 0 || removedSteppingRoots.size() > 0) {
for (String root : newSteppingRoots) {
FileObject fo = getFileObject(root);
if (fo != null && fo.canRead()) {
sourcePath.add(fo);
}
}
sourcePath.removeAll(removedSteppingRoots);
oldCP_ptr[0] = smartSteppingSourcePath;
smartSteppingSourcePath =
createClassPath(
sourcePath.toArray(new FileObject[0]));
newCP_ptr[0] = smartSteppingSourcePath;
}
Set<FileObject> disabledRoots = new HashSet<FileObject>(sourcePathOriginal);
disabledRoots.removeAll(sourcePath);
Set<String> disabledSourceRoots = new HashSet<String>();
for (FileObject fo : disabledRoots) {
disabledSourceRoots.add(getRoot(fo));
}
return disabledSourceRoots;
}
/*
private synchronized boolean sourceRootRename(String[] sourceRoots) {
FileObject[] currentRoots = smartSteppingSourcePath.getRoots();
if (currentRoots.length != sourceRoots.length) {
return false;
}
int i = 0;
int index = -1;
String renamed = null;
FileObject renamedFO = null;
for (FileObject fo : currentRoots) {
String root = getRoot(fo);
if (root != null) {
if (root.equals(sourceRoots[i])) {
if (index < 0) {
index = i;
renamed = root;
renamedFO = fo;
} else {
return false;
}
}
}
i++;
}
String newRoot = sourceRoots[index];
FileObject newFO = getFileObject(newRoot);
if (newFO == null) {
throw Exceptions.attachLocalizedMessage(new IllegalArgumentException(newRoot), newRoot+" does not exists.");
}
//currentRoots[index] = newRoot;
additionalSourceRoots.remove(renamed);
additionalSourceRoots.add(newRoot);
List<FileObject> sourcePath = new ArrayList<FileObject>(
Arrays.asList(smartSteppingSourcePath.getRoots()));
List<FileObject> sourcePathOriginal = new ArrayList<FileObject>(
Arrays.asList(originalSourcePath.getRoots()));
List<FileObject> unorderedSourcePathOriginal = new ArrayList<FileObject>(
Arrays.asList(unorderedOriginalSourcePath.getRoots()));
sourcePath.set(index, newFO);
index = sourcePathOriginal.indexOf(renamedFO);
sourcePathOriginal.set(index, newFO);
index = unorderedSourcePathOriginal.indexOf(renamedFO);
unorderedSourcePathOriginal.set(index, newFO);
smartSteppingSourcePath =
createClassPath(
sourcePath.toArray(new FileObject[0]));
originalSourcePath =
createClassPath(
sourcePathOriginal.toArray(new FileObject[0]));
unorderedOriginalSourcePath =
createClassPath(
unorderedSourcePathOriginal.toArray(new FileObject[0]));
projectSourceRoots = getSourceRoots(originalSourcePath);
Set<String> disabledRoots;
if (baseDir != null) {
disabledRoots = getDisabledSourceRoots(baseDir);
} else {
disabledRoots = getRemoteDisabledSourceRoots();
}
if (disabledRoots.remove(renamed)) {
disabledRoots.add(newRoot);
storeDisabledSourceRoots(disabledRoots);
}
storeAdditionalSourceRoots();
storeSourceRootsOrder(baseDir, getSourceRoots(unorderedOriginalSourcePath), sourcePathPermutation);
return true;
}
*/
/**
* Adds property change listener.
*
* @param l new listener.
*/
@Override
public void addPropertyChangeListener (PropertyChangeListener l) {
pcs.addPropertyChangeListener (l);
}
/**
* Removes property change listener.
*
* @param l removed listener.
*/
@Override
public void removePropertyChangeListener (
PropertyChangeListener l
) {
pcs.removePropertyChangeListener (l);
}
// helper methods ..........................................................
/**
* Normalizes the given path by removing unnecessary "." and ".." sequences.
* This normalization is needed because the compiler stores source paths like "foo/../inc.jsp" into .class files.
* Such paths are not supported by our ClassPath API.
* TODO: compiler bug? report to JDK?
*
* @param path path to normalize
* @return normalized path without "." and ".." elements
*/
public static String normalize(String path) {
for (Matcher m = thisDirectoryPattern.matcher(path); m.find(); )
{
path = m.replaceAll("$1");
m = thisDirectoryPattern.matcher(path);
}
for (Matcher m = parentDirectoryPattern.matcher(path); m.find(); )
{
if (!m.group(2).equals("..")) {
path = path.substring(0, m.start()) + m.group(1) + path.substring(m.end());
m = parentDirectoryPattern.matcher(path);
}
}
return path;
}
/**
* Returns source root for given ClassPath root as String, or <code>null</code>.
*/
public static String getRoot(FileObject fileObject) {
File f = null;
String path = "";
try {
if (fileObject.getFileSystem () instanceof JarFileSystem) {
f = ((JarFileSystem) fileObject.getFileSystem ()).getJarFile ();
if (!fileObject.isRoot()) {
path = "!/"+fileObject.getPath();
}
} else {
f = FileUtil.toFile (fileObject);
}
} catch (FileStateInvalidException ex) {
}
if (f != null) {
return f.getAbsolutePath () + path;
} else {
return null;
}
}
/**
* Returns FileObject for given String.
*/
private static FileObject getFileObject (String file) {
File f = new File (file);
FileObject fo = FileUtil.toFileObject (FileUtil.normalizeFile(f));
String path = null;
if (fo == null && file.contains("!/")) {
int index = file.indexOf("!/");
f = new File(file.substring(0, index));
fo = FileUtil.toFileObject (f);
path = file.substring(index + "!/".length());
}
if (fo != null && FileUtil.isArchiveFile (fo)) {
fo = FileUtil.getArchiveRoot (fo);
if (path !=null) {
fo = fo.getFileObject(path);
}
}
return fo;
}
public static int[] createPermutation(String[] roots, Map<String, Integer> orderIndexes, String[] sortedRoots) {
int n = roots.length;
if (orderIndexes == null) {
int[] perm = new int[n];
for (int i = 0; i < n; i++) {
sortedRoots[i] = roots[i];
perm[i] = i;
}
return perm;
}
class IndexedRoot {
String root;
Integer index;
int order;
IndexedRoot(String root, Integer index, int order) {
this.root = root;
this.index = index;
this.order = order;
}
}
IndexedRoot[] indexedRoots = new IndexedRoot[n];
List<IndexedRoot> indexed = new ArrayList<IndexedRoot>();
for (int i = 0; i < n; i++) {
Integer index = orderIndexes.get(roots[i]);
indexedRoots[i] = new IndexedRoot(roots[i], index, i);
if (index != null) {
indexed.add(indexedRoots[i]);
}
}
class Cmp implements Comparator<IndexedRoot> {
@Override
public int compare(IndexedRoot ir1, IndexedRoot ir2) {
Integer i1 = ir1.index;
Integer i2 = ir2.index;
return i1 - i2;
}
}
Cmp cmp = new Cmp();
if (indexed.size() == indexedRoots.length) {
// All elements have index != null
Arrays.sort(indexedRoots, cmp);
} else if (!indexed.isEmpty()) {
// Sort only the elements with index != null
Collections.sort(indexed, cmp);
// and merge them in in the correct order:
int indexedi = 0;
for (int i = 0; i < n; i++) {
if (indexedRoots[i].index != null) {
indexedRoots[i] = indexed.get(indexedi++);
}
}
}
int[] perm = new int[n];
for (int i = 0; i < n; i++) {
sortedRoots[i] = indexedRoots[i].root;
perm[i] = indexedRoots[i].order;
}
return perm;
}
private ClassPath reorder(ClassPath sourcePath, final Map<String, Integer> orderIndexes) {
String[] roots = getSourceRoots(sourcePath);
class Cmp implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
int i1 = orderIndexes.get(o1);
int i2 = orderIndexes.get(o2);
return i1 - i2;
}
}
Cmp cmp = new Cmp();
Arrays.sort(roots, cmp);
return createClassPath(roots);
}
private static ClassPath createClassPath(String[] roots) {
int n = roots.length;
FileObject[] froots = new FileObject[n];
for (int i = 0; i < n; i++) {
froots[i] = getFileObject(roots[i]);
}
return createClassPath(froots);
}
private static ClassPath createClassPath(FileObject[] froots) {
List<PathResourceImplementation> pris = new ArrayList<PathResourceImplementation> ();
for (FileObject fo : froots) {
if (fo != null && fo.canRead()) {
try {
URL url = fo.toURL();
pris.add(ClassPathSupport.createResource(url));
} catch (IllegalArgumentException iaex) {
// Can be thrown from ClassPathSupport.createResource()
// Ignore - bad source root
//logger.log(Level.INFO, "Invalid source root = "+fo, iaex);
logger.warning(iaex.getLocalizedMessage());
}
}
}
return ClassPathSupport.createClassPath(pris);
}
private static ClassPath createClassPath(URL[] urls) {
List<PathResourceImplementation> pris = new ArrayList<PathResourceImplementation> ();
for (URL url : urls) {
FileObject fo = URLMapper.findFileObject(url);
if (fo != null && fo.canRead()) {
try {
pris.add(ClassPathSupport.createResource(url));
} catch (IllegalArgumentException iaex) {
// Can be thrown from ClassPathSupport.createResource()
// Ignore - bad source root
//logger.log(Level.INFO, "Invalid source root = "+fo, iaex);
logger.warning(iaex.getLocalizedMessage());
}
}
}
return ClassPathSupport.createClassPath(pris);
}
private ArtifactsUpdatedImpl addArtifactsUpdateListenerFor(JPDADebugger debugger, FileObject src) {
URL url = src.toURL();
ArtifactsUpdatedImpl l = new ArtifactsUpdatedImpl(debugger, url, src);
BuildArtifactMapper.addArtifactsUpdatedListener(url, l);
return l;
}
private static List<URL> getURLRoots(ClassPath cp) {
List<URL> urls = new ArrayList<URL>();
for (Entry entry : cp.entries()) {
URL url = entry.getURL();
urls.add(url);
}
return urls;
}
private static boolean CAN_FIX_CLASSES_AUTOMATICALLY = Boolean.getBoolean("debugger.apply-code-changes.on-save"); // NOI18N
private static class ArtifactsUpdatedImpl implements ArtifactsUpdated {
private Reference<JPDADebugger> debuggerRef;
private final URL url;
private FileObject src;
public ArtifactsUpdatedImpl(JPDADebugger debugger, URL url, FileObject src) {
this.debuggerRef = new WeakReference<JPDADebugger>(debugger);
this.url = url;
this.src = src;
}
public URL getURL() {
return url;
}
@Override
public void artifactsUpdated(Iterable<File> artifacts) {
String error = null;
final JPDADebugger debugger = debuggerRef.get();
if (debugger == null) {
error = NbBundle.getMessage(SourcePathProviderImpl.class, "MSG_NoJPDADebugger");
} else if (!debugger.canFixClasses()) {
error = NbBundle.getMessage(SourcePathProviderImpl.class, "MSG_CanNotFix");
} else if (debugger.getState() == JPDADebugger.STATE_DISCONNECTED) {
error = NbBundle.getMessage(SourcePathProviderImpl.class, "MSG_NoDebug");
}
boolean canFixClasses = Properties.getDefault().getProperties("debugger.options.JPDA").
getBoolean("ApplyCodeChangesOnSave", CAN_FIX_CLASSES_AUTOMATICALLY);
if (logger.isLoggable(Level.FINE)) {
logger.fine("artifactsUpdated("+artifacts+") error = '"+error+"', canFixClasses = "+canFixClasses);
}
if (error == null) {
if (!canFixClasses) {
for (File f : artifacts) {
FileObject fo = FileUtil.toFileObject(f);
if (fo != null) {
String className = fileToClassName(fo);
if (className != null) {
FixClassesSupport.ClassesToReload.getInstance().addClassToReload(
debugger, src, className, fo);
}
}
}
return ;
}
Map<String, FileObject> classes = new HashMap<String, FileObject>();
for (File f : artifacts) {
FileObject fo = FileUtil.toFileObject(f);
if (fo != null) {
String className = fileToClassName(fo);
if (className != null) {
classes.put(className, fo);
}
}
}
FixClassesSupport.reloadClasses(debugger, classes);
} else {
BuildArtifactMapper.removeArtifactsUpdatedListener(url, this);
}
if (error != null && canFixClasses) {
FixClassesSupport.notifyError(debugger, error);
}
}
private static String fileToClassName (FileObject fo) {
// remove ".class" from and use dots for for separator
ClassPath cp = ClassPath.getClassPath (fo, ClassPath.EXECUTE);
if (cp == null) {
logger.log(Level.WARNING, "Did not find EXECUTE class path for {0}", fo);
return null;
}
// FileObject root = cp.findOwnerRoot (fo);
return cp.getResourceName (fo, '.', false);
}
}
private class PathRegistryListener implements GlobalPathRegistryListener, PropertyChangeListener {
private RequestProcessor rp = new RequestProcessor(PathRegistryListener.class.getName(), 1);
private RequestProcessor.Task task;
private List<URL> addedRoots = null;
private List<URL> removedRoots = null;
private final Object rootsLock = new Object();
public PathRegistryListener() {
task = rp.create(new Runnable() {
@Override
public void run() {
rootsChanged();
}
});
}
@Override
public void pathsAdded(final GlobalPathRegistryEvent event) {
List<URL> changedPaths = getChangedPaths(event);
if (changedPaths == null) {
return ;
}
synchronized (rootsLock) {
if (addedRoots == null) {
addedRoots = changedPaths;
} else {
addedRoots.addAll(changedPaths);
}
}
task.schedule(1000); // Work with class path is expensive.
}
@Override
public void pathsRemoved(final GlobalPathRegistryEvent event) {
List<URL> changedPaths = getChangedPaths(event);
if (changedPaths == null) {
return ;
}
synchronized (rootsLock) {
if (removedRoots == null) {
removedRoots = changedPaths;
} else {
removedRoots.addAll(changedPaths);
}
}
task.schedule(1000); // Work with class path is expensive.
}
private List<URL> getChangedPaths(final GlobalPathRegistryEvent event) {
if (!ClassPath.SOURCE.equals(event.getId())) {
return null;
}
List<URL> urls = new ArrayList<URL>();
for (ClassPath cp : event.getChangedPaths()) {
for (Entry entry : cp.entries()) {
URL url = entry.getURL();
urls.add(url);
}
}
return urls;
}
private void rootsChanged() {
List<URL> added;
List<URL> removed;
synchronized (rootsLock) {
added = addedRoots;
removed = removedRoots;
addedRoots = null;
removedRoots = null;
}
boolean changed = false;
if (added != null && added.size() > 0) {
synchronized (SourcePathProviderImpl.this) {
if (originalSourcePath == null) {
return ;
}
List<URL> sourcePaths = getURLRoots(originalSourcePath);
sourcePaths.addAll(added);
originalSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
sourcePaths = getURLRoots(smartSteppingSourcePath);
sourcePaths.addAll(added);
smartSteppingSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
}
changed = true;
}
if (removed != null && removed.size() > 0) {
synchronized (SourcePathProviderImpl.this) {
if (originalSourcePath == null) {
return ;
}
List<URL> sourcePaths = getURLRoots(originalSourcePath);
sourcePaths.removeAll(removed);
originalSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
sourcePaths = getURLRoots(smartSteppingSourcePath);
sourcePaths.removeAll(removed);
smartSteppingSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new URL[0]));
}
changed = true;
}
if (changed) {
// Clear caches so that the new source roots are taken into account
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
pcs.firePropertyChange (PROP_SOURCE_ROOTS, null, null);
}
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
// JDK sources changed
// Work with class path is expensive. Move it off AWT.
if (EventQueue.isDispatchThread()) {
rp.post(new Runnable() {
@Override
public void run() {
propertyChange(evt);
}
});
return ;
}
JavaPlatform[] platforms = JavaPlatformManager.getDefault ().
getInstalledPlatforms ();
boolean changed = false;
synchronized (SourcePathProviderImpl.this) {
if (originalSourcePath == null) {
return ;
}
platformSourceRoots.clear();
List<FileObject> sourcePaths = new ArrayList<FileObject>(
Arrays.asList(originalSourcePath.getRoots()));
for(JavaPlatform jp : platforms) {
FileObject[] roots = jp.getSourceFolders().getRoots ();
for (FileObject fo : roots) {
if (!sourcePaths.contains(fo)) {
sourcePaths.add(fo);
changed = true;
}
}
platformSourceRoots.addAll(getSourceRootsSet(jp.getSourceFolders()));
}
if (changed) {
originalSourcePath =
SourcePathProviderImpl.createClassPath(
sourcePaths.toArray(new FileObject[0]));
}
}
if (changed) {
// Clear caches so that the new source roots are taken into account
synchronized (urlCache) {
urlCache.clear();
}
synchronized (urlCacheGlobal) {
urlCacheGlobal.clear();
}
pcs.firePropertyChange (PROP_SOURCE_ROOTS, null, null);
}
}
}
public static final class FileObjectComparator implements Comparator<FileObject> {
@Override
public int compare(FileObject fo1, FileObject fo2) {
String r1 = getRoot(fo1);
String r2 = getRoot(fo2);
if (r1 == null) {
return -1;
}
if (r2 == null) {
return +1;
}
return r1.compareTo(r2);
}
}
private static final class URLCacheMap extends LinkedHashMap<String, String> {
private static final int URL_CACHE_SIZE = 500;
public URLCacheMap() {
super(URL_CACHE_SIZE, .1f, true);
}
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() >= URL_CACHE_SIZE;
}
}
}