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