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 | } |