| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2007, 2009 IBM Corporation and others. |
| 3 | * All rights reserved. This program and the accompanying materials |
| 4 | * are made available under the terms of the Eclipse Public License v1.0 |
| 5 | * which accompanies this distribution, and is available at |
| 6 | * http://www.eclipse.org/legal/epl-v10.html |
| 7 | * |
| 8 | * Contributors: |
| 9 | * IBM Corporation - initial API and implementation |
| 10 | *******************************************************************************/ |
| 11 | package org.eclipse.pde.api.tools.internal.model; |
| 12 | |
| 13 | import java.io.IOException; |
| 14 | import java.util.ArrayList; |
| 15 | import java.util.Collections; |
| 16 | import java.util.HashMap; |
| 17 | import java.util.List; |
| 18 | import java.util.Map; |
| 19 | |
| 20 | import org.eclipse.core.resources.IContainer; |
| 21 | import org.eclipse.core.resources.IFile; |
| 22 | import org.eclipse.core.resources.IProject; |
| 23 | import org.eclipse.core.resources.IResource; |
| 24 | import org.eclipse.core.resources.ResourcesPlugin; |
| 25 | import org.eclipse.core.runtime.CoreException; |
| 26 | import org.eclipse.core.runtime.IPath; |
| 27 | import org.eclipse.core.runtime.Path; |
| 28 | import org.eclipse.jdt.core.IClasspathEntry; |
| 29 | import org.eclipse.jdt.core.IJavaProject; |
| 30 | import org.eclipse.jdt.core.IPackageFragmentRoot; |
| 31 | import org.eclipse.jdt.core.JavaCore; |
| 32 | import org.eclipse.pde.api.tools.internal.ApiDescriptionManager; |
| 33 | import org.eclipse.pde.api.tools.internal.ApiFilterStore; |
| 34 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
| 35 | import org.eclipse.pde.api.tools.internal.provisional.IApiDescription; |
| 36 | import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore; |
| 37 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; |
| 38 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
| 39 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer; |
| 40 | import org.eclipse.pde.api.tools.internal.util.Util; |
| 41 | import org.eclipse.pde.core.build.IBuild; |
| 42 | import org.eclipse.pde.core.build.IBuildEntry; |
| 43 | import org.eclipse.pde.core.plugin.IPluginModelBase; |
| 44 | import org.eclipse.pde.internal.core.build.WorkspaceBuildModel; |
| 45 | |
| 46 | /** |
| 47 | * An API component for a plug-in project in the workspace. |
| 48 | * <p> |
| 49 | * Note: this class requires a running workspace to be instantiated. |
| 50 | * </p> |
| 51 | * @since 1.0.0 |
| 52 | */ |
| 53 | public class PluginProjectApiComponent extends BundleApiComponent { |
| 54 | |
| 55 | /** |
| 56 | * Constant used for controlling tracing in the plug-in workspace component |
| 57 | */ |
| 58 | private static boolean DEBUG = Util.DEBUG; |
| 59 | |
| 60 | /** |
| 61 | * Method used for initializing tracing in the plug-in workspace component |
| 62 | */ |
| 63 | public static void setDebug(boolean debugValue) { |
| 64 | DEBUG = debugValue || Util.DEBUG; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Associated Java project |
| 69 | */ |
| 70 | private IJavaProject fProject = null; |
| 71 | |
| 72 | /** |
| 73 | * Associated IPluginModelBase object |
| 74 | */ |
| 75 | private IPluginModelBase fModel = null; |
| 76 | |
| 77 | /** |
| 78 | * A cache of bundle class path entries to class file containers. |
| 79 | */ |
| 80 | private Map fPathToOutputContainers = null; |
| 81 | |
| 82 | /** |
| 83 | * A cache of output location paths to corresponding class file containers. |
| 84 | */ |
| 85 | private Map fOutputLocationToContainer = null; |
| 86 | |
| 87 | /** |
| 88 | * Constructs an API component for the given Java project in the specified profile. |
| 89 | * |
| 90 | * @param profile the owning profile |
| 91 | * @param location the given location of the component |
| 92 | * @param model the given model |
| 93 | * @param project java project |
| 94 | * @throws CoreException if unable to create the API component |
| 95 | */ |
| 96 | public PluginProjectApiComponent(IApiBaseline profile, String location, IPluginModelBase model) throws CoreException { |
| 97 | super(profile, location); |
| 98 | IPath path = new Path(location); |
| 99 | IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment()); |
| 100 | this.fProject = JavaCore.create(project); |
| 101 | this.fModel = model; |
| 102 | setName(fModel.getResourceString(super.getName())); |
| 103 | } |
| 104 | |
| 105 | /* (non-Javadoc) |
| 106 | * @see org.eclipse.pde.api.tools.internal.BundleApiComponent#isBinaryBundle() |
| 107 | */ |
| 108 | protected boolean isBinaryBundle() { |
| 109 | return false; |
| 110 | } |
| 111 | |
| 112 | /* (non-Javadoc) |
| 113 | * @see org.eclipse.pde.api.tools.internal.BundleApiComponent#isApiEnabled() |
| 114 | */ |
| 115 | protected boolean isApiEnabled() { |
| 116 | return Util.isApiProject(fProject); |
| 117 | } |
| 118 | |
| 119 | /* (non-Javadoc) |
| 120 | * @see org.eclipse.pde.api.tools.internal.descriptors.AbstractApiComponent#dispose() |
| 121 | */ |
| 122 | public void dispose() { |
| 123 | try { |
| 124 | if(hasApiFilterStore()) { |
| 125 | getFilterStore().dispose(); |
| 126 | } |
| 127 | fModel = null; |
| 128 | if (fOutputLocationToContainer != null) { |
| 129 | fOutputLocationToContainer.clear(); |
| 130 | fOutputLocationToContainer = null; |
| 131 | } |
| 132 | if (fPathToOutputContainers != null) { |
| 133 | fPathToOutputContainers.clear(); |
| 134 | fPathToOutputContainers = null; |
| 135 | } |
| 136 | } |
| 137 | catch(CoreException ce) { |
| 138 | ApiPlugin.log(ce); |
| 139 | } |
| 140 | finally { |
| 141 | super.dispose(); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /* (non-Javadoc) |
| 146 | * @see org.eclipse.pde.api.tools.internal.BundleApiComponent#createLocalApiDescription() |
| 147 | */ |
| 148 | protected IApiDescription createLocalApiDescription() throws CoreException { |
| 149 | long time = System.currentTimeMillis(); |
| 150 | if(Util.isApiProject(getJavaProject())) { |
| 151 | setHasApiDescription(true); |
| 152 | } |
| 153 | IApiDescription apiDesc = ApiDescriptionManager.getDefault().getApiDescription(this, getBundleDescription()); |
| 154 | if (DEBUG) { |
| 155 | System.out.println("Time to create api description for: ["+fProject.getElementName()+"] " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| 156 | } |
| 157 | return apiDesc; |
| 158 | } |
| 159 | |
| 160 | /* (non-Javadoc) |
| 161 | * @see org.eclipse.pde.api.tools.internal.BundleApiComponent#createApiFilterStore() |
| 162 | */ |
| 163 | protected IApiFilterStore createApiFilterStore() throws CoreException { |
| 164 | long time = System.currentTimeMillis(); |
| 165 | IApiFilterStore store = new ApiFilterStore(getJavaProject()); |
| 166 | if (DEBUG) { |
| 167 | System.out.println("Time to create api filter store for: ["+fProject.getElementName()+"] " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| 168 | } |
| 169 | return store; |
| 170 | } |
| 171 | |
| 172 | /* (non-Javadoc) |
| 173 | * @see org.eclipse.pde.api.tools.internal.descriptors.BundleApiComponent#createClassFileContainers() |
| 174 | */ |
| 175 | protected synchronized List createApiTypeContainers() throws CoreException { |
| 176 | // first populate build.properties cache so we can create class file containers |
| 177 | // from bundle classpath entries |
| 178 | fPathToOutputContainers = new HashMap(4); |
| 179 | fOutputLocationToContainer = new HashMap(4); |
| 180 | if (fProject.exists() && fProject.getProject().isOpen()) { |
| 181 | IFile prop = fProject.getProject().getFile("build.properties"); //$NON-NLS-1$ |
| 182 | if (prop.exists()) { |
| 183 | WorkspaceBuildModel properties = new WorkspaceBuildModel(prop); |
| 184 | IBuild build = properties.getBuild(); |
| 185 | IBuildEntry entry = build.getEntry("custom"); //$NON-NLS-1$ |
| 186 | if (entry != null) { |
| 187 | String[] tokens = entry.getTokens(); |
| 188 | if (tokens.length == 1 && tokens[0].equals("true")) { //$NON-NLS-1$ |
| 189 | // hack : add the current output location for each classpath entries |
| 190 | IClasspathEntry[] classpathEntries = fProject.getRawClasspath(); |
| 191 | List containers = new ArrayList(); |
| 192 | for (int i = 0; i < classpathEntries.length; i++) { |
| 193 | IClasspathEntry classpathEntry = classpathEntries[i]; |
| 194 | switch(classpathEntry.getEntryKind()) { |
| 195 | case IClasspathEntry.CPE_SOURCE : |
| 196 | String containerPath = classpathEntry.getPath().removeFirstSegments(1).toString(); |
| 197 | IApiTypeContainer container = getApiTypeContainer(containerPath, this); |
| 198 | if (container != null && !containers.contains(container)) { |
| 199 | containers.add(container); |
| 200 | } |
| 201 | break; |
| 202 | case IClasspathEntry.CPE_VARIABLE : |
| 203 | classpathEntry = JavaCore.getResolvedClasspathEntry(classpathEntry); |
| 204 | //$FALL-THROUGH$ |
| 205 | case IClasspathEntry.CPE_LIBRARY : |
| 206 | IPath path = classpathEntry.getPath(); |
| 207 | if (Util.isArchive(path.lastSegment())) { |
| 208 | IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path); |
| 209 | if (resource != null) { |
| 210 | // jar inside the workspace |
| 211 | containers.add(new ArchiveApiTypeContainer(this, resource.getLocation().toOSString())); |
| 212 | } else { |
| 213 | // external jar |
| 214 | containers.add(new ArchiveApiTypeContainer(this, path.toOSString())); |
| 215 | } |
| 216 | } |
| 217 | break; |
| 218 | } |
| 219 | } |
| 220 | if (!containers.isEmpty()) { |
| 221 | IApiTypeContainer cfc = null; |
| 222 | if (containers.size() == 1) { |
| 223 | cfc = (IApiTypeContainer) containers.get(0); |
| 224 | } else { |
| 225 | cfc = new CompositeApiTypeContainer(this, containers); |
| 226 | } |
| 227 | fPathToOutputContainers.put(".", cfc); //$NON-NLS-1$ |
| 228 | } |
| 229 | } |
| 230 | } else { |
| 231 | IBuildEntry[] entries = build.getBuildEntries(); |
| 232 | int length = entries.length; |
| 233 | for (int i = 0; i < length; i++) { |
| 234 | IBuildEntry buildEntry = entries[i]; |
| 235 | if (buildEntry.getName().startsWith(IBuildEntry.JAR_PREFIX)) { |
| 236 | String jar = buildEntry.getName().substring(IBuildEntry.JAR_PREFIX.length()); |
| 237 | String[] tokens = buildEntry.getTokens(); |
| 238 | if (tokens.length == 1) { |
| 239 | IApiTypeContainer container = getApiTypeContainer(tokens[0], this); |
| 240 | if (container != null) { |
| 241 | fPathToOutputContainers.put(jar, container); |
| 242 | } |
| 243 | } else { |
| 244 | List containers = new ArrayList(); |
| 245 | for (int j = 0; j < tokens.length; j++) { |
| 246 | String currentToken = tokens[j]; |
| 247 | IApiTypeContainer container = getApiTypeContainer(currentToken, this); |
| 248 | if (container != null && !containers.contains(container)) { |
| 249 | containers.add(container); |
| 250 | } |
| 251 | } |
| 252 | if (!containers.isEmpty()) { |
| 253 | IApiTypeContainer cfc = null; |
| 254 | if (containers.size() == 1) { |
| 255 | cfc = (IApiTypeContainer) containers.get(0); |
| 256 | } else { |
| 257 | cfc = new CompositeApiTypeContainer(this, containers); |
| 258 | } |
| 259 | fPathToOutputContainers.put(jar, cfc); |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | } |
| 266 | return super.createApiTypeContainers(); |
| 267 | } |
| 268 | return Collections.EMPTY_LIST; |
| 269 | } |
| 270 | |
| 271 | /* (non-Javadoc) |
| 272 | * @see org.eclipse.pde.api.tools.internal.BundleApiComponent#createClassFileContainer(java.lang.String) |
| 273 | */ |
| 274 | protected IApiTypeContainer createApiTypeContainer(String path) throws IOException, CoreException { |
| 275 | if (this.fPathToOutputContainers == null) { |
| 276 | baselineDisposed(getBaseline()); |
| 277 | } |
| 278 | IApiTypeContainer container = (IApiTypeContainer) fPathToOutputContainers.get(path); |
| 279 | if (container == null) { |
| 280 | // could be a binary jar included in the plug-in, just look for it |
| 281 | container = findApiTypeContainer(path); |
| 282 | } |
| 283 | return container; |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * Finds and returns an existing {@link IApiTypeContainer} at the specified location |
| 288 | * in this project, or <code>null</code> if none. |
| 289 | * |
| 290 | * @param location project relative path to the class file container |
| 291 | * @return {@link IApiTypeContainer} or <code>null</code> |
| 292 | */ |
| 293 | private IApiTypeContainer findApiTypeContainer(String location) { |
| 294 | IResource res = fProject.getProject().findMember(new Path(location)); |
| 295 | if (res != null) { |
| 296 | if (res.getType() == IResource.FILE) { |
| 297 | return new ArchiveApiTypeContainer(this, res.getLocation().toOSString()); |
| 298 | } else { |
| 299 | return new DirectoryApiTypeContainer(this, res.getLocation().toOSString()); |
| 300 | } |
| 301 | } |
| 302 | return null; |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Finds and returns an {@link IApiTypeContainer} for the specified |
| 307 | * source folder, or <code>null</code> if it does not exist. If the |
| 308 | * source folder shares an output location with a previous source |
| 309 | * folder, the output location is shared (a new one is not created). |
| 310 | * |
| 311 | * @param location project relative path to the source folder |
| 312 | * @return {@link IApiTypeContainer} or <code>null</code> |
| 313 | */ |
| 314 | private IApiTypeContainer getApiTypeContainer(String location, IApiComponent component) throws CoreException { |
| 315 | if (this.fOutputLocationToContainer == null) { |
| 316 | baselineDisposed(getBaseline()); |
| 317 | } |
| 318 | IResource res = fProject.getProject().findMember(new Path(location)); |
| 319 | if (res != null) { |
| 320 | IPackageFragmentRoot root = fProject.getPackageFragmentRoot(res); |
| 321 | if (root.exists()) { |
| 322 | if (root.getKind() == IPackageFragmentRoot.K_BINARY) { |
| 323 | if (res.getType() == IResource.FOLDER) { |
| 324 | // class file folder |
| 325 | IPath location2 = res.getLocation(); |
| 326 | IApiTypeContainer cfc = (IApiTypeContainer) fOutputLocationToContainer.get(location2); |
| 327 | if (cfc == null) { |
| 328 | cfc = new FolderApiTypeContainer(component, (IContainer) res); |
| 329 | fOutputLocationToContainer.put(location2, cfc); |
| 330 | } |
| 331 | return cfc; |
| 332 | } |
| 333 | } else { |
| 334 | IClasspathEntry entry = root.getRawClasspathEntry(); |
| 335 | IPath outputLocation = entry.getOutputLocation(); |
| 336 | if (outputLocation == null) { |
| 337 | outputLocation = fProject.getOutputLocation(); |
| 338 | } |
| 339 | IApiTypeContainer cfc = (IApiTypeContainer) fOutputLocationToContainer.get(outputLocation); |
| 340 | if (cfc == null) { |
| 341 | IPath projectFullPath = fProject.getProject().getFullPath(); |
| 342 | IContainer container = null; |
| 343 | if (projectFullPath.equals(outputLocation)) { |
| 344 | // The project is its own output location |
| 345 | container = fProject.getProject(); |
| 346 | } else { |
| 347 | container = fProject.getProject().getWorkspace().getRoot().getFolder(outputLocation); |
| 348 | } |
| 349 | if (container.exists()) { |
| 350 | cfc = new FolderApiTypeContainer(component, container); |
| 351 | fOutputLocationToContainer.put(outputLocation, cfc); |
| 352 | } |
| 353 | } |
| 354 | return cfc; |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | return null; |
| 359 | } |
| 360 | |
| 361 | /** |
| 362 | * Returns the Java project associated with this component. |
| 363 | * |
| 364 | * @return associated Java project |
| 365 | */ |
| 366 | public IJavaProject getJavaProject() { |
| 367 | return fProject; |
| 368 | } |
| 369 | |
| 370 | /** |
| 371 | * Returns the cached API type container for the given package fragment root, or <code>null</code> |
| 372 | * if none. The given package fragment has to be a SOURCE package fragment - this method is only |
| 373 | * used by the project API description to obtain a class file corresponding to a compilation unit |
| 374 | * when tag scanning (to resolve signatures). |
| 375 | * |
| 376 | * @param root source package fragment root |
| 377 | * @return API type container associated with the package fragment root, or <code>null</code> |
| 378 | * if none |
| 379 | */ |
| 380 | public IApiTypeContainer getTypeContainer(IPackageFragmentRoot root) throws CoreException { |
| 381 | if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { |
| 382 | getApiTypeContainers(); // ensure initialized |
| 383 | IResource resource = root.getResource(); |
| 384 | if (resource != null) { |
| 385 | String location = resource.getProjectRelativePath().toString(); |
| 386 | return getApiTypeContainer(location, this); |
| 387 | } |
| 388 | } |
| 389 | return null; |
| 390 | } |
| 391 | |
| 392 | } |