/******************************************************************************* * 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()]); } }