| /******************************************************************************* |
| * Copyright (c) 2008-2010 Sonatype, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Sonatype, Inc. - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.m2e.jdt.internal; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| import org.osgi.framework.BundleContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| 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.ResourcesPlugin; |
| import org.eclipse.core.resources.WorkspaceJob; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jdt.core.IClasspathAttribute; |
| import org.eclipse.jdt.core.IClasspathContainer; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.IJavaModel; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IModuleDescription; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.resolver.filter.ArtifactFilter; |
| import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; |
| import org.apache.maven.project.MavenProject; |
| |
| import org.eclipse.m2e.core.MavenPlugin; |
| import org.eclipse.m2e.core.embedder.ArtifactKey; |
| import org.eclipse.m2e.core.embedder.IMaven; |
| import org.eclipse.m2e.core.embedder.IMavenConfiguration; |
| import org.eclipse.m2e.core.internal.IMavenConstants; |
| import org.eclipse.m2e.core.internal.index.IndexManager; |
| import org.eclipse.m2e.core.internal.index.IndexedArtifactFile; |
| import org.eclipse.m2e.core.internal.lifecyclemapping.LifecycleMappingFactory; |
| import org.eclipse.m2e.core.project.IMavenProjectChangedListener; |
| import org.eclipse.m2e.core.project.IMavenProjectFacade; |
| import org.eclipse.m2e.core.project.IMavenProjectRegistry; |
| import org.eclipse.m2e.core.project.MavenProjectChangedEvent; |
| import org.eclipse.m2e.core.project.configurator.ILifecycleMapping; |
| import org.eclipse.m2e.jdt.IClasspathEntryDescriptor; |
| import org.eclipse.m2e.jdt.IClasspathManager; |
| import org.eclipse.m2e.jdt.IClasspathManagerDelegate; |
| import org.eclipse.m2e.jdt.MavenJdtPlugin; |
| |
| |
| /** |
| * This class is responsible for mapping Maven classpath to JDT and back. |
| */ |
| @SuppressWarnings("restriction") |
| public class BuildPathManager implements IMavenProjectChangedListener, IResourceChangeListener, IClasspathManager { |
| private static final Logger log = LoggerFactory.getLogger(BuildPathManager.class); |
| |
| public static final int SOURCE_DOWNLOAD_PRIORITY = Job.DECORATE;//Low priority |
| |
| // local repository variable |
| public static final String M2_REPO = "M2_REPO"; //$NON-NLS-1$ |
| |
| private static final String PROPERTY_SRC_ROOT = ".srcRoot"; //$NON-NLS-1$ |
| |
| private static final String PROPERTY_SRC_ENCODING = ".srcEncoding"; //$NON-NLS-1$ |
| |
| private static final String PROPERTY_SRC_PATH = ".srcPath"; //$NON-NLS-1$ |
| |
| private static final String PROPERTY_JAVADOC_URL = ".javadoc"; //$NON-NLS-1$ |
| |
| public static final String CLASSIFIER_SOURCES = "sources"; //$NON-NLS-1$ |
| |
| public static final String CLASSIFIER_JAVADOC = "javadoc"; //$NON-NLS-1$ |
| |
| public static final String CLASSIFIER_TESTS = "tests"; //$NON-NLS-1$ |
| |
| public static final String CLASSIFIER_TESTSOURCES = "test-sources"; //$NON-NLS-1$ |
| |
| public static final ArtifactFilter SCOPE_FILTER_RUNTIME = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME); |
| |
| public static final ArtifactFilter SCOPE_FILTER_TEST = new ScopeArtifactFilter(Artifact.SCOPE_TEST); |
| |
| final IMavenProjectRegistry projectManager; |
| |
| final IMavenConfiguration mavenConfiguration; |
| |
| final IndexManager indexManager; |
| |
| final BundleContext bundleContext; |
| |
| final IMaven maven; |
| |
| final File stateLocationDir; |
| |
| final Map<String, Set<String>> requiredModulesMap = new ConcurrentHashMap<String, Set<String>>(); |
| |
| private final DownloadSourcesJob downloadSourcesJob; |
| |
| private final DefaultClasspathManagerDelegate defaultDelegate; |
| |
| public BuildPathManager(IMavenProjectRegistry projectManager, IndexManager indexManager, BundleContext bundleContext, |
| File stateLocationDir) { |
| this.projectManager = projectManager; |
| this.indexManager = indexManager; |
| this.mavenConfiguration = MavenPlugin.getMavenConfiguration(); |
| this.bundleContext = bundleContext; |
| this.stateLocationDir = stateLocationDir; |
| this.maven = MavenPlugin.getMaven(); |
| this.downloadSourcesJob = new DownloadSourcesJob(this); |
| downloadSourcesJob.setPriority(SOURCE_DOWNLOAD_PRIORITY); |
| this.defaultDelegate = new DefaultClasspathManagerDelegate(); |
| } |
| |
| public static IClasspathEntry getMavenContainerEntry(IJavaProject javaProject) { |
| if(javaProject != null) { |
| try { |
| for(IClasspathEntry entry : javaProject.getRawClasspath()) { |
| if(MavenClasspathHelpers.isMaven2ClasspathContainer(entry.getPath())) { |
| return entry; |
| } |
| } |
| } catch(JavaModelException ex) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public static IClasspathContainer getMaven2ClasspathContainer(IJavaProject project) throws JavaModelException { |
| IClasspathEntry[] entries = project.getRawClasspath(); |
| for(int i = 0; i < entries.length; i++ ) { |
| IClasspathEntry entry = entries[i]; |
| if(entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER |
| && MavenClasspathHelpers.isMaven2ClasspathContainer(entry.getPath())) { |
| return JavaCore.getClasspathContainer(entry.getPath(), project); |
| } |
| } |
| return null; |
| } |
| |
| public void mavenProjectChanged(MavenProjectChangedEvent[] events, IProgressMonitor monitor) { |
| Set<IProject> projects = new HashSet<IProject>(); |
| monitor.setTaskName(Messages.BuildPathManager_monitor_setting_cp); |
| for(int i = 0; i < events.length; i++ ) { |
| MavenProjectChangedEvent event = events[i]; |
| IFile pom = event.getSource(); |
| IProject project = pom.getProject(); |
| if(project.isAccessible() && projects.add(project)) { |
| updateClasspath(project, monitor); |
| } |
| } |
| } |
| |
| public void updateClasspath(IProject project, IProgressMonitor monitor) { |
| IJavaProject javaProject = JavaCore.create(project); |
| if(javaProject != null) { |
| try { |
| IClasspathEntry containerEntry = getMavenContainerEntry(javaProject); |
| IPath path = containerEntry != null ? containerEntry.getPath() : new Path(CONTAINER_ID); |
| IClasspathEntry[] classpath = getClasspath(project, monitor); |
| IClasspathContainer container = new MavenClasspathContainer(path, classpath); |
| JavaCore.setClasspathContainer(container.getPath(), new IJavaProject[] {javaProject}, |
| new IClasspathContainer[] {container}, monitor); |
| saveContainerState(project, container); |
| } catch(CoreException ex) { |
| log.error(ex.getMessage(), ex); |
| } |
| } |
| } |
| |
| private void saveContainerState(IProject project, IClasspathContainer container) { |
| File containerStateFile = getContainerStateFile(project); |
| FileOutputStream is = null; |
| try { |
| is = new FileOutputStream(containerStateFile); |
| new MavenClasspathContainerSaveHelper().writeContainer(container, is); |
| } catch(IOException ex) { |
| log.error("Can't save classpath container state for " + project.getName(), ex); //$NON-NLS-1$ |
| } finally { |
| if(is != null) { |
| try { |
| is.close(); |
| } catch(IOException ex) { |
| log.error("Can't close output stream for " + containerStateFile.getAbsolutePath(), ex); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| public IClasspathContainer getSavedContainer(IProject project) throws CoreException { |
| File containerStateFile = getContainerStateFile(project); |
| if(!containerStateFile.exists()) { |
| return null; |
| } |
| |
| FileInputStream is = null; |
| try { |
| is = new FileInputStream(containerStateFile); |
| return new MavenClasspathContainerSaveHelper().readContainer(is); |
| } catch(IOException ex) { |
| throw new CoreException(new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, // |
| "Can't read classpath container state for " + project.getName(), ex)); |
| } catch(ClassNotFoundException ex) { |
| throw new CoreException(new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, // |
| "Can't read classpath container state for " + project.getName(), ex)); |
| } finally { |
| if(is != null) { |
| try { |
| is.close(); |
| } catch(IOException ex) { |
| log.error("Can't close output stream for " + containerStateFile.getAbsolutePath(), ex); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| private IClasspathEntry[] getClasspath(IMavenProjectFacade projectFacade, final int kind, |
| final Properties sourceAttachment, boolean uniquePaths, final IProgressMonitor monitor) throws CoreException { |
| |
| final ClasspathDescriptor classpath = new ClasspathDescriptor(uniquePaths); |
| |
| getDelegate(projectFacade, monitor).populateClasspath(classpath, projectFacade, kind, monitor); |
| |
| configureAttachedSourcesAndJavadoc(projectFacade, sourceAttachment, classpath, monitor); |
| |
| IClasspathEntry[] entries = classpath.getEntries(); |
| |
| if(uniquePaths) { |
| Map<IPath, IClasspathEntry> paths = new LinkedHashMap<IPath, IClasspathEntry>(); |
| for(IClasspathEntry entry : entries) { |
| if(!paths.containsKey(entry.getPath())) { |
| paths.put(entry.getPath(), entry); |
| } |
| } |
| return paths.values().toArray(new IClasspathEntry[paths.size()]); |
| } |
| |
| return entries; |
| } |
| |
| private IClasspathManagerDelegate getDelegate(IMavenProjectFacade projectFacade, IProgressMonitor monitor) |
| throws CoreException { |
| ILifecycleMapping lifecycleMapping = LifecycleMappingFactory.getLifecycleMapping(projectFacade); |
| if(lifecycleMapping instanceof IClasspathManagerDelegate) { |
| return (IClasspathManagerDelegate) lifecycleMapping; |
| } |
| return defaultDelegate; |
| } |
| |
| private void configureAttachedSourcesAndJavadoc(IMavenProjectFacade facade, Properties sourceAttachment, |
| ClasspathDescriptor classpath, IProgressMonitor monitor) throws CoreException { |
| for(IClasspathEntryDescriptor desc : classpath.getEntryDescriptors()) { |
| if(IClasspathEntry.CPE_LIBRARY == desc.getEntryKind() && desc.getSourceAttachmentPath() == null) { |
| ArtifactKey a = desc.getArtifactKey(); |
| String key = desc.getPath().toPortableString(); |
| |
| IPath srcPath = desc.getSourceAttachmentPath(); |
| IPath srcRoot = desc.getSourceAttachmentRootPath(); |
| if(srcPath == null && sourceAttachment != null && sourceAttachment.containsKey(key + PROPERTY_SRC_PATH)) { |
| srcPath = Path.fromPortableString((String) sourceAttachment.get(key + PROPERTY_SRC_PATH)); |
| if(sourceAttachment.containsKey(key + PROPERTY_SRC_ROOT)) { |
| srcRoot = Path.fromPortableString((String) sourceAttachment.get(key + PROPERTY_SRC_ROOT)); |
| } |
| } |
| if(srcPath == null && a != null) { |
| srcPath = getSourcePath(a); |
| } |
| if(sourceAttachment != null) { |
| String srcEncoding = sourceAttachment.getProperty(key + PROPERTY_SRC_ENCODING); |
| if(srcEncoding != null) { |
| desc.getClasspathAttributes().put(IClasspathAttribute.SOURCE_ATTACHMENT_ENCODING, srcEncoding); |
| } |
| } |
| |
| // configure javadocs if available |
| String javaDocUrl = desc.getJavadocUrl(); |
| if(javaDocUrl == null && sourceAttachment != null && sourceAttachment.containsKey(key + PROPERTY_JAVADOC_URL)) { |
| javaDocUrl = (String) sourceAttachment.get(key + PROPERTY_JAVADOC_URL); |
| } |
| if(javaDocUrl == null && a != null) { |
| javaDocUrl = getJavaDocUrl(a); |
| } |
| |
| desc.setSourceAttachment(srcPath, srcRoot); |
| desc.setJavadocUrl(javaDocUrl); |
| |
| ArtifactKey aKey = desc.getArtifactKey(); |
| if(aKey != null) { // maybe we should try to find artifactKey little harder here? |
| boolean downloadSources = desc.getSourceAttachmentPath() == null && srcPath == null |
| && mavenConfiguration.isDownloadSources(); |
| boolean downloadJavaDoc = desc.getJavadocUrl() == null && javaDocUrl == null |
| && mavenConfiguration.isDownloadJavaDoc(); |
| |
| scheduleDownload(facade.getProject(), facade.getMavenProject(monitor), aKey, downloadSources, |
| downloadJavaDoc); |
| } |
| } |
| } |
| } |
| |
| private boolean isUnavailable(ArtifactKey a, List<ArtifactRepository> repositories) throws CoreException { |
| return maven.isUnavailable(a.getGroupId(), a.getArtifactId(), a.getVersion(), "jar" /*type*/, a.getClassifier(), //$NON-NLS-1$ |
| repositories); |
| } |
| |
| // public void downloadSources(IProject project, ArtifactKey artifact, boolean downloadSources, boolean downloadJavaDoc) throws CoreException { |
| // List<ArtifactRepository> repositories = null; |
| // IMavenProjectFacade facade = projectManager.getProject(project); |
| // if (facade != null) { |
| // MavenProject mavenProject = facade.getMavenProject(); |
| // if (mavenProject != null) { |
| // repositories = mavenProject.getRemoteArtifactRepositories(); |
| // } |
| // } |
| // doDownloadSources(project, artifact, downloadSources, downloadJavaDoc, repositories); |
| // } |
| |
| public IClasspathEntry[] getClasspath(IProject project, int scope, IProgressMonitor monitor) throws CoreException { |
| return getClasspath(project, scope, true, monitor); |
| } |
| |
| public IClasspathEntry[] getClasspath(IProject project, int scope, boolean uniquePaths, IProgressMonitor monitor) |
| throws CoreException { |
| IMavenProjectFacade facade = projectManager.create(project, monitor); |
| if(facade == null) { |
| return new IClasspathEntry[0]; |
| } |
| try { |
| Properties props = new Properties(); |
| File file = getSourceAttachmentPropertiesFile(project); |
| if(file.canRead()) { |
| InputStream is = new BufferedInputStream(new FileInputStream(file)); |
| try { |
| props.load(is); |
| } finally { |
| is.close(); |
| } |
| } |
| return getClasspath(facade, scope, props, uniquePaths, monitor); |
| } catch(IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, // |
| "Can't save classpath container changes", e)); |
| } |
| } |
| |
| public IClasspathEntry[] getClasspath(IProject project, IProgressMonitor monitor) throws CoreException { |
| return getClasspath(project, CLASSPATH_DEFAULT, monitor); |
| } |
| |
| /** |
| * Downloads artifact sources using background job. If path is null, downloads sources for all classpath entries of |
| * the project, otherwise downloads sources for the first classpath entry with the given path. |
| */ |
| // public void downloadSources(IProject project, IPath path) throws CoreException { |
| // downloadSourcesJob.scheduleDownload(project, path, findArtifacts(project, path), true, false); |
| // } |
| |
| /** |
| * Downloads artifact JavaDocs using background job. If path is null, downloads sources for all classpath entries of |
| * the project, otherwise downloads sources for the first classpath entry with the given path. |
| */ |
| // public void downloadJavaDoc(IProject project, IPath path) throws CoreException { |
| // downloadSourcesJob.scheduleDownload(project, path, findArtifacts(project, path), false, true); |
| // } |
| |
| private Set<ArtifactKey> findArtifacts(IProject project, IPath path) throws CoreException { |
| ArrayList<IClasspathEntry> entries = findClasspathEntries(project, path); |
| |
| Set<ArtifactKey> artifacts = new LinkedHashSet<ArtifactKey>(); |
| |
| for(IClasspathEntry entry : entries) { |
| ArtifactKey artifact = findArtifactByArtifactKey(entry); |
| |
| if(artifact == null) { |
| artifact = findArtifactInIndex(project, entry); |
| if(artifact == null) { |
| // console.logError("Can't find artifact for " + entry.getPath()); |
| } else { |
| // console.logMessage("Found indexed artifact " + artifact + " for " + entry.getPath()); |
| artifacts.add(artifact); |
| } |
| } else { |
| // console.logMessage("Found artifact " + artifact + " for " + entry.getPath()); |
| artifacts.add(artifact); |
| } |
| } |
| |
| return artifacts; |
| } |
| |
| public ArtifactKey findArtifact(IProject project, IPath path) throws CoreException { |
| if(path != null) { |
| Set<ArtifactKey> artifacts = findArtifacts(project, path); |
| // it is not possible to have more than one classpath entry with the same path |
| if(artifacts.size() > 0) { |
| return artifacts.iterator().next(); |
| } |
| } |
| return null; |
| } |
| |
| private ArtifactKey findArtifactByArtifactKey(IClasspathEntry entry) { |
| IClasspathAttribute[] attributes = entry.getExtraAttributes(); |
| String groupId = null; |
| String artifactId = null; |
| String version = null; |
| String classifier = null; |
| for(int j = 0; j < attributes.length; j++ ) { |
| if(GROUP_ID_ATTRIBUTE.equals(attributes[j].getName())) { |
| groupId = attributes[j].getValue(); |
| } else if(ARTIFACT_ID_ATTRIBUTE.equals(attributes[j].getName())) { |
| artifactId = attributes[j].getValue(); |
| } else if(VERSION_ATTRIBUTE.equals(attributes[j].getName())) { |
| version = attributes[j].getValue(); |
| } else if(CLASSIFIER_ATTRIBUTE.equals(attributes[j].getName())) { |
| classifier = attributes[j].getValue(); |
| } |
| } |
| |
| if(groupId != null && artifactId != null && version != null) { |
| return new ArtifactKey(groupId, artifactId, version, classifier); |
| } |
| return null; |
| } |
| |
| private ArtifactKey findArtifactInIndex(IProject project, IClasspathEntry entry) throws CoreException { |
| IFile jarFile = project.getWorkspace().getRoot().getFile(entry.getPath()); |
| File file = jarFile == null || jarFile.getLocation() == null ? entry.getPath().toFile() |
| : jarFile.getLocation().toFile(); |
| |
| IndexedArtifactFile iaf = indexManager.getIndex(project).identify(file); |
| if(iaf != null) { |
| return new ArtifactKey(iaf.group, iaf.artifact, iaf.version, iaf.classifier); |
| } |
| |
| return null; |
| } |
| |
| // TODO should it be just one entry? |
| private ArrayList<IClasspathEntry> findClasspathEntries(IProject project, IPath path) throws JavaModelException { |
| ArrayList<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(); |
| |
| IJavaProject javaProject = JavaCore.create(project); |
| addEntries(entries, javaProject.getRawClasspath(), path); |
| |
| IClasspathContainer container = getMaven2ClasspathContainer(javaProject); |
| if(container != null) { |
| addEntries(entries, container.getClasspathEntries(), path); |
| } |
| return entries; |
| } |
| |
| private void addEntries(Collection<IClasspathEntry> collection, IClasspathEntry[] entries, IPath path) { |
| for(IClasspathEntry entry : entries) { |
| if(entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY && (path == null || path.equals(entry.getPath()))) { |
| collection.add(entry); |
| } |
| } |
| } |
| |
| /** |
| * Extracts and persists custom source/javadoc attachment info |
| */ |
| public void persistAttachedSourcesAndJavadoc(IJavaProject project, IClasspathContainer containerSuggestion, |
| IProgressMonitor monitor) throws CoreException { |
| IFile pom = project.getProject().getFile(IMavenConstants.POM_FILE_NAME); |
| IMavenProjectFacade facade = projectManager.create(pom, false, null); |
| if(facade == null) { |
| return; |
| } |
| |
| // collect all source/javadoc attachement |
| Properties props = new Properties(); |
| IClasspathEntry[] entries = containerSuggestion.getClasspathEntries(); |
| for(int i = 0; i < entries.length; i++ ) { |
| IClasspathEntry entry = entries[i]; |
| if(IClasspathEntry.CPE_LIBRARY == entry.getEntryKind()) { |
| String path = entry.getPath().toPortableString(); |
| if(entry.getSourceAttachmentPath() != null) { |
| props.put(path + PROPERTY_SRC_PATH, entry.getSourceAttachmentPath().toPortableString()); |
| } |
| if(entry.getSourceAttachmentRootPath() != null) { |
| props.put(path + PROPERTY_SRC_ROOT, entry.getSourceAttachmentRootPath().toPortableString()); |
| } |
| String sourceAttachmentEncoding = getSourceAttachmentEncoding(entry); |
| if(sourceAttachmentEncoding != null) { |
| props.put(path + PROPERTY_SRC_ENCODING, sourceAttachmentEncoding); |
| } |
| String javadocUrl = getJavadocLocation(entry); |
| if(javadocUrl != null) { |
| props.put(path + PROPERTY_JAVADOC_URL, javadocUrl); |
| } |
| } |
| } |
| |
| // eliminate all "standard" source/javadoc attachement we get from local repo |
| entries = getClasspath(facade, CLASSPATH_DEFAULT, null, true, monitor); |
| for(int i = 0; i < entries.length; i++ ) { |
| IClasspathEntry entry = entries[i]; |
| if(IClasspathEntry.CPE_LIBRARY == entry.getEntryKind()) { |
| String path = entry.getPath().toPortableString(); |
| String value = (String) props.get(path + PROPERTY_SRC_PATH); |
| if(value != null && entry.getSourceAttachmentPath() != null |
| && value.equals(entry.getSourceAttachmentPath().toPortableString())) { |
| props.remove(path + PROPERTY_SRC_PATH); |
| } |
| value = (String) props.get(path + PROPERTY_SRC_ROOT); |
| if(value != null && entry.getSourceAttachmentRootPath() != null |
| && value.equals(entry.getSourceAttachmentRootPath().toPortableString())) { |
| props.remove(path + PROPERTY_SRC_ROOT); |
| } |
| } |
| } |
| |
| // persist custom source/javadoc attachement info |
| File file = getSourceAttachmentPropertiesFile(project.getProject()); |
| try { |
| OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); |
| try { |
| props.store(os, null); |
| } finally { |
| os.close(); |
| } |
| } catch(IOException e) { |
| throw new CoreException( |
| new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, "Can't save classpath container changes", e)); |
| } |
| |
| // update classpath container. suboptimal as this will re-calculate classpath |
| updateClasspath(project.getProject(), monitor); |
| } |
| |
| /** public for unit tests only */ |
| public String getJavadocLocation(IClasspathEntry entry) { |
| return MavenClasspathHelpers.getAttribute(entry, IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME); |
| } |
| |
| public String getSourceAttachmentEncoding(IClasspathEntry entry) { |
| return MavenClasspathHelpers.getAttribute(entry, IClasspathAttribute.SOURCE_ATTACHMENT_ENCODING); |
| } |
| |
| /** public for unit tests only */ |
| public File getSourceAttachmentPropertiesFile(IProject project) { |
| return new File(stateLocationDir, project.getName() + ".sources"); //$NON-NLS-1$ |
| } |
| |
| /** public for unit tests only */ |
| public File getContainerStateFile(IProject project) { |
| return new File(stateLocationDir, project.getName() + ".container"); //$NON-NLS-1$ |
| } |
| |
| public void resourceChanged(IResourceChangeEvent event) { |
| int type = event.getType(); |
| if(IResourceChangeEvent.PRE_DELETE == type) { |
| // remove custom source and javadoc configuration |
| IProject project = (IProject) event.getResource(); |
| File attachmentProperties = getSourceAttachmentPropertiesFile(project); |
| if(attachmentProperties.exists() && !attachmentProperties.delete()) { |
| log.error("Can't delete " + attachmentProperties.getAbsolutePath()); //$NON-NLS-1$ |
| } |
| |
| // remove classpath container state |
| File containerState = getContainerStateFile(project); |
| if(containerState.exists() && !containerState.delete()) { |
| log.error("Can't delete " + containerState.getAbsolutePath()); //$NON-NLS-1$ |
| } |
| |
| requiredModulesMap.remove(project.getLocation().toString()); |
| |
| } else if(IResourceChangeEvent.POST_CHANGE == type) { |
| |
| IResourceDelta delta = event.getDelta(); // workspace delta |
| IResourceDelta[] resourceDeltas = delta.getAffectedChildren(); |
| final Set<IProject> affectedProjects = new LinkedHashSet<IProject>(resourceDeltas.length); |
| ModuleInfoDetector visitor = new ModuleInfoDetector(affectedProjects); |
| for(IResourceDelta d : resourceDeltas) { |
| IProject project = (IProject) d.getResource(); |
| if(!ModuleSupport.isMavenJavaProject(project)) { |
| continue; |
| } |
| try { |
| d.accept(visitor, false); |
| } catch(CoreException ex) { |
| log.error(ex.getMessage(), ex); |
| } |
| } |
| if(affectedProjects.isEmpty()) { |
| return; |
| } |
| |
| Job job = new WorkspaceJob(Messages.BuildPathManager_update_module_path_job_name) { |
| @Override |
| public IStatus runInWorkspace(IProgressMonitor monitor) { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, affectedProjects.size()); |
| for(IProject p : affectedProjects) { |
| if(monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| if(requiresUpdate(p, subMonitor)) { |
| monitor.setTaskName(p.getName()); |
| updateClasspath(p, subMonitor.newChild(1)); |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| |
| private boolean requiresUpdate(IProject p, IProgressMonitor monitor) { |
| if(!ModuleSupport.isMavenJavaProject(p)) { |
| return false; |
| } |
| |
| IJavaProject jp = JavaCore.create(p); |
| try { |
| IModuleDescription moduleDescription = jp.getModuleDescription(); |
| if(moduleDescription == null) { |
| return false; |
| } |
| String location = p.getLocation().toString(); |
| Set<String> requiredModules = new TreeSet<>(ModuleSupport.getRequiredModules(jp, monitor)); |
| if(monitor.isCanceled()) { |
| return false; |
| } |
| // Probably not the best way to detect if module path has changed, like, on the very 1st time a |
| // module-info.java is modified, there will be no previous state to compare to, but should work |
| // well enough the rest of the time, for cases that don't involve obscure module path configs |
| Set<String> oldRequiredModules = requiredModulesMap.get(location); |
| if(requiredModules.equals(oldRequiredModules)) { |
| return false; |
| } |
| requiredModulesMap.put(location, requiredModules); |
| return true; |
| } catch(JavaModelException ex) { |
| log.error(ex.getMessage(), ex); |
| } |
| return false; |
| } |
| }; |
| job.setRule(MavenPlugin.getProjectConfigurationManager().getRule()); |
| job.schedule(); |
| } |
| } |
| |
| public boolean setupVariables() { |
| boolean changed = false; |
| try { |
| File localRepositoryDir = new File(maven.getLocalRepository().getBasedir()); |
| IPath oldPath = JavaCore.getClasspathVariable(M2_REPO); |
| IPath newPath = new Path(localRepositoryDir.getAbsolutePath()); |
| JavaCore.setClasspathVariable(M2_REPO, // |
| newPath, // |
| new NullProgressMonitor()); |
| changed = !newPath.equals(oldPath); |
| } catch(CoreException ex) { |
| log.error(ex.getMessage(), ex); |
| changed = false; |
| } |
| return changed; |
| } |
| |
| public boolean variablesAreInUse() { |
| try { |
| IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()); |
| IJavaProject[] projects = model.getJavaProjects(); |
| for(int i = 0; i < projects.length; i++ ) { |
| IClasspathEntry[] entries = projects[i].getRawClasspath(); |
| for(int k = 0; k < entries.length; k++ ) { |
| IClasspathEntry curr = entries[k]; |
| if(curr.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { |
| String var = curr.getPath().segment(0); |
| if(M2_REPO.equals(var)) { |
| return true; |
| } |
| } |
| } |
| } |
| } catch(JavaModelException e) { |
| return true; |
| } |
| return false; |
| } |
| |
| static String getSourcesClassifier(String baseClassifier) { |
| return BuildPathManager.CLASSIFIER_TESTS.equals(baseClassifier) ? BuildPathManager.CLASSIFIER_TESTSOURCES |
| : BuildPathManager.CLASSIFIER_SOURCES; |
| } |
| |
| private IPath getSourcePath(ArtifactKey a) { |
| File file = getAttachedArtifactFile(a, getSourcesClassifier(a.getClassifier())); |
| |
| if(file != null) { |
| return Path.fromOSString(file.getAbsolutePath()); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Resolves artifact from local repository. Returns null if the artifact is not available locally |
| */ |
| private File getAttachedArtifactFile(ArtifactKey a, String classifier) { |
| // can't use Maven resolve methods since they mark artifacts as not-found even if they could be resolved remotely |
| try { |
| ArtifactRepository localRepository = maven.getLocalRepository(); |
| String relPath = maven.getArtifactPath(localRepository, a.getGroupId(), a.getArtifactId(), a.getVersion(), "jar", //$NON-NLS-1$ |
| classifier); |
| File file = new File(localRepository.getBasedir(), relPath).getCanonicalFile(); |
| if(file.canRead()) { |
| return file; |
| } |
| } catch(CoreException ex) { |
| // fall through |
| } catch(IOException ex) { |
| // fall through |
| } |
| return null; |
| } |
| |
| private String getJavaDocUrl(ArtifactKey base) { |
| File file = getAttachedArtifactFile(base, CLASSIFIER_JAVADOC); |
| |
| return getJavaDocUrl(file); |
| } |
| |
| static String getJavaDocUrl(File file) { |
| try { |
| if(file != null) { |
| URL fileUrl = file.toURL(); |
| return "jar:" + fileUrl.toExternalForm() + "!/" + getJavaDocPathInArchive(file); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } catch(MalformedURLException ex) { |
| // fall through |
| } |
| |
| return null; |
| } |
| |
| private static String getJavaDocPathInArchive(File file) { |
| ZipFile jarFile = null; |
| try { |
| jarFile = new ZipFile(file); |
| String marker = "package-list"; //$NON-NLS-1$ |
| for(Enumeration<? extends ZipEntry> en = jarFile.entries(); en.hasMoreElements();) { |
| ZipEntry entry = en.nextElement(); |
| String entryName = entry.getName(); |
| if(entryName.endsWith(marker)) { |
| return entry.getName().substring(0, entryName.length() - marker.length()); |
| } |
| } |
| } catch(IOException ex) { |
| // ignore |
| } finally { |
| try { |
| if(jarFile != null) |
| jarFile.close(); |
| } catch(IOException ex) { |
| // |
| } |
| } |
| |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * this is for unit tests only! |
| */ |
| public Job getDownloadSourcesJob() { |
| return downloadSourcesJob; |
| } |
| |
| public void scheduleDownload(IPackageFragmentRoot fragment, boolean downloadSources, boolean downloadJavadoc) { |
| ArtifactKey artifact = fragment.getAdapter(ArtifactKey.class); |
| |
| if(artifact == null) { |
| // we don't know anything about this JAR/ZIP |
| return; |
| } |
| |
| IProject project = fragment.getJavaProject().getProject(); |
| |
| try { |
| if(project.hasNature(IMavenConstants.NATURE_ID)) { |
| IMavenProjectFacade facade = projectManager.getProject(project); |
| MavenProject mavenProject = facade != null ? facade.getMavenProject() : null; |
| if(mavenProject != null) { |
| scheduleDownload(project, mavenProject, artifact, downloadSources, downloadJavadoc); |
| } else { |
| downloadSourcesJob.scheduleDownload(project, artifact, downloadSources, downloadJavadoc); |
| } |
| } else { |
| // this is a non-maven project |
| List<ArtifactRepository> repositories = maven.getArtifactRepositories(); |
| ArtifactKey[] attached = getAttachedSourcesAndJavadoc(artifact, repositories, downloadSources, downloadJavadoc); |
| |
| if(attached[0] != null || attached[1] != null) { |
| downloadSourcesJob.scheduleDownload(fragment, artifact, downloadSources, downloadJavadoc); |
| } |
| } |
| } catch(CoreException e) { |
| log.error("Could not schedule sources/javadoc download", e); //$NON-NLS-1$ |
| } |
| |
| } |
| |
| public void scheduleDownload(final IProject project, final boolean downloadSources, final boolean downloadJavadoc) { |
| try { |
| if(project != null && project.isAccessible() && project.hasNature(IMavenConstants.NATURE_ID)) { |
| IMavenProjectFacade facade = projectManager.getProject(project); |
| MavenProject mavenProject = facade != null ? facade.getMavenProject() : null; |
| if(mavenProject != null) { |
| for(Artifact artifact : mavenProject.getArtifacts()) { |
| ArtifactKey artifactKey = new ArtifactKey(artifact.getGroupId(), artifact.getArtifactId(), |
| artifact.getBaseVersion(), artifact.getClassifier()); |
| scheduleDownload(project, mavenProject, artifactKey, downloadSources, downloadJavadoc); |
| } |
| } else { |
| // project is not in the cache, push all processing to the background job |
| downloadSourcesJob.scheduleDownload(project, null, downloadSources, downloadJavadoc); |
| } |
| } |
| } catch(CoreException e) { |
| log.error("Could not schedule sources/javadoc download", e); //$NON-NLS-1$ |
| } |
| } |
| |
| private void scheduleDownload(IProject project, MavenProject mavenProject, ArtifactKey artifact, |
| boolean downloadSources, boolean downloadJavadoc) throws CoreException { |
| ArtifactKey[] attached = getAttachedSourcesAndJavadoc(artifact, mavenProject.getRemoteArtifactRepositories(), |
| downloadSources, downloadJavadoc); |
| |
| if(attached[0] != null || attached[1] != null) { |
| downloadSourcesJob.scheduleDownload(project, artifact, downloadSources, downloadJavadoc); |
| } |
| } |
| |
| /** |
| * Returns an array of {@link ArtifactKey}s. ArtifactKey[0], holds the sources {@link ArtifactKey}, if source download |
| * was requested and sources are available. ArtifactKey[1], holds the javadoc {@link ArtifactKey}, if javadoc download |
| * was requested, or requested sources are unavailable, and javadoc is available |
| */ |
| ArtifactKey[] getAttachedSourcesAndJavadoc(ArtifactKey a, List<ArtifactRepository> repositories, |
| boolean downloadSources, boolean downloadJavaDoc) throws CoreException { |
| ArtifactKey[] result = new ArtifactKey[2]; |
| if(repositories != null) { |
| ArtifactKey sourcesArtifact = new ArtifactKey(a.getGroupId(), a.getArtifactId(), a.getVersion(), |
| getSourcesClassifier(a.getClassifier())); |
| ArtifactKey javadocArtifact = new ArtifactKey(a.getGroupId(), a.getArtifactId(), a.getVersion(), |
| CLASSIFIER_JAVADOC); |
| if(downloadSources) { |
| if(isUnavailable(sourcesArtifact, repositories)) { |
| // 501553: fall back to requesting JavaDoc, if requested sources are missing, |
| // but only if it doesn't exist locally |
| if(getAttachedArtifactFile(a, CLASSIFIER_JAVADOC) == null) { |
| downloadJavaDoc = true; |
| } |
| } else { |
| result[0] = sourcesArtifact; |
| } |
| } |
| if(downloadJavaDoc && !isUnavailable(javadocArtifact, repositories)) { |
| result[1] = javadocArtifact; |
| } |
| } |
| return result; |
| } |
| |
| void attachSourcesAndJavadoc(IPackageFragmentRoot fragment, File sources, File javadoc, IProgressMonitor monitor) { |
| IJavaProject javaProject = fragment.getJavaProject(); |
| |
| IPath srcPath = sources != null ? Path.fromOSString(sources.getAbsolutePath()) : null; |
| String javaDocUrl = getJavaDocUrl(javadoc); |
| |
| try { |
| IClasspathEntry[] cp = javaProject.getRawClasspath(); |
| for(int i = 0; i < cp.length; i++ ) { |
| IClasspathEntry entry = cp[i]; |
| if(IClasspathEntry.CPE_LIBRARY == entry.getEntryKind() && entry.equals(fragment.getRawClasspathEntry())) { |
| List<IClasspathAttribute> attributes = new ArrayList<IClasspathAttribute>( |
| Arrays.asList(entry.getExtraAttributes())); |
| |
| if(srcPath == null) { |
| // configure javadocs if available |
| if(javaDocUrl != null) { |
| attributes |
| .add(JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, javaDocUrl)); |
| } |
| } |
| |
| cp[i] = JavaCore.newLibraryEntry(entry.getPath(), srcPath, null, entry.getAccessRules(), // |
| attributes.toArray(new IClasspathAttribute[attributes.size()]), // |
| entry.isExported()); |
| |
| break; |
| } |
| } |
| |
| javaProject.setRawClasspath(cp, monitor); |
| } catch(CoreException e) { |
| log.error(e.getMessage(), e); |
| } |
| } |
| |
| static class ModuleInfoDetector implements IResourceDeltaVisitor { |
| |
| private Collection<IProject> affectedProjects; |
| |
| public ModuleInfoDetector(Collection<IProject> affectedProjects) { |
| this.affectedProjects = affectedProjects; |
| } |
| |
| public boolean visit(IResourceDelta delta) { |
| if(delta.getResource() instanceof IFile) { |
| IFile file = (IFile) delta.getResource(); |
| if(ModuleSupport.MODULE_INFO_JAVA.equals(file.getName())) { |
| affectedProjects.add(file.getProject()); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| } |
| } |