/******************************************************************************* * Copyright (c) 2000, 2015 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.jdt.launching; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jdt.internal.launching.DefaultProjectClasspathEntry; import org.eclipse.jdt.internal.launching.VariableClasspathEntry; /** * Default implementation of source lookup path computation and resolution. * <p> * This class may be sub-classed. * </p> * @since 2.0 */ public class StandardSourcePathProvider extends StandardClasspathProvider { /* (non-Javadoc) * @see org.eclipse.jdt.launching.IRuntimeClasspathProvider#computeUnresolvedClasspath(org.eclipse.debug.core.ILaunchConfiguration) */ @Override public IRuntimeClasspathEntry[] computeUnresolvedClasspath(ILaunchConfiguration configuration) throws CoreException { boolean useDefault = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_SOURCE_PATH, true); IRuntimeClasspathEntry[] entries = null; if (useDefault) { // the default source lookup path is the same as the classpath entries = super.computeUnresolvedClasspath(configuration); } else { // recover persisted source path entries = recoverRuntimePath(configuration, IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH); } return entries; } /* (non-Javadoc) * @see org.eclipse.jdt.launching.IRuntimeClasspathProvider#resolveClasspath(org.eclipse.jdt.launching.IRuntimeClasspathEntry[], org.eclipse.debug.core.ILaunchConfiguration) */ @Override public IRuntimeClasspathEntry[] resolveClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException { List<IRuntimeClasspathEntry> all = new UniqueList(entries.length); for (int i = 0; i < entries.length; i++) { switch (entries[i].getType()) { case IRuntimeClasspathEntry.PROJECT: // a project resolves to itself for source lookup (rather than the class file output locations) all.add(entries[i]); break; case IRuntimeClasspathEntry.OTHER: IRuntimeClasspathEntry2 entry = (IRuntimeClasspathEntry2)entries[i]; String typeId = entry.getTypeId(); IRuntimeClasspathEntry[] res = null; if (typeId.equals(DefaultProjectClasspathEntry.TYPE_ID)) { // add the resolved children of the project IRuntimeClasspathEntry[] children = entry.getRuntimeClasspathEntries(configuration); res = JavaRuntime.resolveSourceLookupPath(children, configuration); } else if (typeId.equals(VariableClasspathEntry.TYPE_ID)) { // add the archive itself - we currently do not allow a source attachment res = JavaRuntime.resolveRuntimeClasspathEntry(entry, configuration); } else { res = JavaRuntime.resolveRuntimeClasspathEntry(entry, configuration); } if (res != null) { for (int j = 0; j < res.length; j++) { all.add(res[j]); addManifestReferences(res[j], all); } } break; default: IRuntimeClasspathEntry[] resolved =JavaRuntime.resolveRuntimeClasspathEntry(entries[i], configuration); for (int j = 0; j < resolved.length; j++) { all.add(resolved[j]); addManifestReferences(resolved[j], all); } break; } } return all.toArray(new IRuntimeClasspathEntry[all.size()]); } /** * If the given entry is an archive, adds any archives referenced by the associated manifest. * * @param entry runtime classpath entry * @param all list to add references to */ protected void addManifestReferences(IRuntimeClasspathEntry entry, List<IRuntimeClasspathEntry> all) { if (entry.getType() == IRuntimeClasspathEntry.ARCHIVE) { String location = entry.getLocation(); if (location != null) { try (JarFile jar = new JarFile(location, false);) { Manifest manifest = jar.getManifest(); if (manifest != null) { Attributes mainAttributes = manifest.getMainAttributes(); if (mainAttributes != null) { String value = mainAttributes.getValue(Attributes.Name.CLASS_PATH); if (value != null) { String[] entries = value.split("\\s+"); //$NON-NLS-1$ IPath base = new Path(location); base = base.removeLastSegments(1); for (int i = 0; i < entries.length; i++) { IPath path = base.append(entries[i]); if (path.toFile().exists()) { IRuntimeClasspathEntry ref = JavaRuntime.newArchiveRuntimeClasspathEntry(path); if (!all.contains(ref)) { all.add(ref); } } } } } } } catch (IOException e) { } } } } /* * An ArrayList that acts like a set -i.e. does not allow duplicate items. * hack for bug 112774 */ class UniqueList extends ArrayList<IRuntimeClasspathEntry> { private static final long serialVersionUID = -7402160651027036270L; HashSet<IRuntimeClasspathEntry> set; public UniqueList(int length) { super(length); set = new HashSet<>(length); } @Override public void add(int index, IRuntimeClasspathEntry element) { if (set.add(element)) { super.add(index, element); } } @Override public boolean add(IRuntimeClasspathEntry o) { if (set.add(o)) { return super.add(o); } return false; } @Override public boolean addAll(Collection<? extends IRuntimeClasspathEntry> c) { if (set.addAll(c)) { return super.addAll(c); } return false; } @Override public boolean addAll(int index, Collection<? extends IRuntimeClasspathEntry> c) { if (set.addAll(c)) { return super.addAll(index, c); } return false; } @Override public void clear() { set.clear(); super.clear(); } @Override public boolean contains(Object elem) { return set.contains(elem); } @Override public void ensureCapacity(int minCapacity) { super.ensureCapacity(minCapacity); } @Override public IRuntimeClasspathEntry remove(int index) { IRuntimeClasspathEntry object = super.remove(index); set.remove(object); return object; } @Override protected void removeRange(int fromIndex, int toIndex) { for (int index = fromIndex; index<=toIndex; index++) { remove(index); } } @Override public IRuntimeClasspathEntry set(int index, IRuntimeClasspathEntry element) { set.remove(element); if (set.add(element)) { return super.set(index, element); } return null; //should not happen. } } }