/******************************************************************************* | |
* Copyright (c) 2003, 2011 IBM Corporation and others. | |
* 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: | |
* IBM Corporation - Initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.jst.server.core; | |
import java.io.File; | |
import java.util.*; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.NullProgressMonitor; | |
import org.eclipse.core.runtime.Path; | |
import org.eclipse.jdt.core.IClasspathAttribute; | |
import org.eclipse.jdt.core.IClasspathContainer; | |
import org.eclipse.jdt.core.IClasspathEntry; | |
import org.eclipse.jdt.core.IJavaProject; | |
import org.eclipse.jdt.core.JavaCore; | |
import org.eclipse.jst.server.core.internal.IMemento; | |
import org.eclipse.jst.server.core.internal.JavaServerPlugin; | |
import org.eclipse.jst.server.core.internal.RuntimeClasspathContainer; | |
import org.eclipse.jst.server.core.internal.Trace; | |
import org.eclipse.jst.server.core.internal.XMLMemento; | |
import org.eclipse.wst.server.core.IRuntime; | |
/** | |
* A runtime classpath provider provides the classpath for a Java server runtime. | |
* This provider is scoped by runtime type and may provide the classpath for multiple | |
* runtime instances. | |
* <p> | |
* This abstract class is intended to be extended only by clients | |
* to extend the <code>runtimeClasspathProviders</code> extension point. | |
* </p> | |
* <p> | |
* <b>Provisional API:</b> This class/interface is part of an interim API that is still under development and expected to | |
* change significantly before reaching stability. It is being made available at this early stage to solicit feedback | |
* from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken | |
* (repeatedly) as the API evolves. | |
* </p> | |
* | |
* @plannedfor 3.0 | |
*/ | |
public abstract class RuntimeClasspathProviderDelegate { | |
protected class SourceAttachmentUpdate { | |
String runtimeId; | |
IPath entry; | |
IPath sourceAttachmentPath; | |
IPath sourceAttachmentRootPath; | |
IClasspathAttribute[] attributes; | |
} | |
private volatile List<SourceAttachmentUpdate> sourceAttachments; | |
private String extensionId; | |
private Map<String, IPath> runtimePathMap = Collections.synchronizedMap(new HashMap<String, IPath>()); | |
private Map<String, IClasspathEntry[]> previousClasspath = Collections.synchronizedMap(new HashMap<String, IClasspathEntry[]>()); | |
public RuntimeClasspathProviderDelegate() { | |
// default constructor | |
} | |
/** | |
* Initializes this classpath provider with its life-long id. | |
* <p> | |
* This method is called by the framework. | |
* Clients should never call this method. | |
* </p> | |
* @param id the extension id | |
*/ | |
public final void initialize(String id) { | |
extensionId = id; | |
} | |
/** | |
* Resolves (creates the classpath entries for) the classpath container with | |
* the given runtime and the given classpath container id (returned from | |
* getClasspathEntryIds()). If the classpath container cannot be resolved | |
* (for instance, if the runtime does not exist), return null. | |
* | |
* @param runtime the runtime to resolve the container for | |
* @return an array of classpath entries for the container, or null if the | |
* container could not be resolved | |
* @deprecated use resolveClasspathContainer(IProject, IRuntime) instead | |
*/ | |
public IClasspathEntry[] resolveClasspathContainer(IRuntime runtime) { | |
return null; | |
} | |
/** | |
* Resolves (creates the classpath entries for) the classpath container with | |
* the given runtime and the given classpath container id (returned from | |
* getClasspathEntryIds()). If the classpath container cannot be resolved | |
* (for instance, if the runtime does not exist), return null. | |
* | |
* @param project the project to resolve | |
* @param runtime the runtime to resolve the container for | |
* @return an array of classpath entries for the container, or null if the | |
* container could not be resolved | |
*/ | |
public IClasspathEntry[] resolveClasspathContainer(IProject project, IRuntime runtime) { | |
return null; | |
} | |
/** | |
* Resolve the classpath container. | |
* | |
* @param runtime a runtime | |
* @return a possibly empty array of classpath entries | |
* @deprecated should use resolveClasspathContainerImpl(IProject, IRuntime) instead | |
*/ | |
public IClasspathEntry[] resolveClasspathContainerImpl(IRuntime runtime) { | |
return resolveClasspathContainerImpl(null, runtime); | |
} | |
/** | |
* Resolve the classpath container. | |
* | |
* @param project a project | |
* @param runtime a runtime | |
* @return a possibly empty array of classpath entries | |
*/ | |
public IClasspathEntry[] resolveClasspathContainerImpl(IProject project, IRuntime runtime) { | |
if (runtime == null) | |
return new IClasspathEntry[0]; | |
runtimePathMap.put(runtime.getId(), runtime.getLocation()); | |
IClasspathEntry[] entries = resolveClasspathContainer(project, runtime); | |
if (entries == null) | |
entries = resolveClasspathContainer(runtime); | |
if (entries == null) | |
entries = new IClasspathEntry[0]; | |
synchronized (this) { | |
if (sourceAttachments == null) | |
load(); | |
} | |
List<SourceAttachmentUpdate> srcAttachments = sourceAttachments; | |
if (srcAttachments != null) { | |
int size = entries.length; | |
int size2 = srcAttachments.size(); | |
for (int i = 0; i < size; i++) { | |
for (int j = 0; j < size2; j++) { | |
SourceAttachmentUpdate sau = srcAttachments.get(j); | |
if (sau.runtimeId.equals(runtime.getId()) && sau.entry.equals(entries[i].getPath())) { | |
IClasspathAttribute[] consolidatedClasspathAttributes = consolidateClasspathAttributes(sau.attributes, entries[i].getExtraAttributes()); | |
entries[i] = JavaCore.newLibraryEntry(entries[i].getPath(), sau.sourceAttachmentPath, sau.sourceAttachmentRootPath, entries[i].getAccessRules(), consolidatedClasspathAttributes, false); | |
break; | |
} | |
} | |
} | |
} | |
String key = project.getName() + "/" + runtime.getId(); | |
if (!previousClasspath.containsKey(key)) | |
previousClasspath.put(key, entries); | |
else { | |
IClasspathEntry[] previousClasspathEntries = previousClasspath.get(key); | |
if (previousClasspathEntries == null | |
|| previousClasspathEntries.length != entries.length | |
|| entriesChanged(previousClasspathEntries,entries)) { | |
if (Trace.FINEST) { | |
Trace.trace(Trace.STRING_FINEST, "Classpath update: " + key + " " + entries); | |
} | |
previousClasspath.put(key, entries); | |
IPath path = new Path(RuntimeClasspathContainer.SERVER_CONTAINER); | |
path = path.append(extensionId).append(runtime.getId()); | |
try { | |
IJavaProject javaProject = JavaCore.create(project); | |
JavaCore.setClasspathContainer(path, new IJavaProject[] { javaProject }, | |
new IClasspathContainer[] { null }, new NullProgressMonitor()); | |
} catch (Exception e) { | |
if (Trace.WARNING) { | |
Trace.trace(Trace.STRING_WARNING, "Error updating classpath", e); | |
} | |
} | |
} | |
} | |
return entries; | |
} | |
private boolean entriesChanged(IClasspathEntry[] previousEntries, IClasspathEntry[] entries) { | |
if (previousEntries.length != entries.length) { | |
return true; | |
} | |
for (int i=0; i<previousEntries.length; i++) { | |
if ((previousEntries[i] == null && entries[i] != null) | |
|| (previousEntries[i].getPath() == null && entries[i].getPath() != null) | |
|| !previousEntries[i].getPath().equals(entries[i].getPath())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/* | |
* Returns true if there are any changes in the runtime since the last time that the | |
* classpath was resolved which may affect the classpath, and false otherwise. This | |
* method is used to check projects when a runtime changes and automatically rebuild | |
* them if necessary. | |
* | |
* @param runtime a runtime | |
* @return <code>true</code> if the classpath may change due to a change in the runtime, | |
* and <code>false</code> if there are no changes | |
*/ | |
public boolean hasRuntimeClasspathChanged(IRuntime runtime) { | |
try { | |
IPath path = runtimePathMap.get(runtime.getId()); | |
return (path != null && !path.equals(runtime.getLocation())); | |
} catch (Exception e) { | |
// ignore | |
} | |
return false; | |
} | |
private static void addJarFiles(File dir, List<IClasspathEntry> list, boolean includeSubdirectories) { | |
int depth = 0; | |
if (includeSubdirectories) | |
depth = 2; | |
addJarFiles(dir, list, depth); | |
} | |
private static void addJarFiles(File dir, List<IClasspathEntry> list, int depth) { | |
if (dir == null) | |
throw new IllegalArgumentException(); | |
File[] files = dir.listFiles(); | |
if (files != null) { | |
for (File file : files) { | |
if (file.isDirectory() && depth > 0) { | |
addJarFiles(file, list, depth - 1); | |
} else if (file.getAbsolutePath().endsWith(".jar") || file.getAbsolutePath().endsWith(".zip")) { | |
IPath path = new Path(file.getAbsolutePath()); | |
list.add(JavaCore.newLibraryEntry(path, null, null)); | |
} | |
} | |
} | |
} | |
/** | |
* Add library entries to the given list for every jar file found in the | |
* given directory. Optionally search subdirectories as well. | |
* | |
* @param list a list | |
* @param dir a directory | |
* @param includeSubdirectories <code>true</code> to include subdirectories, and | |
* <code>false</code> otherwise | |
*/ | |
protected static void addLibraryEntries(List<IClasspathEntry> list, File dir, boolean includeSubdirectories) { | |
if (dir == null) | |
throw new IllegalArgumentException(); | |
addJarFiles(dir, list, includeSubdirectories); | |
} | |
/** | |
* Request that the classpath container for the given runtime and id be updated | |
* with the given classpath container entries. | |
* | |
* @param runtime a runtime | |
* @param entries an array of classpath entries | |
*/ | |
public void requestClasspathContainerUpdate(IRuntime runtime, IClasspathEntry[] entries) { | |
// default behaviour is to save the source path entries | |
if (runtime == null || entries == null) | |
return; | |
// find the source attachments | |
List<SourceAttachmentUpdate> srcAttachments = new ArrayList<SourceAttachmentUpdate>(); | |
for (IClasspathEntry entry : entries) { | |
if (entry.getSourceAttachmentPath() != null || (entry.getExtraAttributes() != null && entry.getExtraAttributes().length > 0)) { | |
SourceAttachmentUpdate sau = new SourceAttachmentUpdate(); | |
sau.runtimeId = runtime.getId(); | |
sau.entry = entry.getPath(); | |
sau.sourceAttachmentPath = entry.getSourceAttachmentPath(); | |
sau.sourceAttachmentRootPath = entry.getSourceAttachmentRootPath(); | |
sau.attributes = entry.getExtraAttributes(); | |
srcAttachments.add(sau); | |
} | |
} | |
sourceAttachments = srcAttachments; | |
save(); | |
} | |
/** | |
* Load source attachment info. | |
*/ | |
private void load() { | |
List<SourceAttachmentUpdate> srcAttachments = new ArrayList<SourceAttachmentUpdate>(); | |
String id = extensionId; | |
String filename = JavaServerPlugin.getInstance().getStateLocation().append(id + ".xml").toOSString(); | |
if (!(new File(filename)).exists()) | |
return; | |
try { | |
IMemento memento = XMLMemento.loadMemento(filename); | |
IMemento[] children = memento.getChildren("source-attachment"); | |
for (IMemento child : children) { | |
try { | |
SourceAttachmentUpdate sau = new SourceAttachmentUpdate(); | |
sau.runtimeId = child.getString("runtime-id"); | |
String temp = child.getString("entry"); | |
if (temp != null) | |
sau.entry = new Path(temp); | |
temp = child.getString("source-attachment-path"); | |
if (temp != null) | |
sau.sourceAttachmentPath = new Path(temp); | |
temp = child.getString("source-attachment-root-path"); | |
if (temp != null) | |
sau.sourceAttachmentRootPath = new Path(temp); | |
IMemento[] attrChildren = child.getChildren("attribute"); | |
if (attrChildren != null) { | |
int size2 = attrChildren.length; | |
sau.attributes = new IClasspathAttribute[size2]; | |
for (int j = 0; j < size2; j++) { | |
String name = attrChildren[j].getString("name"); | |
String value = attrChildren[j].getString("value"); | |
sau.attributes[j] = JavaCore.newClasspathAttribute(name, value); | |
} | |
} | |
srcAttachments.add(sau); | |
} catch (Exception e) { | |
if (Trace.WARNING) { | |
Trace.trace(Trace.STRING_WARNING, "Could not load source attachment: " + e); | |
} | |
} | |
} | |
} catch (Exception e) { | |
if (Trace.WARNING) { | |
Trace.trace(Trace.STRING_WARNING, "Could not load source path info", e); | |
} | |
} | |
sourceAttachments = srcAttachments; | |
} | |
/** | |
* Save source attachment info. | |
*/ | |
private synchronized void save() { | |
List<SourceAttachmentUpdate> srcAttachments = sourceAttachments; | |
if (srcAttachments == null) | |
return; | |
String id = extensionId; | |
String filename = JavaServerPlugin.getInstance().getStateLocation().append(id + ".xml").toOSString(); | |
try { | |
XMLMemento memento = XMLMemento.createWriteRoot("classpath"); | |
Iterator iterator = srcAttachments.iterator(); | |
while (iterator.hasNext()) { | |
SourceAttachmentUpdate sau = (SourceAttachmentUpdate) iterator.next(); | |
IMemento child = memento.createChild("source-attachment"); | |
child.putString("runtime-id", sau.runtimeId); | |
if (sau.entry != null) | |
child.putString("entry", sau.entry.toPortableString()); | |
if (sau.sourceAttachmentPath != null) | |
child.putString("source-attachment-path", sau.sourceAttachmentPath.toPortableString()); | |
if (sau.sourceAttachmentRootPath != null) | |
child.putString("source-attachment-root-path", sau.sourceAttachmentRootPath.toPortableString()); | |
if (sau.attributes != null) { | |
for (IClasspathAttribute attr : sau.attributes) { | |
IMemento attrChild = child.createChild("attribute"); | |
attrChild.putString("name", attr.getName()); | |
attrChild.putString("value", attr.getValue()); | |
} | |
} | |
} | |
memento.saveToFile(filename); | |
} catch (Exception e) { | |
if (Trace.SEVERE) { | |
Trace.trace(Trace.STRING_SEVERE, "Error saving source path info", e); | |
} | |
} | |
} | |
public IClasspathAttribute[] consolidateClasspathAttributes(IClasspathAttribute[] sourceAttachmentAttributes, IClasspathAttribute[] classpathEntryAttributes) { | |
List classpathAttributeList = new ArrayList(); | |
classpathAttributeList.addAll(Arrays.asList(sourceAttachmentAttributes)); | |
for (int i = 0; i < classpathEntryAttributes.length; i++) { | |
boolean attributeCollision = false; | |
for (int j = 0; j < sourceAttachmentAttributes.length; j++) { | |
String name = classpathEntryAttributes[i].getName(); | |
if(name != null && name.equals(sourceAttachmentAttributes[j].getName())) { | |
attributeCollision = true; | |
break; | |
} | |
} | |
if(!attributeCollision) { | |
classpathAttributeList.add(classpathEntryAttributes[i]); | |
} | |
} | |
return (IClasspathAttribute[]) classpathAttributeList.toArray(new IClasspathAttribute[classpathAttributeList.size()]); | |
} | |
} |