### Eclipse Workspace Patch 1.0 #P org.eclipse.jdt.core Index: model/org/eclipse/jdt/core/IClasspathEntry.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IClasspathEntry.java,v retrieving revision 1.65 diff -u -r1.65 IClasspathEntry.java --- model/org/eclipse/jdt/core/IClasspathEntry.java 27 Jun 2008 16:04:00 -0000 1.65 +++ model/org/eclipse/jdt/core/IClasspathEntry.java 26 Feb 2010 19:37:59 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -418,6 +418,28 @@ */ IPath getSourceAttachmentRootPath(); + + /** + * Returns the classpath entry that is making a reference to this classpath entry. For entry kinds + * {@link #CPE_LIBRARY}, the return value is the entry that is representing the JAR that includes + * this in the MANIFEST.MF file's Class-Path section. For entry kinds other than + * {@link #CPE_LIBRARY}, this returns null. For those entries that are on the raw classpath already, + * this returns null + *

+ * It is possible that multiple library entries refer to the same entry + * via the MANIFEST.MF file. In those cases, this method returns the first classpath entry + * that appears in the raw classpath. However, this does not mean that the other referencing + * entries do not relate to their referenced entries. + * See {@link JavaCore#getReferencedClasspathEntries(IClasspathEntry, IJavaProject)} for + * more details. + *

+ * + * @return the classpath entry that is referencing this entry or null if + * not applicable. + * @since 3.6 + */ + IClasspathEntry getReferencingEntry(); + /** * Returns whether this entry is exported to dependent projects. * Always returns false for source entries (kind Index: model/org/eclipse/jdt/core/IJavaProject.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java,v retrieving revision 1.103 diff -u -r1.103 IJavaProject.java --- model/org/eclipse/jdt/core/IJavaProject.java 27 Oct 2008 14:47:36 -0000 1.103 +++ model/org/eclipse/jdt/core/IJavaProject.java 26 Feb 2010 19:37:59 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -1008,6 +1008,54 @@ void setRawClasspath(IClasspathEntry[] entries, boolean canModifyResources, IProgressMonitor monitor) throws JavaModelException; /** + * Works same as {@link #setRawClasspath(IClasspathEntry[], IPath, IProgressMonitor)} and + * additionally allows persisting the given array of referenced entries for this project. + * The referenced entries and their attributes are stores in the .classpath file of this + * project. For details on referenced entries, see + * {@link JavaCore#getReferencedClasspathEntries(IClasspathEntry, IJavaProject)} + * and {@link IClasspathEntry#getReferencingEntry()}. + *

+ * Since the referenced entries are stored in the .classpath file, clients can store additional + * information that belong to these entries and retrieve them across sessions, though the referenced + * entries themselves may not be present in the raw classpath. By passing a null + * referencedEntries, clients can choose not to modify the already persisted referenced entries, + * which is fully equivalent to {@link #setRawClasspath(IClasspathEntry[], IPath, IProgressMonitor)}. + * If an empty array is passed as referencedEntries, the already persisted referenced entries, + * if any, will be cleared. + *

+ * If there are duplicates of a referenced entry or if any of the referencedEntries + * is already present in the raw classpath(entries) those referenced entries will + * be excluded and will not be persisted. + *

+ * @param entries a list of classpath entries + * @param referencedEntries the list of referenced classpath entries to be persisted + * @param outputLocation the default output location + * @param monitor the given progress monitor + * @exception JavaModelException if the classpath could not be set. Reasons include: + * + * @see IClasspathEntry + * @see #getReferencedClasspathEntries() + * @since 3.6 + */ + void setRawClasspath(IClasspathEntry[] entries, IClasspathEntry[] referencedEntries, IPath outputLocation, + IProgressMonitor monitor) throws JavaModelException; + + /** + * Returns the list of referenced classpath entries stored in the .classpath file of this + * java project. Clients can store the referenced classpath entries using + * {@link #setRawClasspath(IClasspathEntry[], IClasspathEntry[], IPath, IProgressMonitor)} + * + * @throws JavaModelException + * @return an array of referenced classpath entries stored for this java project + * @since 3.6 + */ + IClasspathEntry[] getReferencedClasspathEntries() throws JavaModelException; + + /** * Sets the classpath of this project using a list of classpath entries. In particular such a classpath may contain * classpath variable entries. Classpath variable entries can be resolved individually ({@link JavaCore#getClasspathVariable(String)}), * or the full classpath can be resolved at once using the helper method {@link #getResolvedClasspath(boolean)}. Index: model/org/eclipse/jdt/core/IPackageFragmentRoot.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IPackageFragmentRoot.java,v retrieving revision 1.54 diff -u -r1.54 IPackageFragmentRoot.java --- model/org/eclipse/jdt/core/IPackageFragmentRoot.java 27 Jun 2008 16:04:00 -0000 1.54 +++ model/org/eclipse/jdt/core/IPackageFragmentRoot.java 26 Feb 2010 19:37:59 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -311,6 +311,18 @@ * @since 2.0 */ IClasspathEntry getRawClasspathEntry() throws JavaModelException; + + /** + * Returns the first resolved classpath entry that corresponds to this package fragment root. + * A resolved classpath entry is said to correspond to a root if the path of the resolved + * entry is equal to the root's path. + * + * @return the first resolved classpath entry that corresponds to this package fragment root + * @throws JavaModelException if this element does not exist or if an + * exception occurs while accessing its corresponding resource. + * @since 3.6 + */ + IClasspathEntry getResolvedClasspathEntry() throws JavaModelException; /** * Returns the absolute path to the source archive attached to Index: model/org/eclipse/jdt/core/JavaCore.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java,v retrieving revision 1.648 diff -u -r1.648 JavaCore.java --- model/org/eclipse/jdt/core/JavaCore.java 25 Feb 2010 19:17:02 -0000 1.648 +++ model/org/eclipse/jdt/core/JavaCore.java 26 Feb 2010 19:37:59 -0000 @@ -4587,6 +4587,33 @@ false, // no access rules to combine extraAttributes); } + + /** + * Returns an array of classpath entries that are referenced directly or indirectly + * by a given classpath entry. For the entry kind {@link IClasspathEntry#CPE_LIBRARY}, + * the method returns the libraries that are included in the Class-Path section of + * the MANIFEST.MF file. If a referenced JAR file has further references to other library + * entries, they are processed recursively and added to the list. For entry kinds other + * than {@link IClasspathEntry#CPE_LIBRARY}, this method returns an empty array. + *

+ * If a referenced entry has already been stored + * in the given project's .classpath, the stored attributes are populated in the corresponding + * referenced entry. For more details on storing referenced entries see + * see {@link IJavaProject#setRawClasspath(IClasspathEntry[], IClasspathEntry[], IPath, + * IProgressMonitor)}. + *

+ * + * @param libraryEntry the library entry whose referenced entries are sought + * @param project project where the persisted referenced entries to be retrieved from + * @return an array of classpath entries that are referenced directly or indirectly by the given entry. + * If not applicable, returns an emptry array. + * @since 3.6 + */ + public static IClasspathEntry[] getReferencedClasspathEntries(IClasspathEntry libraryEntry, IJavaProject project) { + JavaModelManager manager = JavaModelManager.getJavaModelManager(); + return manager.getReferencedClasspathEntries(libraryEntry, project); + } + /** * Removed the given classpath variable. Does nothing if no value was * set for this classpath variable. Index: model/org/eclipse/jdt/internal/core/ClasspathEntry.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java,v retrieving revision 1.123 diff -u -r1.123 ClasspathEntry.java --- model/org/eclipse/jdt/internal/core/ClasspathEntry.java 1 Feb 2010 11:13:25 -0000 1.123 +++ model/org/eclipse/jdt/internal/core/ClasspathEntry.java 26 Feb 2010 19:38:00 -0000 @@ -77,6 +77,7 @@ public static final String TAG_CLASSPATH = "classpath"; //$NON-NLS-1$ public static final String TAG_CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$ + public static final String TAG_REFERENCED_ENTRY = "referencedentry"; //$NON-NLS-1$ public static final String TAG_OUTPUT = "output"; //$NON-NLS-1$ public static final String TAG_KIND = "kind"; //$NON-NLS-1$ public static final String TAG_PATH = "path"; //$NON-NLS-1$ @@ -141,7 +142,7 @@ private IPath[] exclusionPatterns; private char[][] fullExclusionPatternChars; private final static char[][] UNINIT_PATTERNS = new char[][] { "Non-initialized yet".toCharArray() }; //$NON-NLS-1$ - private final static ClasspathEntry[] NO_ENTRIES = new ClasspathEntry[0]; + public final static ClasspathEntry[] NO_ENTRIES = new ClasspathEntry[0]; private final static IPath[] NO_PATHS = new IPath[0]; private final static IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); @@ -197,6 +198,11 @@ * a non-null value. */ public IPath sourceAttachmentRootPath; + + /** + * See {@link IClasspathEntry#getReferencingEntry()} + */ + public IClasspathEntry referencingEntry; /** * Specific output location (for this source entry) @@ -215,11 +221,40 @@ */ public boolean isExported; - /* + /** * The extra attributes */ - IClasspathAttribute[] extraAttributes; + public IClasspathAttribute[] extraAttributes; + public ClasspathEntry( + int contentKind, + int entryKind, + IPath path, + IPath[] inclusionPatterns, + IPath[] exclusionPatterns, + IPath sourceAttachmentPath, + IPath sourceAttachmentRootPath, + IPath specificOutputLocation, + boolean isExported, + IAccessRule[] accessRules, + boolean combineAccessRules, + IClasspathAttribute[] extraAttributes) { + + this( contentKind, + entryKind, + path, + inclusionPatterns, + exclusionPatterns, + sourceAttachmentPath, + sourceAttachmentRootPath, + specificOutputLocation, + null, + isExported, + accessRules, + combineAccessRules, + extraAttributes); + } + /** * Creates a class path entry of the specified kind with the given path. */ @@ -232,6 +267,7 @@ IPath sourceAttachmentPath, IPath sourceAttachmentRootPath, IPath specificOutputLocation, + IClasspathEntry referencingEntry, boolean isExported, IAccessRule[] accessRules, boolean combineAccessRules, @@ -242,7 +278,8 @@ this.path = path; this.inclusionPatterns = inclusionPatterns; this.exclusionPatterns = exclusionPatterns; - + this.referencingEntry = referencingEntry; + int length; if (accessRules != null && (length = accessRules.length) > 0) { AccessRule[] rules = new AccessRule[length]; @@ -489,7 +526,7 @@ /** * Returns the XML encoding of the class path. */ - public void elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine, Map unknownElements) { + public void elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine, Map unknownElements, boolean isReferencedEntry) { HashMap parameters = new HashMap(); parameters.put(TAG_KIND, ClasspathEntry.kindToString(this.entryKind)); @@ -553,12 +590,15 @@ boolean hasRestrictions = getAccessRuleSet() != null; // access rule set is null if no access rules ArrayList unknownChildren = unknownXmlElements != null ? unknownXmlElements.children : null; boolean hasUnknownChildren = unknownChildren != null; + + /* close tag if no extra attributes, no restriction and no unknown children */ + String tagName = isReferencedEntry ? TAG_REFERENCED_ENTRY : TAG_CLASSPATHENTRY; writer.printTag( - TAG_CLASSPATHENTRY, + tagName, parameters, indent, newLine, - !hasExtraAttributes && !hasRestrictions && !hasUnknownChildren/*close tag if no extra attributes, no restriction and no unknown children*/); + !hasExtraAttributes && !hasRestrictions && !hasUnknownChildren); if (hasExtraAttributes) encodeExtraAttributes(writer, indent, newLine); @@ -570,7 +610,7 @@ encodeUnknownChildren(writer, indent, newLine, unknownChildren); if (hasExtraAttributes || hasRestrictions || hasUnknownChildren) - writer.endTag(TAG_CLASSPATHENTRY, indent, true/*insert new line*/); + writer.endTag(tagName, indent, true/*insert new line*/); } void encodeExtraAttributes(XMLWriter writer, boolean indent, boolean newLine) { @@ -1178,6 +1218,11 @@ return this.sourceAttachmentRootPath; } + + public IClasspathEntry getReferencingEntry() { + return this.referencingEntry; + } + /** * Returns the hash code for this classpath entry */ @@ -1380,6 +1425,7 @@ getSourceAttachmentPath(), getSourceAttachmentRootPath(), getOutputLocation(), + this.getReferencingEntry(), this.isExported, getAccessRules(), this.combineAccessRules, @@ -1397,19 +1443,21 @@ return NO_ENTRIES; ClasspathEntry[] result = new ClasspathEntry[length]; for (int i = 0; i < length; i++) { + // Chained(referenced) libraries can have their own attachment path. Hence, set them to null result[i] = new ClasspathEntry( - getContentKind(), - getEntryKind(), - paths[i], - this.inclusionPatterns, - this.exclusionPatterns, - getSourceAttachmentPath(), - getSourceAttachmentRootPath(), - getOutputLocation(), - this.isExported, - getAccessRules(), - this.combineAccessRules, - this.extraAttributes); + getContentKind(), + getEntryKind(), + paths[i], + this.inclusionPatterns, + this.exclusionPatterns, + null, + null, + getOutputLocation(), + this, + this.isExported, + getAccessRules(), + this.combineAccessRules, + NO_EXTRA_ATTRIBUTES); } return result; } Index: model/org/eclipse/jdt/internal/core/JavaModelManager.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelManager.java,v retrieving revision 1.444 diff -u -r1.444 JavaModelManager.java --- model/org/eclipse/jdt/internal/core/JavaModelManager.java 24 Feb 2010 11:02:18 -0000 1.444 +++ model/org/eclipse/jdt/internal/core/JavaModelManager.java 26 Feb 2010 19:38:00 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -1095,6 +1095,7 @@ public Object savedState; public boolean triedRead; public IClasspathEntry[] rawClasspath; + public IClasspathEntry[] referencedEntries; public IJavaModelStatus rawClasspathStatus; public int rawTimeStamp = 0; public boolean writtingRawClasspath = false; @@ -1169,9 +1170,11 @@ return setResolvedClasspath(null, null, null, null, this.rawTimeStamp, true/*add classpath change*/); } - private ClasspathChange setClasspath(IClasspathEntry[] newRawClasspath, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus, IClasspathEntry[] newResolvedClasspath, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, boolean addClasspathChange) { + private ClasspathChange setClasspath(IClasspathEntry[] newRawClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus, IClasspathEntry[] newResolvedClasspath, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, boolean addClasspathChange) { ClasspathChange classpathChange = addClasspathChange ? addClasspathChange() : null; + if (referencedEntries != null) this.referencedEntries = referencedEntries; + if (this.referencedEntries == null) this.referencedEntries = ClasspathEntry.NO_ENTRIES; this.rawClasspath = newRawClasspath; this.outputLocation = newOutputLocation; this.rawClasspathStatus = newRawClasspathStatus; @@ -1191,32 +1194,40 @@ return classpathChange; } - public synchronized ClasspathChange setRawClasspath(IClasspathEntry[] newRawClasspath, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus) { + public ClasspathChange setRawClasspath(IClasspathEntry[] newRawClasspath, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus) { + return setRawClasspath(newRawClasspath, null, newOutputLocation, newRawClasspathStatus); + } + + public synchronized ClasspathChange setRawClasspath(IClasspathEntry[] newRawClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus) { this.rawTimeStamp++; - return setClasspath(newRawClasspath, newOutputLocation, newRawClasspathStatus, null/*resolved classpath*/, null/*root to raw map*/, null/*root to resolved map*/, null/*unresolved status*/, true/*add classpath change*/); + return setClasspath(newRawClasspath, referencedEntries, newOutputLocation, newRawClasspathStatus, null/*resolved classpath*/, null/*root to raw map*/, null/*root to resolved map*/, null/*unresolved status*/, true/*add classpath change*/); } - public synchronized ClasspathChange setResolvedClasspath(IClasspathEntry[] newResolvedClasspath, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, int timeStamp, boolean addClasspathChange) { + public ClasspathChange setResolvedClasspath(IClasspathEntry[] newResolvedClasspath, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, int timeStamp, boolean addClasspathChange) { + return setResolvedClasspath(newResolvedClasspath, null, newRootPathToRawEntries, newRootPathToResolvedEntries, newUnresolvedEntryStatus, timeStamp, addClasspathChange); + } + + public synchronized ClasspathChange setResolvedClasspath(IClasspathEntry[] newResolvedClasspath, IClasspathEntry[] referencedEntries, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, int timeStamp, boolean addClasspathChange) { if (this.rawTimeStamp != timeStamp) return null; - return setClasspath(this.rawClasspath, this.outputLocation, this.rawClasspathStatus, newResolvedClasspath, newRootPathToRawEntries, newRootPathToResolvedEntries, newUnresolvedEntryStatus, addClasspathChange); + return setClasspath(this.rawClasspath, referencedEntries, this.outputLocation, this.rawClasspathStatus, newResolvedClasspath, newRootPathToRawEntries, newRootPathToResolvedEntries, newUnresolvedEntryStatus, addClasspathChange); } - public synchronized IClasspathEntry[] readAndCacheClasspath(JavaProject javaProject) { + public synchronized IClasspathEntry[][] readAndCacheClasspath(JavaProject javaProject) { // read file entries and update status - IClasspathEntry[] classpath; + IClasspathEntry[][] classpath; IJavaModelStatus status; try { classpath = javaProject.readFileEntriesWithException(null/*not interested in unknown elements*/); status = JavaModelStatus.VERIFIED_OK; } catch (CoreException e) { - classpath = JavaProject.INVALID_CLASSPATH; + classpath = new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; status = new JavaModelStatus( IJavaModelStatusConstants.INVALID_CLASSPATH_FILE_FORMAT, Messages.bind(Messages.classpath_cannotReadClasspathFile, javaProject.getElementName())); } catch (IOException e) { - classpath = JavaProject.INVALID_CLASSPATH; + classpath = new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; if (Messages.file_badFormat.equals(e.getMessage())) status = new JavaModelStatus( @@ -1228,7 +1239,7 @@ IJavaModelStatusConstants.INVALID_CLASSPATH_FILE_FORMAT, Messages.bind(Messages.classpath_cannotReadClasspathFile, javaProject.getElementName())); } catch (ClasspathEntry.AssertionFailedException e) { - classpath = JavaProject.INVALID_CLASSPATH; + classpath = new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; status = new JavaModelStatus( IJavaModelStatusConstants.INVALID_CLASSPATH_FILE_FORMAT, @@ -1236,19 +1247,20 @@ } // extract out the output location + int rawClasspathLength = classpath[0].length; IPath output = null; - if (classpath.length > 0) { - IClasspathEntry entry = classpath[classpath.length - 1]; + if (rawClasspathLength > 0) { + IClasspathEntry entry = classpath[0][rawClasspathLength - 1]; if (entry.getContentKind() == ClasspathEntry.K_OUTPUT) { output = entry.getPath(); - IClasspathEntry[] copy = new IClasspathEntry[classpath.length - 1]; - System.arraycopy(classpath, 0, copy, 0, copy.length); - classpath = copy; + IClasspathEntry[] copy = new IClasspathEntry[rawClasspathLength - 1]; + System.arraycopy(classpath[0], 0, copy, 0, copy.length); + classpath[0] = copy; } } // store new raw classpath, new output and new status, and null out resolved info - setRawClasspath(classpath, output, status); + setRawClasspath(classpath[0], classpath[1], output, status); return classpath; } @@ -1292,20 +1304,31 @@ return buffer.toString(); } - public boolean writeAndCacheClasspath(JavaProject javaProject, final IClasspathEntry[] newRawClasspath, final IPath newOutputLocation) throws JavaModelException { + public boolean writeAndCacheClasspath( + JavaProject javaProject, + final IClasspathEntry[] newRawClasspath, + IClasspathEntry[] newReferencedEntries, + final IPath newOutputLocation) throws JavaModelException { try { this.writtingRawClasspath = true; + if (newReferencedEntries == null) newReferencedEntries = this.referencedEntries; + // write .classpath - if (!javaProject.writeFileEntries(newRawClasspath, newOutputLocation)) { + if (!javaProject.writeFileEntries(newRawClasspath, newReferencedEntries, newOutputLocation)) { return false; } // store new raw classpath, new output and new status, and null out resolved info - setRawClasspath(newRawClasspath, newOutputLocation, JavaModelStatus.VERIFIED_OK); + setRawClasspath(newRawClasspath, newReferencedEntries, newOutputLocation, JavaModelStatus.VERIFIED_OK); } finally { this.writtingRawClasspath = false; } return true; } + + public boolean writeAndCacheClasspath(JavaProject javaProject, final IClasspathEntry[] newRawClasspath, final IPath newOutputLocation) throws JavaModelException { + return writeAndCacheClasspath(javaProject, newRawClasspath, null, newOutputLocation); + } + } public static class PerWorkingCopyInfo implements IProblemRequestor { @@ -1819,6 +1842,30 @@ return container; } + public IClasspathEntry[] getReferencedClasspathEntries(IClasspathEntry libraryEntry, IJavaProject project) { + + IClasspathEntry[] referencedEntries = ((ClasspathEntry)libraryEntry).resolvedChainedLibraries(); + PerProjectInfo perProjectInfo = getPerProjectInfo(project.getProject(), false); + + if(perProjectInfo == null) + return referencedEntries; + + List pathToReferencedEntries = new ArrayList(referencedEntries.length); + for (int index = 0; index < referencedEntries.length; index++) { + + if (pathToReferencedEntries.contains(referencedEntries[index].getPath())) + continue; + + IClasspathEntry persistedEntry = null; + if ((persistedEntry = (IClasspathEntry)perProjectInfo.rootPathToResolvedEntries.get(referencedEntries[index].getPath())) != null) { + // TODO: reconsider this - may want to copy the values instead of reference assignment? + referencedEntries[index] = persistedEntry; + } + pathToReferencedEntries.add(referencedEntries[index].getPath()); + } + return referencedEntries; + } + public DeltaProcessor getDeltaProcessor() { return this.deltaState.getDeltaProcessor(); } @@ -3488,7 +3535,7 @@ } else { IClasspathEntry[] entries; try { - entries = ((JavaProject) project).decodeClasspath(containerString, null/*not interested in unknown elements*/); + entries = ((JavaProject) project).decodeClasspath(containerString, null/*not interested in unknown elements*/)[0]; } catch (IOException e) { Util.log(e, "Could not recreate persisted container: \n" + containerString); //$NON-NLS-1$ entries = JavaProject.INVALID_CLASSPATH; Index: model/org/eclipse/jdt/internal/core/JavaProject.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java,v retrieving revision 1.428 diff -u -r1.428 JavaProject.java --- model/org/eclipse/jdt/internal/core/JavaProject.java 22 Feb 2010 07:50:00 -0000 1.428 +++ model/org/eclipse/jdt/internal/core/JavaProject.java 26 Feb 2010 19:38:00 -0000 @@ -18,6 +18,7 @@ import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; @@ -45,6 +46,7 @@ import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; @@ -260,6 +262,20 @@ return false; return true; } + + private static boolean areClasspathEqual(IClasspathEntry[] first, IClasspathEntry[] second) { + if (first != second){ + if (first == null) return false; + int length = first.length; + if (second == null || second.length != length) + return false; + for (int i = 0; i < length; i++) { + if (!first[i].equals(second[i])) + return false; + } + } + return true; + } /** * Returns a canonicalized path from the given external path. @@ -848,7 +864,7 @@ /* * Reads and decode an XML classpath string */ - public IClasspathEntry[] decodeClasspath(String xmlClasspath, Map unknownElements) throws IOException, ClasspathEntry.AssertionFailedException { + public IClasspathEntry[][] decodeClasspath(String xmlClasspath, Map unknownElements) throws IOException, ClasspathEntry.AssertionFailedException { ArrayList paths = new ArrayList(); IClasspathEntry defaultOutput = null; @@ -868,7 +884,7 @@ if (!cpElement.getNodeName().equalsIgnoreCase("classpath")) { //$NON-NLS-1$ throw new IOException(Messages.file_badFormat); } - NodeList list = cpElement.getElementsByTagName("classpathentry"); //$NON-NLS-1$ + NodeList list = cpElement.getElementsByTagName(ClasspathEntry.TAG_CLASSPATHENTRY); int length = list.getLength(); for (int i = 0; i < length; ++i) { @@ -880,15 +896,32 @@ defaultOutput = entry; // separate output } else { paths.add(entry); + } + } } } + int pathSize = paths.size(); + IClasspathEntry[][] entries = new IClasspathEntry[2][]; + entries[0] = new IClasspathEntry[pathSize + (defaultOutput == null ? 0 : 1)]; + paths.toArray(entries[0]); + if (defaultOutput != null) entries[0][pathSize] = defaultOutput; // ensure output is last item + + paths.clear(); + list = cpElement.getElementsByTagName(ClasspathEntry.TAG_REFERENCED_ENTRY); + length = list.getLength(); + + for (int i = 0; i < length; ++i) { + Node node = list.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + IClasspathEntry entry = ClasspathEntry.elementDecode((Element)node, this, unknownElements); + if (entry != null){ + paths.add(entry); + } } } - // return a new empty classpath is it size is 0, to differenciate from an INVALID_CLASSPATH - int pathSize = paths.size(); - IClasspathEntry[] entries = new IClasspathEntry[pathSize + (defaultOutput == null ? 0 : 1)]; - paths.toArray(entries); - if (defaultOutput != null) entries[pathSize] = defaultOutput; // ensure output is last item + entries[1] = new IClasspathEntry[paths.size()]; + paths.toArray(entries[1]); + return entries; } @@ -911,7 +944,7 @@ reader.close(); } - if (!node.getNodeName().equalsIgnoreCase("classpathentry") //$NON-NLS-1$ + if (!node.getNodeName().equalsIgnoreCase(ClasspathEntry.TAG_CLASSPATHENTRY) || node.getNodeType() != Node.ELEMENT_NODE) { return null; } @@ -956,7 +989,7 @@ /** * Returns the XML String encoding of the class path. */ - protected String encodeClasspath(IClasspathEntry[] classpath, IPath outputLocation, boolean indent, Map unknownElements) throws JavaModelException { + protected String encodeClasspath(IClasspathEntry[] classpath, IClasspathEntry[] referencedEntries, IPath outputLocation, boolean indent, Map unknownElements) throws JavaModelException { try { ByteArrayOutputStream s = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$ @@ -964,7 +997,7 @@ xmlWriter.startTag(ClasspathEntry.TAG_CLASSPATH, indent); for (int i = 0; i < classpath.length; ++i) { - ((ClasspathEntry)classpath[i]).elementEncode(xmlWriter, this.project.getFullPath(), indent, true, unknownElements); + ((ClasspathEntry)classpath[i]).elementEncode(xmlWriter, this.project.getFullPath(), indent, true, unknownElements, false); } if (outputLocation != null) { @@ -976,6 +1009,12 @@ xmlWriter.printTag(ClasspathEntry.TAG_CLASSPATHENTRY, parameters, indent, true, true); } + if (referencedEntries != null) { + for (int i = 0; i < referencedEntries.length; ++i) { + ((ClasspathEntry) referencedEntries[i]).elementEncode(xmlWriter, this.project.getFullPath(), indent, true, unknownElements, true); + } + } + xmlWriter.endTag(ClasspathEntry.TAG_CLASSPATH, indent, true/*insert new line*/); writer.flush(); writer.close(); @@ -991,7 +1030,7 @@ OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$ XMLWriter xmlWriter = new XMLWriter(writer, this, false/*don't print XML version*/); - ((ClasspathEntry)classpathEntry).elementEncode(xmlWriter, this.project.getFullPath(), true/*indent*/, true/*insert new line*/, null/*not interested in unknown elements*/); + ((ClasspathEntry)classpathEntry).elementEncode(xmlWriter, this.project.getFullPath(), true/*indent*/, true/*insert new line*/, null/*not interested in unknown elements*/, (classpathEntry.getReferencingEntry() != null)); writer.flush(); writer.close(); @@ -1841,7 +1880,7 @@ IClasspathEntry[] classpath = perProjectInfo.rawClasspath; if (classpath != null) return classpath; - classpath = perProjectInfo.readAndCacheClasspath(this); + classpath = perProjectInfo.readAndCacheClasspath(this)[0]; if (classpath == JavaProject.INVALID_CLASSPATH) return defaultClasspath(); @@ -1850,6 +1889,13 @@ } /** + * @see IJavaProject + */ + public IClasspathEntry[] getReferencedClasspathEntries() throws JavaModelException { + return getPerProjectInfo().referencedEntries; + } + + /** * @see IJavaProject#getRequiredProjectNames() */ public String[] getRequiredProjectNames() throws JavaModelException { @@ -2384,7 +2430,7 @@ * As a side effect, unknown elements are stored in the given map (if not null) * Throws exceptions if the file cannot be accessed or is malformed. */ - public IClasspathEntry[] readFileEntriesWithException(Map unknownElements) throws CoreException, IOException, ClasspathEntry.AssertionFailedException { + public IClasspathEntry[][] readFileEntriesWithException(Map unknownElements) throws CoreException, IOException, ClasspathEntry.AssertionFailedException { IFile rscFile = this.project.getFile(JavaProject.CLASSPATH_FILENAME); byte[] bytes; if (rscFile.exists()) { @@ -2403,7 +2449,7 @@ bytes = org.eclipse.jdt.internal.compiler.util.Util.getFileByteContent(file); } catch (IOException e) { if (!file.exists()) - return defaultClasspath(); + return new IClasspathEntry[][]{defaultClasspath(), ClasspathEntry.NO_ENTRIES}; throw e; } } @@ -2427,18 +2473,18 @@ * This includes the output entry. * As a side effect, unknown elements are stored in the given map (if not null) */ - private IClasspathEntry[] readFileEntries(Map unkwownElements) { + private IClasspathEntry[][] readFileEntries(Map unkwownElements) { try { return readFileEntriesWithException(unkwownElements); } catch (CoreException e) { Util.log(e, "Exception while reading " + getPath().append(JavaProject.CLASSPATH_FILENAME)); //$NON-NLS-1$ - return JavaProject.INVALID_CLASSPATH; + return new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; } catch (IOException e) { Util.log(e, "Exception while reading " + getPath().append(JavaProject.CLASSPATH_FILENAME)); //$NON-NLS-1$ - return JavaProject.INVALID_CLASSPATH; + return new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; } catch (ClasspathEntry.AssertionFailedException e) { Util.log(e, "Exception while reading " + getPath().append(JavaProject.CLASSPATH_FILENAME)); //$NON-NLS-1$ - return JavaProject.INVALID_CLASSPATH; + return new IClasspathEntry[][]{JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES}; } } @@ -2447,14 +2493,14 @@ */ public IPath readOutputLocation() { // Read classpath file without creating markers nor logging problems - IClasspathEntry[] classpath = readFileEntries(null/*not interested in unknown elements*/); - if (classpath == JavaProject.INVALID_CLASSPATH) + IClasspathEntry[][] classpath = readFileEntries(null/*not interested in unknown elements*/); + if (classpath[0] == JavaProject.INVALID_CLASSPATH) return defaultOutputLocation(); // extract the output location IPath outputLocation = null; - if (classpath.length > 0) { - IClasspathEntry entry = classpath[classpath.length - 1]; + if (classpath[0].length > 0) { + IClasspathEntry entry = classpath[0][classpath[0].length - 1]; if (entry.getContentKind() == ClasspathEntry.K_OUTPUT) { outputLocation = entry.getPath(); } @@ -2467,20 +2513,20 @@ */ public IClasspathEntry[] readRawClasspath() { // Read classpath file without creating markers nor logging problems - IClasspathEntry[] classpath = readFileEntries(null/*not interested in unknown elements*/); - if (classpath == JavaProject.INVALID_CLASSPATH) + IClasspathEntry[][] classpath = readFileEntries(null/*not interested in unknown elements*/); + if (classpath[0] == JavaProject.INVALID_CLASSPATH) return defaultClasspath(); // discard the output location - if (classpath.length > 0) { - IClasspathEntry entry = classpath[classpath.length - 1]; + if (classpath[0].length > 0) { + IClasspathEntry entry = classpath[0][classpath[0].length - 1]; if (entry.getContentKind() == ClasspathEntry.K_OUTPUT) { - IClasspathEntry[] copy = new IClasspathEntry[classpath.length - 1]; - System.arraycopy(classpath, 0, copy, 0, copy.length); - classpath = copy; + IClasspathEntry[] copy = new IClasspathEntry[classpath[0].length - 1]; + System.arraycopy(classpath[0], 0, copy, 0, copy.length); + classpath[0] = copy; } } - return classpath; + return classpath[0]; } /** @@ -2533,14 +2579,45 @@ IJavaModelStatus unresolvedEntryStatus = JavaModelStatus.VERIFIED_OK; HashMap rawReverseMap = new HashMap(); Map rootPathToResolvedEntries = new HashMap(); + IClasspathEntry[] referencedEntries = null; } public ResolvedClasspath resolveClasspath(IClasspathEntry[] rawClasspath, boolean usePreviousSession, boolean resolveChainedLibraries) throws JavaModelException { + return resolveClasspath(rawClasspath, null, usePreviousSession, resolveChainedLibraries); + } + + public ResolvedClasspath resolveClasspath(IClasspathEntry[] rawClasspath, IClasspathEntry[] referencedEntries, boolean usePreviousSession, boolean resolveChainedLibraries) throws JavaModelException { JavaModelManager manager = JavaModelManager.getJavaModelManager(); ExternalFoldersManager externalFoldersManager = JavaModelManager.getExternalManager(); ResolvedClasspath result = new ResolvedClasspath(); + Map referencedEntriesMap = new HashMap(); + List rawLibrariesPath = new ArrayList(); LinkedHashSet resolvedEntries = new LinkedHashSet(); + + if(resolveChainedLibraries) { + for (int index = 0; index < rawClasspath.length; index++) { + IClasspathEntry currentEntry = rawClasspath[index]; + if (currentEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + rawLibrariesPath.add(ClasspathEntry.resolveDotDot(currentEntry.getPath())); + } + } + if (referencedEntries != null) { + // The Set is required to keep the order intact while the referencedEntriesMap (Map) + // is used to map the referenced entries with path + LinkedHashSet referencedEntriesSet = new LinkedHashSet(); + for (int index = 0; index < referencedEntries.length; index++) { + IPath path = referencedEntries[index].getPath(); + if (!rawLibrariesPath.contains(path) && referencedEntriesMap.get(path) == null) { + referencedEntriesMap.put(path, referencedEntries[index]); + referencedEntriesSet.add(referencedEntries[index]); + } + } + result.referencedEntries = new IClasspathEntry[referencedEntriesSet.size()]; + referencedEntriesSet.toArray(result.referencedEntries); + } + } + int length = rawClasspath.length; for (int i = 0; i < length; i++) { @@ -2561,14 +2638,19 @@ if (resolvedEntry == null) { result.unresolvedEntryStatus = new JavaModelStatus(IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND, this, rawEntry.getPath()); } else { - if (resolveChainedLibraries && resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + // If the entry is already present in the rawReversetMap, it means the entry and the chained libraries + // have already been processed. So, skip it. + if (resolveChainedLibraries && resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY + && result.rawReverseMap.get(resolvedEntry.getPath()) == null) { // resolve Class-Path: in manifest ClasspathEntry[] extraEntries = ((ClasspathEntry) resolvedEntry).resolvedChainedLibraries(); for (int j = 0, length2 = extraEntries.length; j < length2; j++) { - addToResult(rawEntry, extraEntries[j], result, resolvedEntries, externalFoldersManager); + if (!rawLibrariesPath.contains(extraEntries[j].getPath())) { + addToResult(rawEntry, extraEntries[j], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, true); + } } } - addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager); + addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false); } break; @@ -2603,15 +2685,17 @@ // resolve ".." in library path cEntry = cEntry.resolvedDotDot(); - if (resolveChainedLibraries) { + if (resolveChainedLibraries && result.rawReverseMap.get(cEntry.getPath()) == null) { // resolve Class-Path: in manifest ClasspathEntry[] extraEntries = cEntry.resolvedChainedLibraries(); for (int k = 0, length2 = extraEntries.length; k < length2; k++) { - addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager); + if (!rawLibrariesPath.contains(extraEntries[k].getPath())) { + addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, true); + } } } } - addToResult(rawEntry, cEntry, result, resolvedEntries, externalFoldersManager); + addToResult(rawEntry, cEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false); } break; @@ -2619,18 +2703,20 @@ // resolve ".." in library path resolvedEntry = ((ClasspathEntry) rawEntry).resolvedDotDot(); - if (resolveChainedLibraries) { + if (resolveChainedLibraries && result.rawReverseMap.get(resolvedEntry.getPath()) == null) { // resolve Class-Path: in manifest ClasspathEntry[] extraEntries = ((ClasspathEntry) resolvedEntry).resolvedChainedLibraries(); for (int k = 0, length2 = extraEntries.length; k < length2; k++) { - addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager); + if (!rawLibrariesPath.contains(extraEntries[k].getPath())) { + addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, true); + } } } - addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager); + addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false); break; default : - addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager); + addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false); break; } } @@ -2639,18 +2725,49 @@ return result; } - private void addToResult(IClasspathEntry rawEntry, IClasspathEntry resolvedEntry, ResolvedClasspath result, LinkedHashSet resolvedEntries, ExternalFoldersManager externalFoldersManager) { + private void addToResult(IClasspathEntry rawEntry, IClasspathEntry resolvedEntry, ResolvedClasspath result, + LinkedHashSet resolvedEntries, ExternalFoldersManager externalFoldersManager, + Map oldChainedEntriesMap, boolean addAsChainedEntry) { + IPath resolvedPath; + // If it's already been resolved, do not add to resolvedEntries or newChainedEntries lists if (result.rawReverseMap.get(resolvedPath = resolvedEntry.getPath()) == null) { result.rawReverseMap.put(resolvedPath, rawEntry); result.rootPathToResolvedEntries.put(resolvedPath, resolvedEntry); resolvedEntries.add(resolvedEntry); + if (addAsChainedEntry) { + IClasspathEntry chainedEntry = null; + if (rawEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + + chainedEntry = (ClasspathEntry) oldChainedEntriesMap.get(resolvedPath); + if (chainedEntry != null) { + // This is required to keep the attributes if any added by the user in + // the previous session such as source attachment path etc. + copyFromOldChainedEntry((ClasspathEntry) resolvedEntry, (ClasspathEntry) chainedEntry); + } + } + } } if (resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY && ExternalFoldersManager.isExternalFolderPath(resolvedPath)) { externalFoldersManager.addFolder(resolvedPath); // no-op if not an external folder or if already registered } } + private void copyFromOldChainedEntry(ClasspathEntry resolvedEntry, ClasspathEntry chainedEntry) { + IPath path = chainedEntry.getSourceAttachmentPath(); + if ( path != null) { + resolvedEntry.sourceAttachmentPath = path; + } + path = chainedEntry.getSourceAttachmentRootPath(); + if (path != null) { + resolvedEntry.sourceAttachmentRootPath = path; + } + IClasspathAttribute[] attributes = chainedEntry.getExtraAttributes(); + if (attributes != null) { + resolvedEntry.extraAttributes = attributes; + } + } + /* * Resolve the given perProjectInfo's raw classpath and store the resolved classpath in the perProjectInfo. */ @@ -2665,22 +2782,24 @@ } // get raw info inside a synchronized block to ensure that it is consistent - IClasspathEntry[] rawClasspath; + IClasspathEntry[][] classpath = new IClasspathEntry[2][]; int timeStamp; synchronized (perProjectInfo) { - rawClasspath= perProjectInfo.rawClasspath; - if (rawClasspath == null) - rawClasspath = perProjectInfo.readAndCacheClasspath(this); + classpath[0] = perProjectInfo.rawClasspath; + classpath[1] = perProjectInfo.referencedEntries; + // Checking null only for rawClasspath enough + if (classpath[0] == null) + classpath = perProjectInfo.readAndCacheClasspath(this); timeStamp = perProjectInfo.rawTimeStamp; } - - ResolvedClasspath result = resolveClasspath(rawClasspath, usePreviousSession, true/*resolve chained libraries*/); + ResolvedClasspath result = resolveClasspath(classpath[0], classpath[1], usePreviousSession, true/*resolve chained libraries*/); + if (CP_RESOLUTION_BP_LISTENERS != null) breakpoint(2, this); // store resolved info along with the raw info to ensure consistency - perProjectInfo.setResolvedClasspath(result.resolvedClasspath, result.rawReverseMap, result.rootPathToResolvedEntries, usePreviousSession ? PerProjectInfo.NEED_RESOLUTION : result.unresolvedEntryStatus, timeStamp, addClasspathChange); + perProjectInfo.setResolvedClasspath(result.resolvedClasspath, result.referencedEntries, result.rawReverseMap, result.rootPathToResolvedEntries, usePreviousSession ? PerProjectInfo.NEED_RESOLUTION : result.unresolvedEntryStatus, timeStamp, addClasspathChange); } finally { if (!isClasspathBeingResolved) { manager.setClasspathBeingResolved(this, false); @@ -2708,25 +2827,30 @@ * @return boolean Return whether the .classpath file was modified. * @throws JavaModelException */ - public boolean writeFileEntries(IClasspathEntry[] newClasspath, IPath newOutputLocation) throws JavaModelException { + public boolean writeFileEntries(IClasspathEntry[] newClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation) throws JavaModelException { if (!this.project.isAccessible()) return false; Map unknownElements = new HashMap(); - IClasspathEntry[] fileEntries = readFileEntries(unknownElements); - if (fileEntries != JavaProject.INVALID_CLASSPATH && areClasspathsEqual(newClasspath, newOutputLocation, fileEntries)) { + IClasspathEntry[][] fileEntries = readFileEntries(unknownElements); + if (fileEntries[0] != JavaProject.INVALID_CLASSPATH && + areClasspathsEqual(newClasspath, newOutputLocation, fileEntries[0]) + && areClasspathEqual(referencedEntries, fileEntries[1])) { // no need to save it, it is the same return false; } // actual file saving try { - setSharedProperty(JavaProject.CLASSPATH_FILENAME, encodeClasspath(newClasspath, newOutputLocation, true, unknownElements)); + setSharedProperty(JavaProject.CLASSPATH_FILENAME, encodeClasspath(newClasspath, referencedEntries, newOutputLocation, true, unknownElements)); return true; } catch (CoreException e) { throw new JavaModelException(e); } } + public boolean writeFileEntries(IClasspathEntry[] newClasspath, IPath newOutputLocation) throws JavaModelException { + return writeFileEntries(newClasspath, ClasspathEntry.NO_ENTRIES, newOutputLocation); + } /** * Update the Java command in the build spec (replace existing one if present, @@ -2874,22 +2998,7 @@ boolean canModifyResources, IProgressMonitor monitor) throws JavaModelException { - - try { - if (newRawClasspath == null) //are we already with the default classpath - newRawClasspath = defaultClasspath(); - - SetClasspathOperation op = - new SetClasspathOperation( - this, - newRawClasspath, - newOutputLocation, - canModifyResources); - op.runOperation(monitor); - } catch (JavaModelException e) { - JavaModelManager.getJavaModelManager().getDeltaProcessor().flush(); - throw e; - } + setRawClasspath(newRawClasspath, null, newOutputLocation, canModifyResources, monitor); } /** @@ -2907,6 +3016,32 @@ true/*can change resource (as per API contract)*/, monitor); } + + public void setRawClasspath(IClasspathEntry[] entries, IClasspathEntry[] referencedEntries, IPath outputLocation, + IProgressMonitor monitor) throws JavaModelException { + setRawClasspath(entries, referencedEntries, outputLocation, true, monitor); + } + + protected void setRawClasspath(IClasspathEntry[] newRawClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation, + boolean canModifyResources, IProgressMonitor monitor) throws JavaModelException { + + try { + if (newRawClasspath == null) //are we already with the default classpath + newRawClasspath = defaultClasspath(); + + SetClasspathOperation op = + new SetClasspathOperation( + this, + newRawClasspath, + referencedEntries, + newOutputLocation, + canModifyResources); + op.runOperation(monitor); + } catch (JavaModelException e) { + JavaModelManager.getJavaModelManager().getDeltaProcessor().flush(); + throw e; + } + } /** * @see IJavaProject Index: model/org/eclipse/jdt/internal/core/PackageFragmentRoot.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/PackageFragmentRoot.java,v retrieving revision 1.134 diff -u -r1.134 PackageFragmentRoot.java --- model/org/eclipse/jdt/internal/core/PackageFragmentRoot.java 25 Nov 2008 14:38:15 -0000 1.134 +++ model/org/eclipse/jdt/internal/core/PackageFragmentRoot.java 26 Feb 2010 19:38:00 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -554,6 +554,22 @@ } return rawEntry; } +/* + * @see IPackageFragmentRoot + */ +public IClasspathEntry getResolvedClasspathEntry() throws JavaModelException { + IClasspathEntry resolvedEntry = null; + JavaProject project = (JavaProject)getJavaProject(); + project.getResolvedClasspath(); // force the reverse rawEntry cache to be populated + Map rootPathToResolvedEntries = project.getPerProjectInfo().rootPathToResolvedEntries; + if (rootPathToResolvedEntries != null) { + resolvedEntry = (IClasspathEntry) rootPathToResolvedEntries.get(getPath()); + } + if (resolvedEntry == null) { + throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH, this)); + } + return resolvedEntry; +} public IResource resource() { Index: model/org/eclipse/jdt/internal/core/SetClasspathOperation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SetClasspathOperation.java,v retrieving revision 1.156 diff -u -r1.156 SetClasspathOperation.java --- model/org/eclipse/jdt/internal/core/SetClasspathOperation.java 10 Oct 2008 09:48:05 -0000 1.156 +++ model/org/eclipse/jdt/internal/core/SetClasspathOperation.java 26 Feb 2010 19:38:00 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2010 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 @@ -30,21 +30,33 @@ public class SetClasspathOperation extends ChangeClasspathOperation { IClasspathEntry[] newRawClasspath; + IClasspathEntry[] referencedEntries; IPath newOutputLocation; JavaProject project; + public SetClasspathOperation( + JavaProject project, + IClasspathEntry[] newRawClasspath, + IPath newOutputLocation, + boolean canChangeResource) { + + this(project, newRawClasspath, null, newOutputLocation, canChangeResource); + } + /** * When executed, this operation sets the raw classpath and output location of the given project. */ public SetClasspathOperation( JavaProject project, IClasspathEntry[] newRawClasspath, + IClasspathEntry[] referencedEntries, IPath newOutputLocation, boolean canChangeResource) { super(new IJavaElement[] { project }, canChangeResource); this.project = project; this.newRawClasspath = newRawClasspath; + this.referencedEntries = referencedEntries; this.newOutputLocation = newOutputLocation; } @@ -56,7 +68,7 @@ try { // set raw classpath and null out resolved info PerProjectInfo perProjectInfo = this.project.getPerProjectInfo(); - ClasspathChange classpathChange = perProjectInfo.setRawClasspath(this.newRawClasspath, this.newOutputLocation, JavaModelStatus.VERIFIED_OK/*format is ok*/); + ClasspathChange classpathChange = perProjectInfo.setRawClasspath(this.newRawClasspath, this.referencedEntries, this.newOutputLocation, JavaModelStatus.VERIFIED_OK/*format is ok*/); // if needed, generate delta, update project ref, create markers, ... classpathChanged(classpathChange); #P org.eclipse.jdt.core.tests.model Index: src/org/eclipse/jdt/core/tests/model/ClasspathTests.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ClasspathTests.java,v retrieving revision 1.207 diff -u -r1.207 ClasspathTests.java --- src/org/eclipse/jdt/core/tests/model/ClasspathTests.java 22 Feb 2010 07:50:08 -0000 1.207 +++ src/org/eclipse/jdt/core/tests/model/ClasspathTests.java 26 Feb 2010 19:38:01 -0000 @@ -88,8 +88,8 @@ // All specified tests which do not belong to the class are skipped... static { // Names of tests to run: can be "testBugXXXX" or "BugXXXX") -// TESTS_PREFIX = "testClasspathDuplicateExtraAttribute"; -// TESTS_NAMES = new String[] {"testClasspathValidation42"}; +// TESTS_PREFIX = "testBug252341c"; +// TESTS_NAMES = new String[] {"testClasspathWithNonExistentLibraryEntry"}; // TESTS_NUMBERS = new int[] { 23, 28, 38 }; // TESTS_RANGE = new int[] { 21, 38 }; } @@ -6116,5 +6116,316 @@ deleteExternalResource("lib.jar"); } } +/** + * @bug 252431:New API is needed to better identify referenced jars in the Class-Path: entry + * Test that 1) referenced libraries are added to the resolved classpath in the right order + * 2) referenced libraries are added to the appropriate referencing library in the correct order + * 3) referenced libraries and top-level libraries retain the source attachment and source attachment root path + * 4) referenced libraries point to the correct entry as their referencingEntry. + * 5) referenced libraries and their attributes are persisted in the .classpath file + * + * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=252431" + * @throws Exception + */ +public void testBug252341a() throws Exception { + try { + IJavaProject p = createJavaProject("P"); + addLibrary(p, "lib1.jar", "abc.zip", new String[0], + new String[] { + "META-INF/MANIFEST.MF", + "Manifest-Version: 1.0\n" + + "Class-Path: lib2.jar lib3.jar\n", + }, + JavaCore.VERSION_1_4); + createFile("/P/lib2.jar", ""); + createFile("/P/lib3.jar", ""); + + // Test referenced entries are included in the right order in the resolved classpath + IClasspathEntry[] resolvedClasspath = p.getResolvedClasspath(true); + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/abc.zip][isExported:true]"); + + IClasspathEntry[] rawClasspath = p.getRawClasspath(); + assertClasspathEquals(rawClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + "JCL_LIB[CPE_VARIABLE][K_SOURCE][isExported:false]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/abc.zip][isExported:true]"); + + // Test referenced entries for a particular entry appear in the right order and the referencingEntry + // attribute has the correct value + IClasspathEntry[] chains = JavaCore.getReferencedClasspathEntries(rawClasspath[2], p); + assertClasspathEquals(chains, + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + assertSame("Referencing Entry", rawClasspath[2], chains[0].getReferencingEntry()); + assertSame("Referencing Entry", rawClasspath[2], chains[1].getReferencingEntry()); + + // Test a newly created library entry with similar attributes but without any referencing entry is equal to + // the original referenced entry + IClasspathEntry tempLibEntry = JavaCore.newLibraryEntry(chains[0].getPath(), chains[0].getSourceAttachmentPath(), chains[0].getSourceAttachmentRootPath(), true); + assertEquals("Library Entry", tempLibEntry, chains[0]); + + // Test the source attachment and other attributes added to the referenced entries are stored and retrieved properly + assertEquals("source attachment", resolvedClasspath[4].getSourceAttachmentPath().toPortableString(), "/P/abc.zip"); + assertNull("source attachment", chains[0].getSourceAttachmentPath()); + assertNull("source attachment", chains[1].getSourceAttachmentPath()); + assertNull("source attachment root", chains[0].getSourceAttachmentRootPath()); + assertNull("source attachment root", chains[1].getSourceAttachmentRootPath()); + + ((ClasspathEntry)chains[0]).sourceAttachmentPath = new Path("/P/efg.zip"); + ((ClasspathEntry)chains[1]).sourceAttachmentPath = new Path("/P/xyz.zip"); + ((ClasspathEntry)chains[0]).sourceAttachmentRootPath = new Path("/src2"); + ((ClasspathEntry)chains[1]).sourceAttachmentRootPath = new Path("/src3"); + + IClasspathAttribute javadocLoc = JavaCore.newClasspathAttribute("javadoc_location", "/P/efg.zip"); + ((ClasspathEntry)chains[0]).extraAttributes = new IClasspathAttribute[]{javadocLoc}; + + p.setRawClasspath(rawClasspath, chains, p.getOutputLocation(), null); + + // Test the .classpath file contains all the referenced entries and their attributes + String contents = new String (org.eclipse.jdt.internal.core.util.Util.getResourceContentsAsCharArray(getFile("/P/.classpath"))); + assertSourceEquals( + "Unexpected content", + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n", + contents); + + p.close(); + p.open(null); + rawClasspath = p.getRawClasspath(); + resolvedClasspath = p.getResolvedClasspath(true); + + assertClasspathEquals(rawClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + "JCL_LIB[CPE_VARIABLE][K_SOURCE][isExported:false]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/abc.zip][isExported:true]"); + + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/efg.zip][rootPath:/src2][isExported:true][attributes:javadoc_location=/P/efg.zip]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/xyz.zip][rootPath:/src3][isExported:true]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/abc.zip][isExported:true]"); + + } finally { + deleteProject("P"); + } +} +/** + * Additional tests for bug 252431. + * When multiple libraries have one or more common referenced library in their MANIFEST + * 1) The common referenced libries are added to the first library entry in the raw classpath + * 2) Removing one of the top-level library from the raw classpath doesn't remove the referenced + * entry that was commonly referenced by another entry and the referenced entry's source + * attachment and other attributes are retained. + * 3) Passing a NULL referencedEntries array retains the referenced entries + * 4) Passing an empty array as referencedEntries clears the earlier referenced entries + * + * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=252431" + * @throws Exception + */ +public void testBug252341b() throws Exception { + try { + IJavaProject p = createJavaProject("P"); + addLibrary(p, "lib1.jar", null, new String[0], + new String[] { + "META-INF/MANIFEST.MF", + "Manifest-Version: 1.0\n" + + "Class-Path: lib3.jar lib4.jar\n", + }, + JavaCore.VERSION_1_4); + + addLibrary(p, "lib2.jar", null, new String[0], + new String[] { + "META-INF/MANIFEST.MF", + "Manifest-Version: 1.0\n" + + "Class-Path: lib3.jar lib5.jar\n", + }, + JavaCore.VERSION_1_4); + createFile("/P/lib3.jar", ""); + createFile("/P/lib4.jar", ""); + createFile("/P/lib5.jar", ""); + + // Test that the referenced entries are not included in the raw classpath + IClasspathEntry[] rawClasspath = p.getRawClasspath(); + assertClasspathEquals( + rawClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + "JCL_LIB[CPE_VARIABLE][K_SOURCE][isExported:false]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + IClasspathEntry[] rawEntries = new IClasspathEntry[2]; + rawEntries[0] = JavaCore.newLibraryEntry(new Path("/P/lib1.jar"), null, null, true); + rawEntries[1] = JavaCore.newLibraryEntry(new Path("/P/lib2.jar"), null, null, true); + + // Test that the referenced entries are included in the raw classpath and in the right order + IClasspathEntry[] resolvedClasspath = p.getResolvedClasspath(true); + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib4.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + // Test that the referenced classpath entries has the appropriate referencingEntry value + IClasspathEntry[] chains = JavaCore.getReferencedClasspathEntries(rawEntries[0], p); + assertClasspathEquals( + chains, + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib4.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + assertEquals("Referencing Entry" , rawEntries[0], chains[0].getReferencingEntry()); + assertEquals("Referencing Entry" , rawEntries[0], chains[1].getReferencingEntry()); + + chains = JavaCore.getReferencedClasspathEntries(rawEntries[1], p); + assertClasspathEquals( + chains, + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + assertEquals("Referencing Entry" , rawEntries[0], chains[0].getReferencingEntry()); + assertEquals("Referencing Entry" , rawEntries[1], chains[1].getReferencingEntry()); + +// // Test IPackageFragmentRoot#getResolvedClasspathEntry + IPackageFragmentRoot[] roots = p.getPackageFragmentRoots(); + assertEquals("Package fragment root", roots[2].getResolvedClasspathEntry(), resolvedClasspath[2]); + assertEquals("Package fragment root", roots[3].getResolvedClasspathEntry(), resolvedClasspath[3]); + assertEquals("Package fragment root", roots[4].getResolvedClasspathEntry(), resolvedClasspath[4]); + assertEquals("Package fragment root", roots[5].getResolvedClasspathEntry(), resolvedClasspath[5]); + assertEquals("Package fragment root", roots[6].getResolvedClasspathEntry(), resolvedClasspath[6]); + + // Test the attributes added to the referenced classpath entries are stored and retrieved properly + ((ClasspathEntry)chains[0]).sourceAttachmentPath = new Path("/P/efg.zip"); + ((ClasspathEntry)chains[1]).sourceAttachmentPath = new Path("/P/xyz.zip"); + ((ClasspathEntry)chains[0]).sourceAttachmentRootPath = new Path("/src2"); + ((ClasspathEntry)chains[1]).sourceAttachmentRootPath = new Path("/src3"); + + IClasspathAttribute javadocLoc = JavaCore.newClasspathAttribute("javadoc_location", "/P/efg.zip"); + ((ClasspathEntry)chains[0]).extraAttributes = new IClasspathAttribute[]{javadocLoc}; + + p.setRawClasspath(rawClasspath, chains, p.getOutputLocation(), null); + resolvedClasspath = p.getResolvedClasspath(true); + + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/efg.zip][rootPath:/src2][isExported:true][attributes:javadoc_location=/P/efg.zip]\n" + + "/P/lib4.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib1.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/xyz.zip][rootPath:/src3][isExported:true]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + // Test that removing any of the referencing entry from the raw classpath has the correct effect + // on the resolved classpath. Also test passing referencedEntries = null retains the existing + // referenced entries + IClasspathEntry[] newRawClasspath = new IClasspathEntry[rawClasspath.length-1]; + System.arraycopy(rawClasspath, 0, newRawClasspath, 0, 2); + System.arraycopy(rawClasspath, 3, newRawClasspath, 2, 1); + p.setRawClasspath(newRawClasspath, null, p.getOutputLocation(), null); + resolvedClasspath = p.getResolvedClasspath(true); + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/efg.zip][rootPath:/src2][isExported:true][attributes:javadoc_location=/P/efg.zip]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][sourcePath:/P/xyz.zip][rootPath:/src3][isExported:true]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + // Test that passing empty array of referencedEntries clears all the earlier ones. + p.setRawClasspath(newRawClasspath, new IClasspathEntry[]{}, p.getOutputLocation(), null); + resolvedClasspath = p.getResolvedClasspath(true); + assertClasspathEquals(resolvedClasspath, + "/P[CPE_SOURCE][K_SOURCE][isExported:false]\n" + + ""+ getExternalJCLPathString() + "[CPE_LIBRARY][K_BINARY][isExported:false]\n" + + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib2.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + + } finally { + deleteProject("P"); + } +} +/** + * Additional tests for bug 252431. + * Test that duplicate referenced entries or entries that are already present in the raw classpath + * are excluded from the referenced entries when invoking + * {@link IJavaProject#setRawClasspath(IClasspathEntry[], IClasspathEntry[], IPath, IProgressMonitor)} + * + * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=252431" + * @throws Exception + */ +public void testBug252341c() throws Exception { + try { + IJavaProject p = createJavaProject("P"); + addLibrary(p, "lib1.jar", null, new String[0], + new String[] { + "META-INF/MANIFEST.MF", + "Manifest-Version: 1.0\n" + + "Class-Path: lib3.jar lib4.jar\n", + }, + JavaCore.VERSION_1_4); + + addLibrary(p, "lib2.jar", null, new String[0], + new String[] { + "META-INF/MANIFEST.MF", + "Manifest-Version: 1.0\n" + + "Class-Path: lib3.jar lib5.jar\n", + }, + JavaCore.VERSION_1_4); + createFile("/P/lib3.jar", ""); + createFile("/P/lib4.jar", ""); + createFile("/P/lib5.jar", ""); + + IClasspathEntry[] rawClasspath = p.getRawClasspath(); + + IClasspathEntry[] rawEntries = new IClasspathEntry[2]; + rawEntries[0] = JavaCore.newLibraryEntry(new Path("/P/lib1.jar"), null, null, true); + rawEntries[1] = JavaCore.newLibraryEntry(new Path("/P/lib2.jar"), null, null, true); + + // Test that the referenced classpath entries has the appropriate referencingEntry value + IClasspathEntry[] chains = JavaCore.getReferencedClasspathEntries(rawEntries[0], p); + + IClasspathEntry[] referencedEntries = new IClasspathEntry[5]; + referencedEntries[0] = chains[0]; + referencedEntries[1] = chains[1]; + + chains = JavaCore.getReferencedClasspathEntries(rawEntries[1], p); + + referencedEntries[2] = chains[0]; + referencedEntries[3] = chains[1]; + referencedEntries[4] = chains[1]; + + p.setRawClasspath(rawClasspath, referencedEntries, p.getOutputLocation(), null); + + p.close(); + p.open(null); + + IClasspathEntry[] storedReferencedEnties = p.getReferencedClasspathEntries(); + assertClasspathEquals(storedReferencedEnties, + "/P/lib3.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib4.jar[CPE_LIBRARY][K_BINARY][isExported:true]\n" + + "/P/lib5.jar[CPE_LIBRARY][K_BINARY][isExported:true]"); + } + finally { + deleteProject("P"); + } +} }