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