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; |
12 | |
13 | import java.io.File; |
14 | import java.io.FileFilter; |
15 | import java.io.FileInputStream; |
16 | import java.io.FileOutputStream; |
17 | import java.io.IOException; |
18 | import java.io.InputStream; |
19 | import java.io.OutputStream; |
20 | import java.io.UnsupportedEncodingException; |
21 | import java.util.ArrayList; |
22 | import java.util.HashMap; |
23 | import java.util.HashSet; |
24 | import java.util.Iterator; |
25 | import java.util.List; |
26 | |
27 | import javax.xml.parsers.DocumentBuilder; |
28 | import javax.xml.parsers.DocumentBuilderFactory; |
29 | import javax.xml.parsers.FactoryConfigurationError; |
30 | import javax.xml.parsers.ParserConfigurationException; |
31 | |
32 | import org.eclipse.core.resources.IProject; |
33 | import org.eclipse.core.resources.IResource; |
34 | import org.eclipse.core.resources.IResourceChangeEvent; |
35 | import org.eclipse.core.resources.IResourceChangeListener; |
36 | import org.eclipse.core.resources.IResourceDelta; |
37 | import org.eclipse.core.resources.ISaveContext; |
38 | import org.eclipse.core.resources.ISaveParticipant; |
39 | import org.eclipse.core.resources.ResourcesPlugin; |
40 | import org.eclipse.core.runtime.CoreException; |
41 | import org.eclipse.core.runtime.IPath; |
42 | import org.eclipse.core.runtime.IStatus; |
43 | import org.eclipse.core.runtime.Path; |
44 | import org.eclipse.core.runtime.Platform; |
45 | import org.eclipse.core.runtime.Status; |
46 | import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
47 | import org.eclipse.core.runtime.preferences.IPreferencesService; |
48 | import org.eclipse.core.runtime.preferences.IScopeContext; |
49 | import org.eclipse.core.runtime.preferences.InstanceScope; |
50 | import org.eclipse.jdt.core.ElementChangedEvent; |
51 | import org.eclipse.jdt.core.IElementChangedListener; |
52 | import org.eclipse.jdt.core.IJavaElement; |
53 | import org.eclipse.jdt.core.IJavaElementDelta; |
54 | import org.eclipse.jdt.core.IJavaProject; |
55 | import org.eclipse.jdt.core.IPackageFragment; |
56 | import org.eclipse.jdt.core.IPackageFragmentRoot; |
57 | import org.eclipse.jdt.core.JavaCore; |
58 | import org.eclipse.pde.api.tools.internal.model.ApiBaseline; |
59 | import org.eclipse.pde.api.tools.internal.model.ApiModelCache; |
60 | import org.eclipse.pde.api.tools.internal.model.ApiModelFactory; |
61 | import org.eclipse.pde.api.tools.internal.model.StubApiComponent; |
62 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
63 | import org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager; |
64 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; |
65 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
66 | import org.eclipse.pde.api.tools.internal.util.Util; |
67 | import org.eclipse.pde.core.plugin.IPluginModelBase; |
68 | import org.eclipse.pde.core.plugin.PluginRegistry; |
69 | import org.w3c.dom.Document; |
70 | import org.w3c.dom.Element; |
71 | import org.w3c.dom.NodeList; |
72 | import org.xml.sax.SAXException; |
73 | import org.xml.sax.helpers.DefaultHandler; |
74 | |
75 | /** |
76 | * This manager is used to maintain (persist, restore, access, update) Api baselines. |
77 | * This manager is lazy, in that caches are built and maintained when requests |
78 | * are made for information, nothing is pre-loaded when the manager is initialized. |
79 | * |
80 | * @since 1.0.0 |
81 | * @noinstantiate This class is not intended to be instantiated by clients. |
82 | */ |
83 | public final class ApiBaselineManager implements IApiBaselineManager, ISaveParticipant, IElementChangedListener, IResourceChangeListener { |
84 | |
85 | /** |
86 | * Constant used for controlling tracing in the API tool builder |
87 | */ |
88 | private static boolean DEBUG = Util.DEBUG; |
89 | |
90 | /** |
91 | * Method used for initializing tracing in the API tool builder |
92 | */ |
93 | public static void setDebug(boolean debugValue) { |
94 | DEBUG = debugValue || Util.DEBUG; |
95 | } |
96 | |
97 | /** |
98 | * Constant for the default API baseline. |
99 | * Value is: <code>default_api_profile</code> |
100 | */ |
101 | private static final String DEFAULT_BASELINE = "default_api_profile"; //$NON-NLS-1$ |
102 | |
103 | /** |
104 | * Constant representing the id of the workspace {@link IApiBaseline}. |
105 | * Value is: <code>workspace</code> |
106 | */ |
107 | public static final String WORKSPACE_API_BASELINE_ID = "workspace"; //$NON-NLS-1$ |
108 | |
109 | /** |
110 | * Constant representing the file extension for a baseline file. |
111 | * Value is: <code>.profile</code> |
112 | */ |
113 | private static final String BASELINE_FILE_EXTENSION = ".profile"; //$NON-NLS-1$ |
114 | |
115 | /** |
116 | * The main cache for the manager. |
117 | * The form of the cache is: |
118 | * <pre> |
119 | * HashMap<String(baselineid), {@link IApiBaseline}> |
120 | * </pre> |
121 | */ |
122 | private HashMap baselinecache = null; |
123 | |
124 | /** |
125 | * Cache of baseline names to the location with their infos in it |
126 | */ |
127 | private HashMap handlecache = null; |
128 | |
129 | private HashSet hasinfos = null; |
130 | |
131 | /** |
132 | * The current default {@link IApiBaseline} |
133 | */ |
134 | private String defaultbaseline = null; |
135 | |
136 | /** |
137 | * The current workspace baseline |
138 | */ |
139 | private IApiBaseline workspacebaseline = null; |
140 | |
141 | /** |
142 | * The default save location for persisting the cache from this manager. |
143 | */ |
144 | private IPath savelocation = null; |
145 | |
146 | /** |
147 | * If the cache of profiles needs to be saved or not. |
148 | */ |
149 | private boolean fNeedsSaving = false; |
150 | |
151 | /** |
152 | * The singleton instance |
153 | */ |
154 | private static ApiBaselineManager fInstance = null; |
155 | |
156 | /** |
157 | * Constructor |
158 | */ |
159 | private ApiBaselineManager(boolean framework) { |
160 | if(framework) { |
161 | ApiPlugin.getDefault().addSaveParticipant(this); |
162 | JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE); |
163 | ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_BUILD); |
164 | savelocation = ApiPlugin.getDefault().getStateLocation().append(".api_profiles").addTrailingSeparator(); //$NON-NLS-1$ |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * Returns the singleton instance of the manager |
170 | * @return the singleton instance of the manager |
171 | */ |
172 | public static synchronized ApiBaselineManager getManager() { |
173 | if(fInstance == null) { |
174 | fInstance = new ApiBaselineManager(ApiPlugin.isRunningInFramework()); |
175 | } |
176 | return fInstance; |
177 | } |
178 | |
179 | /* (non-Javadoc) |
180 | * @see org.eclipse.pde.api.tools.IApiBaselineManager#getApiProfile(java.lang.String) |
181 | */ |
182 | public synchronized IApiBaseline getApiBaseline(String name) { |
183 | initializeStateCache(); |
184 | return (ApiBaseline) baselinecache.get(name); |
185 | } |
186 | |
187 | /* (non-Javadoc) |
188 | * @see org.eclipse.pde.api.tools.IApiBaselineManager#getApiProfiles() |
189 | */ |
190 | public synchronized IApiBaseline[] getApiBaselines() { |
191 | initializeStateCache(); |
192 | return (IApiBaseline[]) baselinecache.values().toArray(new IApiBaseline[baselinecache.size()]); |
193 | } |
194 | |
195 | /* (non-Javadoc) |
196 | * @see org.eclipse.pde.api.tools.IApiBaselineManager#addApiProfile(org.eclipse.pde.api.tools.model.component.IApiBaseline) |
197 | */ |
198 | public synchronized void addApiBaseline(IApiBaseline newbaseline) { |
199 | if(newbaseline != null) { |
200 | initializeStateCache(); |
201 | baselinecache.put(newbaseline.getName(), newbaseline); |
202 | if(((ApiBaseline)newbaseline).peekInfos()) { |
203 | hasinfos.add(newbaseline.getName()); |
204 | } |
205 | fNeedsSaving = true; |
206 | } |
207 | } |
208 | |
209 | /* (non-Javadoc) |
210 | * @see org.eclipse.pde.api.tools.IApiBaselineManager#removeApiProfile(java.lang.String) |
211 | */ |
212 | public synchronized boolean removeApiBaseline(String name) { |
213 | if(name != null) { |
214 | initializeStateCache(); |
215 | IApiBaseline profile = (IApiBaseline) baselinecache.remove(name); |
216 | if(profile != null) { |
217 | profile.dispose(); |
218 | boolean success = true; |
219 | if(savelocation == null) { |
220 | return success; |
221 | } |
222 | //remove from filesystem |
223 | File file = savelocation.append(name+BASELINE_FILE_EXTENSION).toFile(); |
224 | if(file.exists()) { |
225 | success &= file.delete(); |
226 | } |
227 | fNeedsSaving = true; |
228 | |
229 | //flush the model cache |
230 | ApiModelCache.getCache().removeElementInfo(profile); |
231 | return success; |
232 | } |
233 | } |
234 | return false; |
235 | } |
236 | |
237 | /** |
238 | * Loads the infos for the given baseline from persisted storage (the *.profile file) |
239 | * @param baseline |
240 | * @throws CoreException |
241 | */ |
242 | public void loadBaselineInfos(IApiBaseline baseline) throws CoreException { |
243 | initializeStateCache(); |
244 | if(hasinfos.contains(baseline.getName())) { |
245 | return; |
246 | } |
247 | String filename = (String) handlecache.get(baseline.getName()); |
248 | if(filename != null) { |
249 | File file = new File(filename); |
250 | if(file.exists()) { |
251 | FileInputStream inputStream = null; |
252 | try { |
253 | inputStream = new FileInputStream(file); |
254 | restoreBaseline(baseline, inputStream); |
255 | } catch (IOException e) { |
256 | ApiPlugin.log(e); |
257 | } finally { |
258 | if (inputStream != null) { |
259 | try { |
260 | inputStream.close(); |
261 | } catch(IOException e) { |
262 | // ignore |
263 | } |
264 | } |
265 | } |
266 | hasinfos.add(baseline.getName()); |
267 | } |
268 | } |
269 | } |
270 | |
271 | /** |
272 | * Initializes the profile cache lazily. Only performs work |
273 | * if the current cache has not been created yet |
274 | * @throws FactoryConfigurationError |
275 | * @throws ParserConfigurationException |
276 | */ |
277 | private synchronized void initializeStateCache() { |
278 | long time = System.currentTimeMillis(); |
279 | if(baselinecache == null) { |
280 | handlecache = new HashMap(8); |
281 | hasinfos = new HashSet(8); |
282 | baselinecache = new HashMap(8); |
283 | if(!ApiPlugin.isRunningInFramework()) { |
284 | return; |
285 | } |
286 | File[] baselines = savelocation.toFile().listFiles(new FileFilter() { |
287 | public boolean accept(File pathname) { |
288 | return pathname.getName().endsWith(BASELINE_FILE_EXTENSION); |
289 | } |
290 | }); |
291 | if(baselines != null) { |
292 | IApiBaseline newbaseline = null; |
293 | for(int i = 0; i < baselines.length; i++) { |
294 | File baseline = baselines[i]; |
295 | if(baseline.exists()) { |
296 | newbaseline = new ApiBaseline(new Path(baseline.getName()).removeFileExtension().toString()); |
297 | handlecache.put(newbaseline.getName(), baseline.getAbsolutePath()); |
298 | baselinecache.put(newbaseline.getName(), newbaseline); |
299 | } |
300 | } |
301 | } |
302 | String def = getDefaultProfilePref(); |
303 | IApiBaseline baseline = (IApiBaseline) baselinecache.get(def); |
304 | defaultbaseline = (baseline != null ? def : null); |
305 | if(DEBUG) { |
306 | System.out.println("Time to initialize state cache: " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
307 | } |
308 | } |
309 | } |
310 | |
311 | /** |
312 | * @return the default API baseline saved in the preferences, or <code>null</code> if there isn't one |
313 | */ |
314 | private String getDefaultProfilePref() { |
315 | IPreferencesService service = Platform.getPreferencesService(); |
316 | return service.getString(ApiPlugin.PLUGIN_ID, DEFAULT_BASELINE, null, new IScopeContext[] {new InstanceScope()}); |
317 | } |
318 | |
319 | /** |
320 | * Persists all of the cached elements to individual xml files named |
321 | * with the id of the API profile |
322 | * @throws IOException |
323 | */ |
324 | private void persistStateCache() throws CoreException, IOException { |
325 | if(savelocation == null) { |
326 | return; |
327 | } |
328 | IEclipsePreferences node = new InstanceScope().getNode(ApiPlugin.PLUGIN_ID); |
329 | if(defaultbaseline != null) { |
330 | node.put(DEFAULT_BASELINE, defaultbaseline); |
331 | } |
332 | else { |
333 | node.remove(DEFAULT_BASELINE); |
334 | } |
335 | if(baselinecache != null && hasinfos != null) { |
336 | File dir = new File(savelocation.toOSString()); |
337 | if(!dir.exists()) { |
338 | dir.mkdirs(); |
339 | } |
340 | String id = null; |
341 | File file = null; |
342 | FileOutputStream fout = null; |
343 | IApiBaseline baseline = null; |
344 | for(Iterator iter = baselinecache.keySet().iterator(); iter.hasNext();) { |
345 | id = (String) iter.next(); |
346 | baseline = (IApiBaseline) baselinecache.get(id); |
347 | if(!hasinfos.contains(baseline.getName())) { |
348 | continue; |
349 | } |
350 | file = savelocation.append(id+BASELINE_FILE_EXTENSION).toFile(); |
351 | if(!file.exists()) { |
352 | file.createNewFile(); |
353 | } |
354 | try { |
355 | fout = new FileOutputStream(file); |
356 | writeBaselineDescription(baseline, fout); |
357 | fout.flush(); |
358 | } |
359 | finally { |
360 | fout.close(); |
361 | } |
362 | } |
363 | } |
364 | } |
365 | |
366 | /** |
367 | * Writes out the current state of the {@link IApiBaseline} as XML |
368 | * to the given output stream |
369 | * @param stream |
370 | * @throws CoreException |
371 | */ |
372 | private void writeBaselineDescription(IApiBaseline baseline, OutputStream stream) throws CoreException { |
373 | String xml = getProfileXML(baseline); |
374 | try { |
375 | stream.write(xml.getBytes(IApiCoreConstants.UTF_8)); |
376 | } catch (UnsupportedEncodingException e) { |
377 | abort("Error writing pofile descrition", e); //$NON-NLS-1$ |
378 | } catch (IOException e) { |
379 | abort("Error writing pofile descrition", e); //$NON-NLS-1$ |
380 | } |
381 | } |
382 | |
383 | /** |
384 | * Returns an XML description of the given profile. |
385 | * |
386 | * @param profile API profile |
387 | * @return XML |
388 | * @throws CoreException |
389 | */ |
390 | private String getProfileXML(IApiBaseline profile) throws CoreException { |
391 | Document document = Util.newDocument(); |
392 | Element root = document.createElement(IApiXmlConstants.ELEMENT_APIPROFILE); |
393 | document.appendChild(root); |
394 | root.setAttribute(IApiXmlConstants.ATTR_NAME, profile.getName()); |
395 | root.setAttribute(IApiXmlConstants.ATTR_VERSION, IApiXmlConstants.API_PROFILE_CURRENT_VERSION); |
396 | String location = profile.getLocation(); |
397 | if (location != null) { |
398 | root.setAttribute(IApiXmlConstants.ATTR_LOCATION, location); |
399 | } |
400 | Element celement = null; |
401 | IApiComponent[] components = profile.getApiComponents(); |
402 | for(int i = 0, max = components.length; i < max; i++) { |
403 | IApiComponent comp = components[i]; |
404 | if (!comp.isSystemComponent()) { |
405 | celement = document.createElement(IApiXmlConstants.ELEMENT_APICOMPONENT); |
406 | celement.setAttribute(IApiXmlConstants.ATTR_ID, comp.getId()); |
407 | celement.setAttribute(IApiXmlConstants.ATTR_VERSION, comp.getVersion()); |
408 | celement.setAttribute(IApiXmlConstants.ATTR_LOCATION, new Path(comp.getLocation()).toPortableString()); |
409 | root.appendChild(celement); |
410 | } |
411 | } |
412 | return Util.serializeDocument(document); |
413 | } |
414 | |
415 | /** |
416 | * Throws a core exception with the given message and underlying exception, |
417 | * if any. |
418 | * |
419 | * @param message error message |
420 | * @param e underlying exception or <code>null</code> |
421 | * @throws CoreException |
422 | */ |
423 | private static void abort(String message, Throwable e) throws CoreException { |
424 | throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, message, e)); |
425 | } |
426 | |
427 | /** |
428 | * Constructs and returns a profile from the given input stream (persisted profile). |
429 | * |
430 | * @param stream input stream |
431 | * @return API profile |
432 | * @throws CoreException if unable to restore the profile |
433 | */ |
434 | private void restoreBaseline(IApiBaseline baseline, InputStream stream) throws CoreException { |
435 | long start = System.currentTimeMillis(); |
436 | DocumentBuilder parser = null; |
437 | try { |
438 | parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
439 | parser.setErrorHandler(new DefaultHandler()); |
440 | } catch (ParserConfigurationException e) { |
441 | abort("Error restoring API baseline", e); //$NON-NLS-1$ |
442 | } catch (FactoryConfigurationError e) { |
443 | abort("Error restoring API baseline", e); //$NON-NLS-1$ |
444 | } |
445 | try { |
446 | Document document = parser.parse(stream); |
447 | Element root = document.getDocumentElement(); |
448 | if(root.getNodeName().equals(IApiXmlConstants.ELEMENT_APIPROFILE)) { |
449 | String baselineLocation = root.getAttribute(IApiXmlConstants.ATTR_LOCATION); |
450 | if (baselineLocation != null && !baselineLocation.equals(Util.EMPTY_STRING)) { |
451 | baseline.setLocation(Path.fromPortableString(baselineLocation).toOSString()); |
452 | } |
453 | // un-pooled components |
454 | NodeList children = root.getElementsByTagName(IApiXmlConstants.ELEMENT_APICOMPONENT); |
455 | List components = new ArrayList(); |
456 | for(int j = 0; j < children.getLength(); j++) { |
457 | Element componentNode = (Element) children.item(j); |
458 | // this also contains components in pools, so don't process them |
459 | if (componentNode.getParentNode().equals(root)) { |
460 | String location = componentNode.getAttribute(IApiXmlConstants.ATTR_LOCATION); |
461 | IApiComponent component = ApiModelFactory.newApiComponent(baseline, Path.fromPortableString(location).toOSString()); |
462 | if(component != null) { |
463 | components.add(component); |
464 | } |
465 | } |
466 | } |
467 | // pooled components - only for xml file with version <= 1 |
468 | // since version 2, pools have been removed |
469 | children = root.getElementsByTagName(IApiXmlConstants.ELEMENT_POOL); |
470 | IApiComponent component = null; |
471 | for(int j = 0; j < children.getLength(); j++) { |
472 | String location = ((Element) children.item(j)).getAttribute(IApiXmlConstants.ATTR_LOCATION); |
473 | IPath poolPath = Path.fromPortableString(location); |
474 | NodeList componentNodes = root.getElementsByTagName(IApiXmlConstants.ELEMENT_APICOMPONENT); |
475 | for (int i = 0; i < componentNodes.getLength(); i++) { |
476 | Element compElement = (Element) componentNodes.item(i); |
477 | String id = compElement.getAttribute(IApiXmlConstants.ATTR_ID); |
478 | String ver = compElement.getAttribute(IApiXmlConstants.ATTR_VERSION); |
479 | StringBuffer name = new StringBuffer(); |
480 | name.append(id); |
481 | name.append('_'); |
482 | name.append(ver); |
483 | File file = poolPath.append(name.toString()).toFile(); |
484 | if (!file.exists()) { |
485 | name.append(".jar"); //$NON-NLS-1$ |
486 | file = poolPath.append(name.toString()).toFile(); |
487 | } |
488 | component = ApiModelFactory.newApiComponent(baseline, file.getAbsolutePath()); |
489 | if(component != null) { |
490 | components.add(component); |
491 | } |
492 | } |
493 | } |
494 | baseline.addApiComponents((IApiComponent[]) components.toArray(new IApiComponent[components.size()])); |
495 | } |
496 | } catch (IOException e) { |
497 | abort("Error restoring API baseline", e); //$NON-NLS-1$ |
498 | } catch(SAXException e) { |
499 | abort("Error restoring API baseline", e); //$NON-NLS-1$ |
500 | } finally { |
501 | try { |
502 | stream.close(); |
503 | } catch (IOException io) { |
504 | ApiPlugin.log(io); |
505 | } |
506 | } |
507 | if (baseline == null) { |
508 | abort("Invalid profile description", null); //$NON-NLS-1$ |
509 | } |
510 | if(DEBUG) { |
511 | System.out.println("Time to restore a persisted profile : " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
512 | } |
513 | } |
514 | |
515 | /* (non-Javadoc) |
516 | * @see org.eclipse.core.resources.ISaveParticipant#saving(org.eclipse.core.resources.ISaveContext) |
517 | */ |
518 | public void saving(ISaveContext context) throws CoreException { |
519 | if(!fNeedsSaving) { |
520 | return; |
521 | } |
522 | try { |
523 | persistStateCache(); |
524 | cleanStateCache(); |
525 | fNeedsSaving = false; |
526 | } catch (IOException e) { |
527 | ApiPlugin.log(e); |
528 | } |
529 | } |
530 | |
531 | /** |
532 | * Cleans out all but the default baseline from the in-memory cache of baselines |
533 | */ |
534 | private void cleanStateCache() { |
535 | if(baselinecache != null) { |
536 | IApiBaseline baseline = null; |
537 | for(Iterator iter = baselinecache.keySet().iterator(); iter.hasNext();) { |
538 | baseline = (IApiBaseline) baselinecache.get(iter.next()); |
539 | if(!baseline.getName().equals(defaultbaseline)) { |
540 | baseline.dispose(); |
541 | hasinfos.remove(baseline.getName()); |
542 | //iter.remove(); |
543 | } |
544 | } |
545 | } |
546 | } |
547 | |
548 | /** |
549 | * Returns if the given name is an existing profile name |
550 | * @param name |
551 | * @return true if the given name is an existing profile name, false otherwise |
552 | */ |
553 | public boolean isExistingProfileName(String name) { |
554 | if(baselinecache == null) { |
555 | return false; |
556 | } |
557 | return baselinecache.keySet().contains(name); |
558 | } |
559 | |
560 | /** |
561 | * Cleans up the manager |
562 | */ |
563 | public void stop() { |
564 | try { |
565 | if(baselinecache != null) { |
566 | // we should first dispose all existing profiles |
567 | for (Iterator iterator = this.baselinecache.values().iterator(); iterator.hasNext();) { |
568 | IApiBaseline profile = (IApiBaseline) iterator.next(); |
569 | profile.dispose(); |
570 | } |
571 | this.baselinecache.clear(); |
572 | } |
573 | synchronized (this) { |
574 | if(this.workspacebaseline != null) { |
575 | this.workspacebaseline.dispose(); |
576 | } |
577 | } |
578 | if(this.handlecache != null) { |
579 | this.handlecache.clear(); |
580 | } |
581 | if(hasinfos != null) { |
582 | hasinfos.clear(); |
583 | } |
584 | StubApiComponent.disposeAllCaches(); |
585 | } |
586 | finally { |
587 | if(ApiPlugin.isRunningInFramework()) { |
588 | ApiPlugin.getDefault().removeSaveParticipant(this); |
589 | JavaCore.removeElementChangedListener(this); |
590 | ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
591 | } |
592 | } |
593 | } |
594 | |
595 | /* (non-Javadoc) |
596 | * @see org.eclipse.core.resources.ISaveParticipant#doneSaving(org.eclipse.core.resources.ISaveContext) |
597 | */ |
598 | public void doneSaving(ISaveContext context) {} |
599 | |
600 | /* (non-Javadoc) |
601 | * @see org.eclipse.core.resources.ISaveParticipant#prepareToSave(org.eclipse.core.resources.ISaveContext) |
602 | */ |
603 | public void prepareToSave(ISaveContext context) throws CoreException {} |
604 | |
605 | /* (non-Javadoc) |
606 | * @see org.eclipse.core.resources.ISaveParticipant#rollback(org.eclipse.core.resources.ISaveContext) |
607 | */ |
608 | public void rollback(ISaveContext context) {} |
609 | |
610 | /* (non-Javadoc) |
611 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager#getDefaultApiBaseline() |
612 | */ |
613 | public synchronized IApiBaseline getDefaultApiBaseline() { |
614 | initializeStateCache(); |
615 | return (IApiBaseline) baselinecache.get(defaultbaseline); |
616 | } |
617 | |
618 | /* (non-Javadoc) |
619 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager#setDefaultApiBaseline(java.lang.String) |
620 | */ |
621 | public void setDefaultApiBaseline(String name) { |
622 | fNeedsSaving = true; |
623 | defaultbaseline = name; |
624 | } |
625 | |
626 | /* (non-Javadoc) |
627 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager#getWorkspaceBaseline() |
628 | */ |
629 | public synchronized IApiBaseline getWorkspaceBaseline() { |
630 | if(ApiPlugin.isRunningInFramework()) { |
631 | if(this.workspacebaseline == null) { |
632 | try { |
633 | this.workspacebaseline = createWorkspaceBaseline(); |
634 | } catch (CoreException e) { |
635 | ApiPlugin.log(e); |
636 | } |
637 | } |
638 | return this.workspacebaseline; |
639 | } |
640 | return null; |
641 | } |
642 | |
643 | /** |
644 | * Disposes the workspace profile such that a new one will be created |
645 | * on the next request. |
646 | */ |
647 | private synchronized void disposeWorkspaceBaseline(IProject project) { |
648 | if (workspacebaseline != null) { |
649 | if (acceptProject(project) || workspacebaseline.getApiComponent(project) != null) { |
650 | if(DEBUG) { |
651 | System.out.println("disposing workspace baseline"); //$NON-NLS-1$ |
652 | } |
653 | workspacebaseline.dispose(); |
654 | StubApiComponent.disposeAllCaches(); |
655 | workspacebaseline = null; |
656 | } |
657 | } |
658 | } |
659 | |
660 | /** |
661 | * Creates a workspace {@link IApiBaseline} |
662 | * @return a new workspace {@link IApiBaseline} or <code>null</code> |
663 | */ |
664 | private IApiBaseline createWorkspaceBaseline() throws CoreException { |
665 | long time = System.currentTimeMillis(); |
666 | IApiBaseline baseline = null; |
667 | try { |
668 | baseline = ApiModelFactory.newApiBaseline(ApiBaselineManager.WORKSPACE_API_BASELINE_ID); |
669 | // populate it with only projects that are API aware |
670 | IPluginModelBase[] models = PluginRegistry.getActiveModels(); |
671 | List componentsList = new ArrayList(models.length); |
672 | IApiComponent apiComponent = null; |
673 | for (int i = 0, length = models.length; i < length; i++) { |
674 | try { |
675 | apiComponent = ApiModelFactory.newApiComponent(baseline, models[i]); |
676 | if (apiComponent != null) { |
677 | componentsList.add(apiComponent); |
678 | } |
679 | } catch (CoreException e) { |
680 | ApiPlugin.log(e); |
681 | } |
682 | } |
683 | baseline.addApiComponents((IApiComponent[]) componentsList.toArray(new IApiComponent[componentsList.size()])); |
684 | } finally { |
685 | if (DEBUG) { |
686 | System.out.println("Time to create a workspace profile : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
687 | } |
688 | } |
689 | return baseline; |
690 | } |
691 | |
692 | /* (non-Javadoc) |
693 | * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent) |
694 | */ |
695 | public void elementChanged(ElementChangedEvent event) { |
696 | if(!ApiPlugin.isRunningInFramework()) { |
697 | return; |
698 | } |
699 | Object obj = event.getSource(); |
700 | if(obj instanceof IJavaElementDelta) { |
701 | processJavaElementDeltas(((IJavaElementDelta)obj).getAffectedChildren(), null); |
702 | } |
703 | } |
704 | |
705 | /** |
706 | * Processes the java element deltas of interest |
707 | * @param deltas |
708 | */ |
709 | private synchronized void processJavaElementDeltas(IJavaElementDelta[] deltas, IJavaProject project) { |
710 | try { |
711 | for(int i = 0; i < deltas.length; i++) { |
712 | IJavaElementDelta delta = deltas[i]; |
713 | switch(delta.getElement().getElementType()) { |
714 | case IJavaElement.JAVA_PROJECT: { |
715 | IJavaProject proj = (IJavaProject) delta.getElement(); |
716 | IProject pj = proj.getProject(); |
717 | int flags = delta.getFlags(); |
718 | switch (delta.getKind()) { |
719 | case IJavaElementDelta.CHANGED: { |
720 | if( (flags & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0 || |
721 | (flags & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0 || |
722 | (flags & IJavaElementDelta.F_CLOSED) != 0 || |
723 | (flags & IJavaElementDelta.F_OPENED) != 0) { |
724 | if(DEBUG) { |
725 | System.out.println("--> processing CLASSPATH CHANGE/CLOSE/OPEN project: ["+proj.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
726 | } |
727 | disposeWorkspaceBaseline(pj); |
728 | } |
729 | if (!acceptProject(pj)) { |
730 | return; |
731 | } |
732 | if((flags & IJavaElementDelta.F_CHILDREN) != 0) { |
733 | if(DEBUG) { |
734 | System.out.println("--> processing child deltas of project: ["+proj.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
735 | } |
736 | processJavaElementDeltas(delta.getAffectedChildren(), proj); |
737 | } else { |
738 | IResourceDelta[] resourcedeltas = delta.getResourceDeltas(); |
739 | if(resourcedeltas != null) { |
740 | IResourceDelta rdelta = null; |
741 | for (int j = 0; j < resourcedeltas.length; j++) { |
742 | rdelta = resourcedeltas[j].findMember(new Path(Util.MANIFEST_NAME)); |
743 | if(rdelta!= null && rdelta.getKind() == IResourceDelta.CHANGED && (rdelta.getFlags() & IResourceDelta.CONTENT) > 0) { |
744 | if(DEBUG) { |
745 | System.out.println("--> processing manifest delta"); //$NON-NLS-1$ |
746 | } |
747 | disposeWorkspaceBaseline(pj); |
748 | break; |
749 | } |
750 | } |
751 | } |
752 | } |
753 | break; |
754 | } |
755 | case IJavaElementDelta.REMOVED: { |
756 | if((flags & IJavaElementDelta.F_MOVED_TO) != 0) { |
757 | if(DEBUG) { |
758 | System.out.println("--> processing PROJECT RENAME: ["+proj.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
759 | } |
760 | disposeWorkspaceBaseline(pj); |
761 | } |
762 | break; |
763 | } |
764 | } |
765 | break; |
766 | } |
767 | case IJavaElement.PACKAGE_FRAGMENT_ROOT: { |
768 | IPackageFragmentRoot root = (IPackageFragmentRoot) delta.getElement(); |
769 | if(DEBUG) { |
770 | System.out.println("processed package fragment root delta: ["+root.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
771 | } |
772 | switch(delta.getKind()) { |
773 | case IJavaElementDelta.CHANGED: { |
774 | if(DEBUG) { |
775 | System.out.println("processed children of CHANGED package fragment root: ["+root.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
776 | } |
777 | processJavaElementDeltas(delta.getAffectedChildren(), project); |
778 | break; |
779 | } |
780 | } |
781 | break; |
782 | } |
783 | case IJavaElement.PACKAGE_FRAGMENT: { |
784 | IPackageFragment fragment = (IPackageFragment) delta.getElement(); |
785 | if(delta.getKind() == IJavaElementDelta.REMOVED) { |
786 | handlePackageRemoval(project.getProject(), fragment); |
787 | } |
788 | break; |
789 | } |
790 | } |
791 | } |
792 | } catch (CoreException e) { |
793 | ApiPlugin.log(e); |
794 | } |
795 | } |
796 | |
797 | /** |
798 | * Handles the specified {@link IPackageFragment} being removed. |
799 | * When a packaged is removed, we: |
800 | * <ol> |
801 | * <li>Remove the package from the cache of resolved providers |
802 | * of that package (in the API baseline)</li> |
803 | * </ol> |
804 | * @param project |
805 | * @param fragment |
806 | * @throws CoreException |
807 | */ |
808 | private void handlePackageRemoval(IProject project, IPackageFragment fragment) throws CoreException { |
809 | if(DEBUG) { |
810 | System.out.println("processed package fragment REMOVE delta: ["+fragment.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
811 | } |
812 | ((ApiBaseline)getWorkspaceBaseline()).clearPackage(fragment.getElementName()); |
813 | } |
814 | |
815 | /** |
816 | * Returns if we should care about the specified project |
817 | * @param project |
818 | * @return true if the project is an 'API aware' project, false otherwise |
819 | */ |
820 | private boolean acceptProject(IProject project) { |
821 | try { |
822 | return project.isAccessible() && project.hasNature(ApiPlugin.NATURE_ID); |
823 | } |
824 | catch(CoreException e) { |
825 | return false; |
826 | } |
827 | } |
828 | |
829 | /* (non-Javadoc) |
830 | * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
831 | */ |
832 | public void resourceChanged(IResourceChangeEvent event) { |
833 | if(!ApiPlugin.isRunningInFramework()) { |
834 | return; |
835 | } |
836 | // clean all API errors when a project description changes |
837 | IResourceDelta delta = event.getDelta(); |
838 | if (delta != null) { |
839 | IResourceDelta[] children = delta.getAffectedChildren(IResourceDelta.CHANGED); |
840 | boolean dispose = false; |
841 | IResource resource = null; |
842 | IProject modifiedProject = null; |
843 | for (int i = 0; i < children.length; i++) { |
844 | resource = children[i].getResource(); |
845 | if (children[i].getResource().getType() == IResource.PROJECT) { |
846 | if ((children[i].getFlags() & IResourceDelta.DESCRIPTION) != 0) { |
847 | IProject project = (IProject)resource; |
848 | if (project.isAccessible()) { |
849 | try { |
850 | if (!project.getDescription().hasNature(ApiPlugin.NATURE_ID)) { |
851 | IJavaProject jp = JavaCore.create(project); |
852 | if (jp.exists()) { |
853 | ApiDescriptionManager.getDefault().clean(jp, true, true); |
854 | } |
855 | } |
856 | } catch (CoreException e) { |
857 | ApiPlugin.log(e.getStatus()); |
858 | } |
859 | modifiedProject = project; |
860 | dispose = true; |
861 | } |
862 | } |
863 | } |
864 | } |
865 | if(dispose) { |
866 | disposeWorkspaceBaseline(modifiedProject); |
867 | } |
868 | } |
869 | } |
870 | |
871 | } |