| 1 | /******************************************************************************* | 
| 2 |  * Copyright (c) 2007, 2009 IBM Corporation and others. | 
| 3 |  * All rights reserved. This program and the accompanying materials | 
| 4 |  * are made available under the terms of the Eclipse Public License v1.0 | 
| 5 |  * which accompanies this distribution, and is available at | 
| 6 |  * http://www.eclipse.org/legal/epl-v10.html | 
| 7 |  * | 
| 8 |  * Contributors: | 
| 9 |  *     IBM Corporation - initial API and implementation | 
| 10 |  *******************************************************************************/ | 
| 11 | package org.eclipse.pde.api.tools.internal.model; | 
| 12 |   | 
| 13 | import java.io.BufferedReader; | 
| 14 | import java.io.File; | 
| 15 | import java.io.FileInputStream; | 
| 16 | import java.io.FileOutputStream; | 
| 17 | import java.io.IOException; | 
| 18 | import java.io.InputStream; | 
| 19 | import java.io.StringReader; | 
| 20 | import java.net.MalformedURLException; | 
| 21 | import java.net.URL; | 
| 22 | import java.util.ArrayList; | 
| 23 | import java.util.Dictionary; | 
| 24 | import java.util.Enumeration; | 
| 25 | import java.util.HashSet; | 
| 26 | import java.util.Hashtable; | 
| 27 | import java.util.Iterator; | 
| 28 | import java.util.List; | 
| 29 | import java.util.Map; | 
| 30 | import java.util.Set; | 
| 31 | import java.util.jar.JarFile; | 
| 32 | import java.util.jar.Manifest; | 
| 33 | import java.util.zip.ZipEntry; | 
| 34 | import java.util.zip.ZipFile; | 
| 35 |   | 
| 36 | import javax.xml.parsers.FactoryConfigurationError; | 
| 37 | import javax.xml.parsers.ParserConfigurationException; | 
| 38 | import javax.xml.parsers.SAXParser; | 
| 39 | import javax.xml.parsers.SAXParserFactory; | 
| 40 |   | 
| 41 | import org.eclipse.core.runtime.CoreException; | 
| 42 | import org.eclipse.core.runtime.IStatus; | 
| 43 | import org.eclipse.core.runtime.Path; | 
| 44 | import org.eclipse.core.runtime.Status; | 
| 45 | import org.eclipse.osgi.service.resolver.BundleDescription; | 
| 46 | import org.eclipse.osgi.service.resolver.BundleSpecification; | 
| 47 | import org.eclipse.osgi.service.resolver.ExportPackageDescription; | 
| 48 | import org.eclipse.osgi.service.resolver.HostSpecification; | 
| 49 | import org.eclipse.osgi.service.resolver.ResolverError; | 
| 50 | import org.eclipse.osgi.service.resolver.StateObjectFactory; | 
| 51 | import org.eclipse.osgi.util.ManifestElement; | 
| 52 | import org.eclipse.osgi.util.NLS; | 
| 53 | import org.eclipse.pde.api.tools.internal.ApiBaselineManager; | 
| 54 | import org.eclipse.pde.api.tools.internal.ApiDescription; | 
| 55 | import org.eclipse.pde.api.tools.internal.ApiDescriptionProcessor; | 
| 56 | import org.eclipse.pde.api.tools.internal.BundleVersionRange; | 
| 57 | import org.eclipse.pde.api.tools.internal.CompositeApiDescription; | 
| 58 | import org.eclipse.pde.api.tools.internal.IApiCoreConstants; | 
| 59 | import org.eclipse.pde.api.tools.internal.RequiredComponentDescription; | 
| 60 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; | 
| 61 | import org.eclipse.pde.api.tools.internal.provisional.Factory; | 
| 62 | import org.eclipse.pde.api.tools.internal.provisional.IApiAccess; | 
| 63 | import org.eclipse.pde.api.tools.internal.provisional.IApiDescription; | 
| 64 | import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore; | 
| 65 | import org.eclipse.pde.api.tools.internal.provisional.IRequiredComponentDescription; | 
| 66 | import org.eclipse.pde.api.tools.internal.provisional.ProfileModifiers; | 
| 67 | import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; | 
| 68 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IPackageDescriptor; | 
| 69 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; | 
| 70 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; | 
| 71 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement; | 
| 72 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer; | 
| 73 | import org.eclipse.pde.api.tools.internal.util.FileManager; | 
| 74 | import org.eclipse.pde.api.tools.internal.util.SourceDefaultHandler; | 
| 75 | import org.eclipse.pde.api.tools.internal.util.Util; | 
| 76 | import org.eclipse.pde.internal.core.TargetWeaver; | 
| 77 | import org.osgi.framework.BundleException; | 
| 78 | import org.osgi.framework.Constants; | 
| 79 | import org.osgi.framework.Version; | 
| 80 | import org.xml.sax.InputSource; | 
| 81 | import org.xml.sax.SAXException; | 
| 82 |   | 
| 83 | /** | 
| 84 |  * Implementation of an API component based on a bundle in the file system. | 
| 85 |  *  | 
| 86 |  * @since 1.0.0 | 
| 87 |  */ | 
| 88 | public class BundleApiComponent extends AbstractApiComponent { | 
| 89 |          | 
| 90 |         static final String TMP_API_FILE_PREFIX = "api"; //$NON-NLS-1$ | 
| 91 |         static final String TMP_API_FILE_POSTFIX = "tmp"; //$NON-NLS-1$ | 
| 92 |          | 
| 93 |         /** | 
| 94 |          * Dictionary parsed from MANIFEST.MF | 
| 95 |          */ | 
| 96 |         private Dictionary fManifest; | 
| 97 |          | 
| 98 |         /** | 
| 99 |          * Manifest headers that are maintained after {@link BundleDescription} creation. | 
| 100 |          * Only these headers are maintained in the manifest dictionary to reduce footprint.   | 
| 101 |          */ | 
| 102 |         private static final String[] MANIFEST_HEADERS = new String[] { | 
| 103 |                 IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE, | 
| 104 |                 Constants.BUNDLE_CLASSPATH, | 
| 105 |                 Constants.BUNDLE_NAME, | 
| 106 |                 Constants.BUNDLE_VERSION | 
| 107 |         }; | 
| 108 |          | 
| 109 |         /** | 
| 110 |          * Whether there is an underlying .api_description file | 
| 111 |          */ | 
| 112 |         private boolean fHasApiDescription = false; | 
| 113 |          | 
| 114 |         /** | 
| 115 |          * Root location of component in the file system | 
| 116 |          */ | 
| 117 |         private String fLocation; | 
| 118 |          | 
| 119 |         /** | 
| 120 |          * Underlying bundle description (OSGi model of a bundle) | 
| 121 |          */ | 
| 122 |         private BundleDescription fBundleDescription; | 
| 123 |          | 
| 124 |         /** | 
| 125 |          * Symbolic name of this bundle | 
| 126 |          */ | 
| 127 |         private String fSymbolicName = null; | 
| 128 |          | 
| 129 |         /** | 
| 130 |          * Bundle version | 
| 131 |          */ | 
| 132 |         private Version fVersion = null; | 
| 133 |          | 
| 134 |         /** | 
| 135 |          * Cached value for the lowest EEs | 
| 136 |          */ | 
| 137 |         private String[] lowestEEs; | 
| 138 |   | 
| 139 |         /** | 
| 140 |          * Constructs a new API component from the specified location in the file system | 
| 141 |          * in the given profile. | 
| 142 |          *  | 
| 143 |          * @param profile owning profile | 
| 144 |          * @param location directory or jar file | 
| 145 |          * @exception CoreException if unable to create a component from the specified location | 
| 146 |          */ | 
| 147 |         public BundleApiComponent(IApiBaseline profile, String location) throws CoreException { | 
| 148 |                 super(profile); | 
| 149 |                 fLocation = location; | 
| 150 |         } | 
| 151 |          | 
| 152 |         /* (non-Javadoc) | 
| 153 |          * @see org.eclipse.pde.api.tools.internal.descriptors.AbstractApiComponent#dispose() | 
| 154 |          */ | 
| 155 |         public void dispose() { | 
| 156 |                 try { | 
| 157 |                         super.dispose(); | 
| 158 |                 } finally { | 
| 159 |                         synchronized(this) { | 
| 160 |                                 fManifest = null; | 
| 161 |                                 fBundleDescription = null; | 
| 162 |                         } | 
| 163 |                 } | 
| 164 |         } | 
| 165 |          | 
| 166 |         /** | 
| 167 |          * Returns this bundle's manifest as a dictionary. | 
| 168 |          *  | 
| 169 |          * @return manifest dictionary | 
| 170 |          * @exception CoreException if something goes terribly wrong | 
| 171 |          */ | 
| 172 |         protected synchronized Dictionary getManifest() throws CoreException { | 
| 173 |                 if(fManifest == null) { | 
| 174 |                         try { | 
| 175 |                                 fManifest = (Dictionary) loadManifest(new File(fLocation)); | 
| 176 |                         } catch (IOException e) { | 
| 177 |                                 abort("Unable to load manifest due to IO error", e); //$NON-NLS-1$ | 
| 178 |                         } | 
| 179 |                 } | 
| 180 |                 return fManifest; | 
| 181 |         } | 
| 182 |   | 
| 183 |         /** | 
| 184 |          * Reduce the manifest to only contain required headers after {@link BundleDescription} creation. | 
| 185 |          */ | 
| 186 |         protected synchronized void doManifestCompaction() throws CoreException { | 
| 187 |                 Dictionary temp = fManifest; | 
| 188 |                 fManifest = new Hashtable(MANIFEST_HEADERS.length); | 
| 189 |                 for (int i = 0; i < MANIFEST_HEADERS.length; i++) { | 
| 190 |                         String header = MANIFEST_HEADERS[i]; | 
| 191 |                         Object value = temp.get(header); | 
| 192 |                         if (value != null) { | 
| 193 |                                 fManifest.put(header, value); | 
| 194 |                         } | 
| 195 |                 } | 
| 196 |         } | 
| 197 |          | 
| 198 |         /** | 
| 199 |          * Returns if the bundle at the specified location is a valid bundle or not. | 
| 200 |          * Validity is determined via the existence of a readable manifest file | 
| 201 |          * @param location | 
| 202 |          * @return true if the bundle at the given location is valid false otherwise | 
| 203 |          * @throws IOException | 
| 204 |          */ | 
| 205 |         public boolean isValidBundle() throws CoreException { | 
| 206 |                 Dictionary manifest = getManifest(); | 
| 207 |                 return manifest != null && (manifest.get(Constants.BUNDLE_NAME) != null && manifest.get(Constants.BUNDLE_VERSION) != null); | 
| 208 |         } | 
| 209 |          | 
| 210 |         /** | 
| 211 |          * Initializes component state from the underlying bundle for the given | 
| 212 |          * state. | 
| 213 |          *  | 
| 214 |          * @param state PDE state | 
| 215 |          * @throws CoreException on failure | 
| 216 |          */ | 
| 217 |         protected synchronized void init(long bundleId) throws CoreException { | 
| 218 |                 try { | 
| 219 |                         Dictionary manifest = getManifest(); | 
| 220 |                         if (isBinaryBundle() && ApiBaselineManager.WORKSPACE_API_BASELINE_ID.equals(getBaseline().getName())) { | 
| 221 |                                 // must account for bundles in development mode - look for class files in output | 
| 222 |                                 // folders rather than jars | 
| 223 |                                 TargetWeaver.weaveManifest(manifest); | 
| 224 |                         } | 
| 225 |                         StateObjectFactory factory = StateObjectFactory.defaultFactory; | 
| 226 |                         fBundleDescription = factory.createBundleDescription(((ApiBaseline)getBaseline()).getState(), manifest, fLocation, bundleId); | 
| 227 |                         fSymbolicName = fBundleDescription.getSymbolicName(); | 
| 228 |                         fVersion = fBundleDescription.getVersion(); | 
| 229 |                         setName((String)getManifest().get(Constants.BUNDLE_NAME)); | 
| 230 |                 } catch (BundleException e) { | 
| 231 |                         abort("Unable to create API component from specified location: " + fLocation, e); //$NON-NLS-1$ | 
| 232 |                 } | 
| 233 |                 // compact manifest after initialization - only keep used headers | 
| 234 |                 doManifestCompaction(); | 
| 235 |         } | 
| 236 |          | 
| 237 |         /** | 
| 238 |          * Returns whether this API component represents a binary bundle versus a project bundle. | 
| 239 |          *  | 
| 240 |          * @return whether this API component represents a binary bundle | 
| 241 |          */ | 
| 242 |         protected boolean isBinaryBundle() { | 
| 243 |                 return true; | 
| 244 |         } | 
| 245 |          | 
| 246 |         /* (non-Javadoc) | 
| 247 |          * @see org.eclipse.pde.api.tools.internal.AbstractApiComponent#createApiDescription() | 
| 248 |          */ | 
| 249 |         protected IApiDescription createApiDescription() throws CoreException { | 
| 250 |                 BundleDescription[] fragments = getBundleDescription().getFragments(); | 
| 251 |                 if (fragments.length == 0) { | 
| 252 |                         return createLocalApiDescription(); | 
| 253 |                 } | 
| 254 |                 // build a composite description | 
| 255 |                 IApiDescription[] descriptions = new IApiDescription[fragments.length + 1]; | 
| 256 |                 for (int i = 0; i < fragments.length; i++) { | 
| 257 |                         BundleDescription fragment = fragments[i]; | 
| 258 |                         BundleApiComponent component = (BundleApiComponent) getBaseline().getApiComponent(fragment.getSymbolicName()); | 
| 259 |                         descriptions[i + 1] = component.getApiDescription(); | 
| 260 |                 } | 
| 261 |                 descriptions[0] = createLocalApiDescription(); | 
| 262 |                 return new CompositeApiDescription(descriptions); | 
| 263 |         } | 
| 264 |   | 
| 265 |         /** | 
| 266 |          * Creates and returns this component's API description based on packages | 
| 267 |          * supplied by this component, exported packages, and associated directives. | 
| 268 |          *  | 
| 269 |          * @return API description | 
| 270 |          * @throws CoreException if unable to initialize  | 
| 271 |          */ | 
| 272 |         protected IApiDescription createLocalApiDescription() throws CoreException { | 
| 273 |                 IApiDescription apiDesc = new ApiDescription(getId()); | 
| 274 |                 // first mark all packages as internal | 
| 275 |                 initializeApiDescription(apiDesc, getBundleDescription(), getLocalPackageNames()); | 
| 276 |                 try { | 
| 277 |                         String xml = loadApiDescription(new File(fLocation)); | 
| 278 |                         setHasApiDescription(xml != null); | 
| 279 |                         if (xml != null) { | 
| 280 |                                 ApiDescriptionProcessor.annotateApiSettings(null, apiDesc, xml); | 
| 281 |                         } | 
| 282 |                 } catch (IOException e) { | 
| 283 |                         abort("Unable to load .api_description file ", e); //$NON-NLS-1$ | 
| 284 |                 } | 
| 285 |                 return apiDesc; | 
| 286 |         } | 
| 287 |          | 
| 288 |         /** | 
| 289 |          * Returns the names of all packages that originate from this bundle. | 
| 290 |          * Does not include packages that originate from fragments or a host. | 
| 291 |          *  | 
| 292 |          * @return local package names | 
| 293 |          * @throws CoreException | 
| 294 |          */ | 
| 295 |         protected Set getLocalPackageNames() throws CoreException { | 
| 296 |                 Set names = new HashSet(); | 
| 297 |                 IApiTypeContainer[] containers = getApiTypeContainers(); | 
| 298 |                 IApiComponent comp = null; | 
| 299 |                 for (int i = 0; i < containers.length; i++) { | 
| 300 |                         comp = (IApiComponent) containers[i].getAncestor(IApiElement.COMPONENT); | 
| 301 |                         if (comp != null && comp.getId().equals(getId())) { | 
| 302 |                                 String[] packageNames = containers[i].getPackageNames(); | 
| 303 |                                 for (int j = 0; j < packageNames.length; j++) { | 
| 304 |                                         names.add(packageNames[j]); | 
| 305 |                                 } | 
| 306 |                         } | 
| 307 |                 } | 
| 308 |                 return names; | 
| 309 |         }         | 
| 310 |          | 
| 311 |   | 
| 312 |         /** | 
| 313 |          * Initializes the given API description based on package exports in the manifest. | 
| 314 |          * The API description for a bundle only contains packages that originate from | 
| 315 |          * this bundle (so a host will not contain API descriptions for packages that | 
| 316 |          * originate from fragments). However, a host's API description will be represented | 
| 317 |          * by a proxy that delegates to the host and all of its fragments to provide | 
| 318 |          * a complete description of the host. | 
| 319 |          *  | 
| 320 |          * @param apiDesc API description to initialize | 
| 321 |          * @param bundle the bundle to load from | 
| 322 |          * @param packages the complete set of packages names originating from the backing | 
| 323 |          *                 component | 
| 324 |          * @throws CoreException if an error occurs | 
| 325 |          */ | 
| 326 |         public static void initializeApiDescription(IApiDescription apiDesc, BundleDescription bundle, Set packages) throws CoreException { | 
| 327 |                 Iterator iterator = packages.iterator(); | 
| 328 |                 while (iterator.hasNext()) { | 
| 329 |                         String name = (String) iterator.next(); | 
| 330 |                         apiDesc.setVisibility(Factory.packageDescriptor(name), VisibilityModifiers.PRIVATE); | 
| 331 |                 } | 
| 332 |                 // then process exported packages that originate from this bundle | 
| 333 |                 // considering host and fragment package exports | 
| 334 |                 List supplied = new ArrayList(); | 
| 335 |                 ExportPackageDescription[] exportPackages = bundle.getExportPackages(); | 
| 336 |                 addSuppliedPackages(packages, supplied, exportPackages); | 
| 337 |                 HostSpecification host = bundle.getHost(); | 
| 338 |                 if (host != null) { | 
| 339 |                         BundleDescription[] hosts = host.getHosts(); | 
| 340 |                         for (int i = 0; i < hosts.length; i++) { | 
| 341 |                                 addSuppliedPackages(packages, supplied, hosts[i].getExportPackages()); | 
| 342 |                         } | 
| 343 |                 } | 
| 344 |                 BundleDescription[] fragments = bundle.getFragments(); | 
| 345 |                 for (int i = 0; i < fragments.length; i++) { | 
| 346 |                         addSuppliedPackages(packages, supplied, fragments[i].getExportPackages()); | 
| 347 |                 } | 
| 348 |                  | 
| 349 |                 annotateExportedPackages(apiDesc, (ExportPackageDescription[]) supplied.toArray(new ExportPackageDescription[supplied.size()])); | 
| 350 |         } | 
| 351 |   | 
| 352 |         /** | 
| 353 |          * Adds package exports to the given list if the associated package originates | 
| 354 |          * from this bundle. | 
| 355 |          *    | 
| 356 |          * @param packages names of packages supplied by this bundle | 
| 357 |          * @param supplied list to append package exports to | 
| 358 |          * @param exportPackages package exports to consider | 
| 359 |          */ | 
| 360 |         protected static void addSuppliedPackages(Set packages, List supplied, ExportPackageDescription[] exportPackages) { | 
| 361 |                 for (int i = 0; i < exportPackages.length; i++) { | 
| 362 |                         ExportPackageDescription pkg = exportPackages[i]; | 
| 363 |                         String name = pkg.getName(); | 
| 364 |                         if (name.equals(".")) { //$NON-NLS-1$ | 
| 365 |                                 // translate . to default package | 
| 366 |                                 name = Util.DEFAULT_PACKAGE_NAME; | 
| 367 |                         } | 
| 368 |                         if (packages.contains(name)) { | 
| 369 |                                 supplied.add(pkg); | 
| 370 |                         } | 
| 371 |                 } | 
| 372 |         } | 
| 373 |          | 
| 374 |         /** | 
| 375 |          * Annotates the API description with exported packages. | 
| 376 |          *  | 
| 377 |          * @param apiDesc description to annotate | 
| 378 |          * @param exportedPackages packages that are exported | 
| 379 |          */ | 
| 380 |         protected static void annotateExportedPackages(IApiDescription apiDesc, ExportPackageDescription[] exportedPackages) { | 
| 381 |                 for(int i = 0; i < exportedPackages.length; i++) { | 
| 382 |                         ExportPackageDescription pkg = exportedPackages[i]; | 
| 383 |                         boolean internal = ((Boolean) pkg.getDirective("x-internal")).booleanValue(); //$NON-NLS-1$ | 
| 384 |                         String[] friends = (String[]) pkg.getDirective("x-friends"); //$NON-NLS-1$ | 
| 385 |                         String pkgName = pkg.getName(); | 
| 386 |                         if (pkgName.equals(".")) { //$NON-NLS-1$ | 
| 387 |                                 // default package | 
| 388 |                                 pkgName = ""; //$NON-NLS-1$ | 
| 389 |                         } | 
| 390 |                         IPackageDescriptor pkgDesc = Factory.packageDescriptor(pkgName); | 
| 391 |                         if(internal) { | 
| 392 |                                 apiDesc.setVisibility(pkgDesc, VisibilityModifiers.PRIVATE); | 
| 393 |                         } | 
| 394 |                         if (friends != null) { | 
| 395 |                                 apiDesc.setVisibility(pkgDesc, VisibilityModifiers.PRIVATE); | 
| 396 |                                 for(int j = 0; j < friends.length; j++) { | 
| 397 |                                         //annotate the api description for x-friends access levels | 
| 398 |                                         apiDesc.setAccessLevel( | 
| 399 |                                                         Factory.componentDescriptor(friends[j]),  | 
| 400 |                                                         Factory.packageDescriptor(pkgName),  | 
| 401 |                                                         IApiAccess.FRIEND); | 
| 402 |                                 } | 
| 403 |                         } | 
| 404 |                         if (!internal && friends == null) { | 
| 405 |                                 //there could have been directives that have nothing to do with | 
| 406 |                                 //visibility, so we need to add the package as API in that case | 
| 407 |                                 apiDesc.setVisibility(pkgDesc, VisibilityModifiers.API); | 
| 408 |                         } | 
| 409 |                 } | 
| 410 |         } | 
| 411 |          | 
| 412 |         /* (non-Javadoc) | 
| 413 |          * @see org.eclipse.pde.api.tools.internal.AbstractApiComponent#createApiFilterStore() | 
| 414 |          */ | 
| 415 |         protected IApiFilterStore createApiFilterStore() throws CoreException { | 
| 416 |                 //always return a new empty store since we do not support filtering from bundles | 
| 417 |                 return null; | 
| 418 |         } | 
| 419 |          | 
| 420 |         /** | 
| 421 |          * @see org.eclipse.pde.api.tools.internal.AbstractApiTypeContainer#createApiTypeContainers() | 
| 422 |          */ | 
| 423 |         protected synchronized List createApiTypeContainers() throws CoreException { | 
| 424 |                 if (this.fBundleDescription == null) { | 
| 425 |                         baselineDisposed(getBaseline()); | 
| 426 |                 } | 
| 427 |                 List containers = new ArrayList(5); | 
| 428 |                 try { | 
| 429 |                         List all = new ArrayList(); | 
| 430 |                         // build the classpath from bundle and all fragments | 
| 431 |                         all.add(this); | 
| 432 |                         boolean considerFragments = true; | 
| 433 |                         if (Util.ORG_ECLIPSE_SWT.equals(getId())) { | 
| 434 |                                 // if SWT is a project to be built/analyzed don't consider its fragments | 
| 435 |                                 considerFragments = !isApiEnabled(); | 
| 436 |                         } | 
| 437 |                         if (considerFragments) {  | 
| 438 |                                 BundleDescription[] fragments = fBundleDescription.getFragments(); | 
| 439 |                                 for (int i = 0; i < fragments.length; i++) { | 
| 440 |                                         BundleDescription fragment = fragments[i]; | 
| 441 |                                         BundleApiComponent component = (BundleApiComponent) getBaseline().getApiComponent(fragment.getSymbolicName()); | 
| 442 |                                         if (component != null) { | 
| 443 |                                                 // force initialization of the fragment so we can retrieve its class file containers | 
| 444 |                                                 component.getApiTypeContainers(); | 
| 445 |                                                 all.add(component); | 
| 446 |                                         } | 
| 447 |                                 } | 
| 448 |                         } | 
| 449 |                         Iterator iterator = all.iterator(); | 
| 450 |                         Set entryNames = new HashSet(5); | 
| 451 |                         BundleApiComponent other = null; | 
| 452 |                         while (iterator.hasNext()) { | 
| 453 |                                 BundleApiComponent component = (BundleApiComponent) iterator.next(); | 
| 454 |                                 String[] paths = getClasspathEntries(component.getManifest()); | 
| 455 |                                 for (int i = 0; i < paths.length; i++) { | 
| 456 |                                         String path = paths[i]; | 
| 457 |                                         // don't re-process the same entry twice (except default entries ".") | 
| 458 |                                         if (!(".".equals(path))) { //$NON-NLS-1$ | 
| 459 |                                                 if (entryNames.contains(path)) { | 
| 460 |                                                         continue; | 
| 461 |                                                 } | 
| 462 |                                         } | 
| 463 |                                         IApiTypeContainer container = component.createApiTypeContainer(path); | 
| 464 |                                         if (container == null) { | 
| 465 |                                                 for(Iterator iter = all.iterator(); iter.hasNext();) { | 
| 466 |                                                         other = (BundleApiComponent) iter.next(); | 
| 467 |                                                         if (other != component) { | 
| 468 |                                                                 container = other.createApiTypeContainer(path); | 
| 469 |                                                         } | 
| 470 |                                                 } | 
| 471 |                                         } | 
| 472 |                                         if (container != null) { | 
| 473 |                                                 containers.add(container); | 
| 474 |                                                 if (!(".".equals(path))) { //$NON-NLS-1$ | 
| 475 |                                                         entryNames.add(path); | 
| 476 |                                                 } | 
| 477 |                                         } | 
| 478 |                                 } | 
| 479 |                         } | 
| 480 |                 } catch (BundleException e) { | 
| 481 |                         abort("Unable to parse bundle classpath", e); //$NON-NLS-1$ | 
| 482 |                 } catch (IOException e) { | 
| 483 |                         abort("Unable to initialize class file containers", e); //$NON-NLS-1$ | 
| 484 |                 } | 
| 485 |                 return containers; | 
| 486 |         } | 
| 487 |          | 
| 488 |         /** | 
| 489 |          * Returns whether this API component is enabled for API analysis by the API builder. | 
| 490 |          *  | 
| 491 |          * @return whether this API component is enabled for API analysis by the API builder. | 
| 492 |          */ | 
| 493 |         protected boolean isApiEnabled() { | 
| 494 |                 return false; | 
| 495 |         } | 
| 496 |          | 
| 497 |         /** | 
| 498 |          * Returns classpath entries defined in the given manifest. | 
| 499 |          *  | 
| 500 |          * @param manifest | 
| 501 |          * @return classpath entries as bundle relative paths | 
| 502 |          * @throws BundleException | 
| 503 |          */ | 
| 504 |         protected String[] getClasspathEntries(Dictionary manifest) throws BundleException { | 
| 505 |                 ManifestElement[] classpath = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, (String) manifest.get(Constants.BUNDLE_CLASSPATH)); | 
| 506 |                 String elements[] = null; | 
| 507 |                 if (classpath == null) { | 
| 508 |                         // default classpath is '.' | 
| 509 |                         elements = new String[]{"."}; //$NON-NLS-1$ | 
| 510 |                 } else { | 
| 511 |                         elements = new String[classpath.length]; | 
| 512 |                         for (int i = 0; i < classpath.length; i++) { | 
| 513 |                                 elements[i] = classpath[i].getValue(); | 
| 514 |                         } | 
| 515 |                 } | 
| 516 |                 return elements; | 
| 517 |         } | 
| 518 |          | 
| 519 |         /** | 
| 520 |          * Creates and returns an {@link IApiTypeContainer} at the specified path in | 
| 521 |          * this bundle, or <code>null</code> if the {@link IApiTypeContainer} does not | 
| 522 |          * exist. The path is the name (path) of entries specified by the | 
| 523 |          * <code>Bundle-ClassPath:</code> header. | 
| 524 |          *  | 
| 525 |          * @param path relative path to a class file container in this bundle | 
| 526 |          * @return {@link IApiTypeContainer} or <code>null</code> | 
| 527 |          * @exception IOException | 
| 528 |          */ | 
| 529 |         protected IApiTypeContainer createApiTypeContainer(String path) throws IOException, CoreException { | 
| 530 |                 File bundle = new File(fLocation); | 
| 531 |                 if (bundle.isDirectory()) { | 
| 532 |                         // bundle is folder | 
| 533 |                         File entry = new File(bundle, path); | 
| 534 |                         if (entry.exists()) { | 
| 535 |                                 if (entry.isFile()) { | 
| 536 |                                         return new ArchiveApiTypeContainer(this, entry.getCanonicalPath()); | 
| 537 |                                 } else { | 
| 538 |                                         return new DirectoryApiTypeContainer(this, entry.getCanonicalPath()); | 
| 539 |                                 } | 
| 540 |                         } | 
| 541 |                 } else { | 
| 542 |                         // bundle is jar'd | 
| 543 |                         ZipFile zip = null; | 
| 544 |                         try { | 
| 545 |                                 if (path.equals(".")) { //$NON-NLS-1$ | 
| 546 |                                         return new ArchiveApiTypeContainer(this, fLocation); | 
| 547 |                                 } else { | 
| 548 |                                         //classpath element can be jar or folder | 
| 549 |                                         //https://bugs.eclipse.org/bugs/show_bug.cgi?id=279729 | 
| 550 |                                         zip = new ZipFile(fLocation); | 
| 551 |                                         ZipEntry entry = zip.getEntry(path); | 
| 552 |                                         if (entry != null) { | 
| 553 |                                                 File tmpfolder = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ | 
| 554 |                                                 if(entry.isDirectory()) { | 
| 555 |                                                         //extract the dir and all children | 
| 556 |                                                         File dir = File.createTempFile(TMP_API_FILE_PREFIX, TMP_API_FILE_POSTFIX); | 
| 557 |                                                         dir.deleteOnExit(); | 
| 558 |                                                         //hack to create a temp directory | 
| 559 |                                                         // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4735419 | 
| 560 |                                                         if(dir.delete()) { | 
| 561 |                                                                 dir.mkdir(); | 
| 562 |                                                                 FileManager.getManager().recordTempFileRoot(dir.getCanonicalPath()); | 
| 563 |                                                         } | 
| 564 |                                                         extractDirectory(zip, entry.getName(), dir); | 
| 565 |                                                         if(dir.isDirectory() && dir.exists()) { | 
| 566 |                                                                 return new DirectoryApiTypeContainer(this, dir.getCanonicalPath()); | 
| 567 |                                                         } | 
| 568 |                                                 } | 
| 569 |                                                 else { | 
| 570 |                                                         File file = extractEntry(zip, entry, tmpfolder); | 
| 571 |                                                         if(Util.isArchive(file.getName())) { | 
| 572 |                                                                 File parent = file.getParentFile(); | 
| 573 |                                                                 if(!parent.equals(tmpfolder)) { | 
| 574 |                                                                         FileManager.getManager().recordTempFileRoot(parent.getCanonicalPath()); | 
| 575 |                                                                 } | 
| 576 |                                                                 else { | 
| 577 |                                                                         FileManager.getManager().recordTempFileRoot(file.getCanonicalPath()); | 
| 578 |                                                                 } | 
| 579 |                                                                 return new ArchiveApiTypeContainer(this, file.getCanonicalPath()); | 
| 580 |                                                         } | 
| 581 |                                                 } | 
| 582 |                                         } | 
| 583 |                                 } | 
| 584 |                         } finally { | 
| 585 |                                 if (zip != null) { | 
| 586 |                                         zip.close(); | 
| 587 |                                 } | 
| 588 |                         } | 
| 589 |                 } | 
| 590 |                 return null; | 
| 591 |         } | 
| 592 |                  | 
| 593 |         /** | 
| 594 |          * Extracts a directory from the archive given a path prefix for entries to retrieve. | 
| 595 |          * <code>null</code> can be passed in as a prefix, causing all entries to be be extracted from  | 
| 596 |          * the archive. | 
| 597 |          *  | 
| 598 |          * @param zip the {@link ZipFile} to extract from | 
| 599 |          * @param pathprefix the prefix'ing path to include for extraction | 
| 600 |          * @param parent the parent directory to extract to | 
| 601 |          * @throws IOException if the {@link ZipFile} cannot be read or extraction fails to write the file(s) | 
| 602 |          */ | 
| 603 |         void extractDirectory(ZipFile zip, String pathprefix, File parent) throws IOException { | 
| 604 |                 Enumeration entries = zip.entries(); | 
| 605 |                 String prefix = (pathprefix == null ? Util.EMPTY_STRING : pathprefix); | 
| 606 |                 ZipEntry entry = null; | 
| 607 |                 File file = null; | 
| 608 |                 while (entries.hasMoreElements()) { | 
| 609 |                         entry = (ZipEntry) entries.nextElement(); | 
| 610 |                         if(entry.getName().startsWith(prefix)) { | 
| 611 |                                 file = new File(parent, entry.getName()); | 
| 612 |                                 if (entry.isDirectory()) { | 
| 613 |                                         file.mkdir(); | 
| 614 |                                         continue; | 
| 615 |                                 } | 
| 616 |                                 extractEntry(zip, entry, parent); | 
| 617 |                         } | 
| 618 |                 } | 
| 619 |         } | 
| 620 |          | 
| 621 |         /** | 
| 622 |          * Extracts a non-directory entry from a zip file and returns the File handle | 
| 623 |          * @param zip the zip to extract from | 
| 624 |          * @param entry the entry to extract | 
| 625 |          * @param parent the parent directory to add the extracted entry to | 
| 626 |          * @return the file handle to the extracted entry, <code>null</code> otherwise | 
| 627 |          * @throws IOException | 
| 628 |          */ | 
| 629 |         File extractEntry(ZipFile zip, ZipEntry entry, File parent) throws IOException { | 
| 630 |                 InputStream inputStream = null; | 
| 631 |                 File file; | 
| 632 |                 FileOutputStream outputStream = null; | 
| 633 |                 try { | 
| 634 |                         inputStream = zip.getInputStream(entry); | 
| 635 |                         file = new File(parent, entry.getName()); | 
| 636 |                         File lparent = file.getParentFile(); | 
| 637 |                         if(!lparent.exists()) { | 
| 638 |                                 lparent.mkdirs(); | 
| 639 |                         } | 
| 640 |                         outputStream = new FileOutputStream(file); | 
| 641 |                         byte[] bytes = new byte[8096]; | 
| 642 |                         while (inputStream.available() > 0) { | 
| 643 |                                 int read = inputStream.read(bytes); | 
| 644 |                                 if (read > 0) { | 
| 645 |                                         outputStream.write(bytes, 0, read); | 
| 646 |                                 } | 
| 647 |                         } | 
| 648 |                 } finally { | 
| 649 |                         if (inputStream != null) { | 
| 650 |                                 try { | 
| 651 |                                         inputStream.close(); | 
| 652 |                                 } catch(IOException e) { | 
| 653 |                                         ApiPlugin.log(e); | 
| 654 |                                 } | 
| 655 |                         } | 
| 656 |                         if (outputStream != null) { | 
| 657 |                                 try { | 
| 658 |                                         outputStream.close(); | 
| 659 |                                 } catch(IOException e) { | 
| 660 |                                         ApiPlugin.log(e); | 
| 661 |                                 } | 
| 662 |                         } | 
| 663 |                 } | 
| 664 |                 return file; | 
| 665 |         } | 
| 666 |          | 
| 667 |         /** | 
| 668 |          * Parses a bunlde's manifest into a dictionary. The bundle may be in a jar | 
| 669 |          * or in a directory at the specified location. | 
| 670 |          *  | 
| 671 |          * @param bundleLocation root location of the bundle | 
| 672 |          * @return bundle manifest dictionary or <code>null</code> if none | 
| 673 |          * @throws IOException if unable to parse | 
| 674 |          */ | 
| 675 |         protected Map loadManifest(File bundleLocation) throws IOException { | 
| 676 |                 ZipFile jarFile = null; | 
| 677 |                 InputStream manifestStream = null; | 
| 678 |                 String extension = new Path(bundleLocation.getName()).getFileExtension(); | 
| 679 |                 try { | 
| 680 |                         if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$ | 
| 681 |                                 jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ); | 
| 682 |                                 ZipEntry manifestEntry = jarFile.getEntry(JarFile.MANIFEST_NAME); | 
| 683 |                                 if (manifestEntry != null) { | 
| 684 |                                         manifestStream = jarFile.getInputStream(manifestEntry); | 
| 685 |                                 } | 
| 686 |                         } else { | 
| 687 |                                 File file = new File(bundleLocation, JarFile.MANIFEST_NAME); | 
| 688 |                                 if (file.exists()) | 
| 689 |                                         manifestStream = new FileInputStream(file); | 
| 690 |                         } | 
| 691 |                         if (manifestStream == null) { | 
| 692 |                                 return null; | 
| 693 |                         } | 
| 694 |                         return ManifestElement.parseBundleManifest(manifestStream, new Hashtable(10)); | 
| 695 |                 } catch (BundleException e) { | 
| 696 |                         ApiPlugin.log(e); | 
| 697 |                 } finally { | 
| 698 |                         closingZipFileAndStream(manifestStream, jarFile); | 
| 699 |                 } | 
| 700 |                 return null; | 
| 701 |         } | 
| 702 |          | 
| 703 |         /** | 
| 704 |          * Reads and returns this bunlde's manifest in a Manifest object. | 
| 705 |          * The bundle may be in a jar or in a directory at the specified location. | 
| 706 |          *  | 
| 707 |          * @param bundleLocation root location of the bundle | 
| 708 |          * @return manifest or <code>null</code> if not present | 
| 709 |          * @throws IOException if unable to parse | 
| 710 |          */ | 
| 711 |         protected Manifest readManifest(File bundleLocation) throws IOException { | 
| 712 |                 ZipFile jarFile = null; | 
| 713 |                 InputStream manifestStream = null; | 
| 714 |                 try { | 
| 715 |                         String extension = new Path(bundleLocation.getName()).getFileExtension(); | 
| 716 |                         if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$ | 
| 717 |                                 jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ); | 
| 718 |                                 ZipEntry manifestEntry = jarFile.getEntry(JarFile.MANIFEST_NAME); | 
| 719 |                                 if (manifestEntry != null) { | 
| 720 |                                         manifestStream = jarFile.getInputStream(manifestEntry); | 
| 721 |                                 } | 
| 722 |                         } else { | 
| 723 |                                 File file = new File(bundleLocation, JarFile.MANIFEST_NAME); | 
| 724 |                                 if (file.exists()) | 
| 725 |                                         manifestStream = new FileInputStream(file); | 
| 726 |                         } | 
| 727 |                         if (manifestStream == null) { | 
| 728 |                                 return null; | 
| 729 |                         } | 
| 730 |                         return new Manifest(manifestStream); | 
| 731 |                 } finally { | 
| 732 |                         closingZipFileAndStream(manifestStream, jarFile); | 
| 733 |                 } | 
| 734 |         } | 
| 735 |   | 
| 736 |         void closingZipFileAndStream(InputStream stream, ZipFile jarFile) { | 
| 737 |                 try { | 
| 738 |                         if (stream != null) { | 
| 739 |                                 stream.close(); | 
| 740 |                         } | 
| 741 |                 } catch (IOException e) { | 
| 742 |                         ApiPlugin.log(e); | 
| 743 |                 } | 
| 744 |                 try { | 
| 745 |                         if (jarFile != null) { | 
| 746 |                                 jarFile.close(); | 
| 747 |                         } | 
| 748 |                 } catch (IOException e) { | 
| 749 |                         ApiPlugin.log(e); | 
| 750 |                 } | 
| 751 |         } | 
| 752 |          | 
| 753 |         /** | 
| 754 |          * Reads and returns the file contents corresponding to the given file name. | 
| 755 |          * The bundle may be in a jar or in a directory at the specified location. | 
| 756 |          *  | 
| 757 |          * @param xmlFileName the given file name | 
| 758 |          * @param bundleLocation the root location of the bundle | 
| 759 |          * @return the file contents or <code>null</code> if not present | 
| 760 |          */ | 
| 761 |         protected String readFileContents(String xmlFileName, File bundleLocation) { | 
| 762 |                 ZipFile jarFile = null; | 
| 763 |                 InputStream stream = null; | 
| 764 |                 try { | 
| 765 |                         String extension = new Path(bundleLocation.getName()).getFileExtension(); | 
| 766 |                         if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$ | 
| 767 |                                 jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ); | 
| 768 |                                 ZipEntry manifestEntry = jarFile.getEntry(xmlFileName); | 
| 769 |                                 if (manifestEntry != null) { | 
| 770 |                                         stream = jarFile.getInputStream(manifestEntry); | 
| 771 |                                 } | 
| 772 |                         } else { | 
| 773 |                                 File file = new File(bundleLocation, xmlFileName); | 
| 774 |                                 if (file.exists()) { | 
| 775 |                                         stream = new FileInputStream(file); | 
| 776 |                                 } | 
| 777 |                         } | 
| 778 |                         if (stream == null) { | 
| 779 |                                 return null; | 
| 780 |                         } | 
| 781 |                         return new String(Util.getInputStreamAsCharArray(stream, -1, IApiCoreConstants.UTF_8)); | 
| 782 |                 } catch(IOException e) { | 
| 783 |                         //TODO abort | 
| 784 |                         ApiPlugin.log(e); | 
| 785 |                 } finally { | 
| 786 |                         closingZipFileAndStream(stream, jarFile); | 
| 787 |                 } | 
| 788 |                 return null; | 
| 789 |         } | 
| 790 |   | 
| 791 |         /** | 
| 792 |          * Parses a bundle's .api_description XML into a string. The file may be in a jar | 
| 793 |          * or in a directory at the specified location. | 
| 794 |          *  | 
| 795 |          * @param bundleLocation root location of the bundle | 
| 796 |          * @return API description XML as a string or <code>null</code> if none | 
| 797 |          * @throws IOException if unable to parse | 
| 798 |          */ | 
| 799 |         protected String loadApiDescription(File bundleLocation) throws IOException { | 
| 800 |                 ZipFile jarFile = null; | 
| 801 |                 InputStream stream = null; | 
| 802 |                 String contents = null; | 
| 803 |                 try { | 
| 804 |                         String extension = new Path(bundleLocation.getName()).getFileExtension(); | 
| 805 |                         if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$ | 
| 806 |                                 jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ); | 
| 807 |                                 ZipEntry manifestEntry = jarFile.getEntry(IApiCoreConstants.API_DESCRIPTION_XML_NAME); | 
| 808 |                                 if (manifestEntry != null) { | 
| 809 |                                         // new file is present | 
| 810 |                                         stream = jarFile.getInputStream(manifestEntry); | 
| 811 |                                 } | 
| 812 |                         } else { | 
| 813 |                                 File file = new File(bundleLocation, IApiCoreConstants.API_DESCRIPTION_XML_NAME); | 
| 814 |                                 if (file.exists()) { | 
| 815 |                                         // use new file | 
| 816 |                                         stream = new FileInputStream(file); | 
| 817 |                                 } | 
| 818 |                         } | 
| 819 |                         if (stream == null) { | 
| 820 |                                 return null; | 
| 821 |                         } | 
| 822 |                         char[] charArray = Util.getInputStreamAsCharArray(stream, -1, IApiCoreConstants.UTF_8); | 
| 823 |                         contents = new String(charArray); | 
| 824 |                 } finally { | 
| 825 |                         closingZipFileAndStream(stream, jarFile); | 
| 826 |                 } | 
| 827 |                 return contents; | 
| 828 |         } | 
| 829 |          | 
| 830 |          | 
| 831 |         /** | 
| 832 |          * Returns a URL describing a file inside a bundle. | 
| 833 |          *  | 
| 834 |          * @param bundleLocation root location of the bundle. May be a | 
| 835 |          *  directory or a file (jar) | 
| 836 |          * @param filePath bundle relative path to desired file | 
| 837 |          * @return URL to the file | 
| 838 |          * @throws MalformedURLException  | 
| 839 |          */ | 
| 840 |         protected URL getFileInBundle(File bundleLocation, String filePath) throws MalformedURLException { | 
| 841 |                 String extension = new Path(bundleLocation.getName()).getFileExtension(); | 
| 842 |                 StringBuffer urlSt = new StringBuffer(); | 
| 843 |                 if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$ | 
| 844 |                         urlSt.append("jar:file:"); //$NON-NLS-1$ | 
| 845 |                         urlSt.append(bundleLocation.getAbsolutePath()); | 
| 846 |                         urlSt.append("!/"); //$NON-NLS-1$ | 
| 847 |                         urlSt.append(filePath); | 
| 848 |                 } else { | 
| 849 |                         urlSt.append("file:"); //$NON-NLS-1$ | 
| 850 |                         urlSt.append(bundleLocation.getAbsolutePath()); | 
| 851 |                         urlSt.append(File.separatorChar); | 
| 852 |                         urlSt.append(filePath); | 
| 853 |                 }         | 
| 854 |                 return new URL(urlSt.toString()); | 
| 855 |         } | 
| 856 |          | 
| 857 |         /* (non-Javadoc) | 
| 858 |          * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getExecutionEnvironments() | 
| 859 |          */ | 
| 860 |         public synchronized String[] getExecutionEnvironments() throws CoreException { | 
| 861 |                 if (this.fBundleDescription == null) { | 
| 862 |                         baselineDisposed(getBaseline()); | 
| 863 |                 } | 
| 864 |                 return fBundleDescription.getExecutionEnvironments(); | 
| 865 |         } | 
| 866 |   | 
| 867 |         /* (non-Javadoc) | 
| 868 |          * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getId() | 
| 869 |          */ | 
| 870 |         public final String getId() { | 
| 871 |                 return fSymbolicName; | 
| 872 |         } | 
| 873 |   | 
| 874 |         /* (non-Javadoc) | 
| 875 |          * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getRequiredComponents() | 
| 876 |          */ | 
| 877 |         public synchronized IRequiredComponentDescription[] getRequiredComponents() throws CoreException { | 
| 878 |                 if (this.fBundleDescription == null) { | 
| 879 |                         baselineDisposed(getBaseline()); | 
| 880 |                 } | 
| 881 |                 BundleSpecification[] requiredBundles = fBundleDescription.getRequiredBundles(); | 
| 882 |                 IRequiredComponentDescription[] req = new IRequiredComponentDescription[requiredBundles.length]; | 
| 883 |                 for (int i = 0; i < requiredBundles.length; i++) { | 
| 884 |                         BundleSpecification bundle = requiredBundles[i]; | 
| 885 |                         req[i] = new RequiredComponentDescription(bundle.getName(), | 
| 886 |                                         new BundleVersionRange(bundle.getVersionRange()), | 
| 887 |                                         bundle.isOptional(), | 
| 888 |                                         bundle.isExported()); | 
| 889 |                 } | 
| 890 |                 return req; | 
| 891 |         } | 
| 892 |   | 
| 893 |         /* (non-Javadoc) | 
| 894 |          * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getVersion() | 
| 895 |          */ | 
| 896 |         public synchronized String getVersion() { | 
| 897 |                 return fVersion.toString(); | 
| 898 |         } | 
| 899 |          | 
| 900 |         /** | 
| 901 |          * Returns this component's bundle description. | 
| 902 |          *  | 
| 903 |          * @return bundle description | 
| 904 |          */ | 
| 905 |         public synchronized BundleDescription getBundleDescription() throws CoreException { | 
| 906 |                 if (this.fBundleDescription == null) { | 
| 907 |                         baselineDisposed(getBaseline()); | 
| 908 |                 } | 
| 909 |                 return fBundleDescription; | 
| 910 |         } | 
| 911 |   | 
| 912 |         /* (non-Javadoc) | 
| 913 |          * @see java.lang.Object#toString() | 
| 914 |          */ | 
| 915 |         public String toString() { | 
| 916 |                 if (fBundleDescription != null) { | 
| 917 |                         try { | 
| 918 |                                 StringBuffer buffer = new StringBuffer(); | 
| 919 |                                 buffer.append(fBundleDescription.toString()); | 
| 920 |                                 buffer.append(" - "); //$NON-NLS-1$ | 
| 921 |                                 buffer.append("[fragment: ").append(isFragment()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$ | 
| 922 |                                 buffer.append("[host: ").append(fBundleDescription.getFragments().length > 0).append("] "); //$NON-NLS-1$ //$NON-NLS-2$ | 
| 923 |                                 buffer.append("[system bundle: ").append(isSystemComponent()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$ | 
| 924 |                                 buffer.append("[source bundle: ").append(isSourceComponent()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$ | 
| 925 |                                 return buffer.toString(); | 
| 926 |                         } | 
| 927 |                         catch(CoreException ce) {}  | 
| 928 |                 } | 
| 929 |                 return super.toString(); | 
| 930 |         } | 
| 931 |   | 
| 932 |         /* (non-Javadoc) | 
| 933 |          * @see org.eclipse.pde.api.tools.model.component.IApiComponent#getLocation() | 
| 934 |          */ | 
| 935 |         public String getLocation() { | 
| 936 |                 return fLocation; | 
| 937 |         } | 
| 938 |   | 
| 939 |         /* (non-Javadoc) | 
| 940 |          * @see org.eclipse.pde.api.tools.model.component.IApiComponent#isSystemComponent() | 
| 941 |          */ | 
| 942 |         public boolean isSystemComponent() { | 
| 943 |                 return false; | 
| 944 |         } | 
| 945 |          | 
| 946 |         /** | 
| 947 |          * Returns a boolean option from the map or the default value if not present. | 
| 948 |          *  | 
| 949 |          * @param options option map | 
| 950 |          * @param optionName option name | 
| 951 |          * @param defaultValue default value for option if not present | 
| 952 |          * @return boolean value | 
| 953 |          */ | 
| 954 |         protected boolean getBooleanOption(Map options, String optionName, boolean defaultValue) { | 
| 955 |                 Boolean optionB = (Boolean)options.get(optionName); | 
| 956 |                 if (optionB != null) { | 
| 957 |                         return optionB.booleanValue(); | 
| 958 |                 } | 
| 959 |                 return defaultValue; | 
| 960 |         } | 
| 961 |          | 
| 962 |         /* (non-Javadoc) | 
| 963 |          * @see IApiComponent#isSourceComponent() | 
| 964 |          */ | 
| 965 |         public synchronized boolean isSourceComponent() throws CoreException { | 
| 966 |                 if (this.fManifest == null) { | 
| 967 |                         baselineDisposed(getBaseline()); | 
| 968 |                 } | 
| 969 |                 ManifestElement[] sourceBundle = null; | 
| 970 |                 try { | 
| 971 |                         sourceBundle = ManifestElement.parseHeader(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE, (String) fManifest.get(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE)); | 
| 972 |                 } catch (BundleException e) { | 
| 973 |                         // ignore | 
| 974 |                 } | 
| 975 |                 if (sourceBundle != null) { | 
| 976 |                         // this is a source bundle with the new format | 
| 977 |                         return true; | 
| 978 |                 } | 
| 979 |                 // check for the old format | 
| 980 |                 String pluginXMLContents = readFileContents(IApiCoreConstants.PLUGIN_XML_NAME,new File(getLocation())); | 
| 981 |                 if (pluginXMLContents != null) { | 
| 982 |                         if (containsSourceExtensionPoint(pluginXMLContents)) { | 
| 983 |                                 return true; | 
| 984 |                         } | 
| 985 |                 } | 
| 986 |                 // check if it contains a fragment.xml with the appropriate extension point | 
| 987 |                 pluginXMLContents = readFileContents(IApiCoreConstants.FRAGMENT_XML_NAME,new File(getLocation())); | 
| 988 |                 if (pluginXMLContents != null) { | 
| 989 |                         if (containsSourceExtensionPoint(pluginXMLContents)) { | 
| 990 |                                 return true; | 
| 991 |                         } | 
| 992 |                 } | 
| 993 |                 // parse XML contents to find extension points | 
| 994 |                 return false; | 
| 995 |         } | 
| 996 |   | 
| 997 |         /** | 
| 998 |          * Check if the given source contains an source extension point. | 
| 999 |          *  | 
| 1000 |          * @param pluginXMLContents the given file contents | 
| 1001 |          * @return true if it contains a source extension point, false otherwise | 
| 1002 |          */ | 
| 1003 |         private boolean containsSourceExtensionPoint(String pluginXMLContents) { | 
| 1004 |                 SAXParserFactory factory = null; | 
| 1005 |                 try { | 
| 1006 |                         factory = SAXParserFactory.newInstance(); | 
| 1007 |                 } catch (FactoryConfigurationError e) { | 
| 1008 |                         return false; | 
| 1009 |                 } | 
| 1010 |                 SAXParser saxParser = null; | 
| 1011 |                 try { | 
| 1012 |                         saxParser = factory.newSAXParser(); | 
| 1013 |                 } catch (ParserConfigurationException e) { | 
| 1014 |                         // ignore | 
| 1015 |                 } catch (SAXException e) { | 
| 1016 |                         // ignore | 
| 1017 |                 } | 
| 1018 |   | 
| 1019 |                 if (saxParser == null) { | 
| 1020 |                         return false; | 
| 1021 |                 } | 
| 1022 |   | 
| 1023 |                 // Parse | 
| 1024 |                 InputSource inputSource = new InputSource(new BufferedReader(new StringReader(pluginXMLContents))); | 
| 1025 |                 try { | 
| 1026 |                         SourceDefaultHandler defaultHandler = new SourceDefaultHandler(); | 
| 1027 |                         saxParser.parse(inputSource, defaultHandler); | 
| 1028 |                         return defaultHandler.isSource(); | 
| 1029 |                 } catch (SAXException e) { | 
| 1030 |                         // ignore | 
| 1031 |                 } catch (IOException e) { | 
| 1032 |                         // ignore | 
| 1033 |                 } | 
| 1034 |                 return false; | 
| 1035 |         }         | 
| 1036 |   | 
| 1037 |         /* (non-Javadoc) | 
| 1038 |          * @see org.eclipse.pde.api.tools.IApiComponent#isFragment() | 
| 1039 |          */ | 
| 1040 |         public synchronized boolean isFragment() throws CoreException { | 
| 1041 |                 if (this.fBundleDescription == null) { | 
| 1042 |                         baselineDisposed(getBaseline()); | 
| 1043 |                 } | 
| 1044 |                 return fBundleDescription.getHost() != null; | 
| 1045 |         } | 
| 1046 |   | 
| 1047 |         /** | 
| 1048 |          * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getHost() | 
| 1049 |          */ | 
| 1050 |         public synchronized IApiComponent getHost() throws CoreException { | 
| 1051 |                 if (this.fBundleDescription == null) { | 
| 1052 |                         baselineDisposed(getBaseline()); | 
| 1053 |                 } | 
| 1054 |                 HostSpecification host = fBundleDescription.getHost(); | 
| 1055 |                 if(host != null) { | 
| 1056 |                         return getBaseline().getApiComponent(host.getName()); | 
| 1057 |                 } | 
| 1058 |                 return null; | 
| 1059 |         } | 
| 1060 |          | 
| 1061 |         /* (non-Javadoc) | 
| 1062 |          * @see org.eclipse.pde.api.tools.IApiComponent#hasFragments() | 
| 1063 |          */ | 
| 1064 |         public synchronized boolean hasFragments() throws CoreException { | 
| 1065 |                 if (this.fBundleDescription == null) { | 
| 1066 |                         baselineDisposed(getBaseline()); | 
| 1067 |                 } | 
| 1068 |                 return fBundleDescription.getFragments().length != 0; | 
| 1069 |         } | 
| 1070 |          | 
| 1071 |         /** | 
| 1072 |          * Sets whether this bundle has an underlying API description file. | 
| 1073 |          *  | 
| 1074 |          * @param hasApiDescription whether this bundle has an underlying API description file | 
| 1075 |          */ | 
| 1076 |         protected void setHasApiDescription(boolean hasApiDescription) { | 
| 1077 |                 fHasApiDescription = hasApiDescription; | 
| 1078 |         } | 
| 1079 |          | 
| 1080 |         /* (non-Javadoc) | 
| 1081 |          * @see org.eclipse.pde.api.tools.internal.provisional.IApiComponent#hasApiDescription() | 
| 1082 |          */ | 
| 1083 |         public boolean hasApiDescription() { | 
| 1084 |                 // ensure initialized | 
| 1085 |                 try { | 
| 1086 |                         getApiDescription(); | 
| 1087 |                 } catch (CoreException e) { | 
| 1088 |                 } | 
| 1089 |                 return fHasApiDescription; | 
| 1090 |         } | 
| 1091 |          | 
| 1092 |         /* (non-Javadoc) | 
| 1093 |          * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getLowestEEs() | 
| 1094 |          */ | 
| 1095 |         public String[] getLowestEEs() throws CoreException { | 
| 1096 |                 if (this.lowestEEs != null) return this.lowestEEs; | 
| 1097 |                 String[] temp = null; | 
| 1098 |                 String[] executionEnvironments = this.getExecutionEnvironments(); | 
| 1099 |                 int length = executionEnvironments.length; | 
| 1100 |                 switch(length) { | 
| 1101 |                         case 0 : | 
| 1102 |                                 return null; | 
| 1103 |                         case 1 : | 
| 1104 |                                 temp = new String[] { executionEnvironments[0] }; | 
| 1105 |                                 break; | 
| 1106 |                         default : | 
| 1107 |                                 int values = ProfileModifiers.NO_PROFILE_VALUE; | 
| 1108 |                                 for (int i = 0; i < length; i++) { | 
| 1109 |                                         values |= ProfileModifiers.getValue(executionEnvironments[i]); | 
| 1110 |                                 } | 
| 1111 |                                 if (ProfileModifiers.isJRE(values)) { | 
| 1112 |                                         if (ProfileModifiers.isJRE_1_1(values)) { | 
| 1113 |                                                 temp = new String[] { ProfileModifiers.JRE_1_1_NAME }; | 
| 1114 |                                         } else if (ProfileModifiers.isJ2SE_1_2(values)) { | 
| 1115 |                                                 temp = new String[] { ProfileModifiers.J2SE_1_2_NAME }; | 
| 1116 |                                         } else if (ProfileModifiers.isJ2SE_1_3(values)) { | 
| 1117 |                                                 temp = new String[] { ProfileModifiers.J2SE_1_3_NAME }; | 
| 1118 |                                         } else if (ProfileModifiers.isJ2SE_1_4(values)) { | 
| 1119 |                                                 temp = new String[] { ProfileModifiers.J2SE_1_4_NAME }; | 
| 1120 |                                         } else if (ProfileModifiers.isJ2SE_1_5(values)) { | 
| 1121 |                                                 temp = new String[] { ProfileModifiers.J2SE_1_5_NAME }; | 
| 1122 |                                         } else { | 
| 1123 |                                                 // this is 1.6 | 
| 1124 |                                                 temp = new String[] { ProfileModifiers.JAVASE_1_6_NAME }; | 
| 1125 |                                         } | 
| 1126 |                                 } | 
| 1127 |                                 if (ProfileModifiers.isCDC_Foundation(values)) { | 
| 1128 |                                         if (ProfileModifiers.isCDC_1_0_FOUNDATION_1_0(values)) { | 
| 1129 |                                                 if (temp != null) { | 
| 1130 |                                                         temp = new String[] { temp[0], ProfileModifiers.CDC_1_0_FOUNDATION_1_0_NAME }; | 
| 1131 |                                                 } else { | 
| 1132 |                                                         temp = new String[] { ProfileModifiers.CDC_1_0_FOUNDATION_1_0_NAME }; | 
| 1133 |                                                 } | 
| 1134 |                                         } else { | 
| 1135 |                                                 if (temp != null) { | 
| 1136 |                                                         temp = new String[] { temp[0], ProfileModifiers.CDC_1_1_FOUNDATION_1_1_NAME }; | 
| 1137 |                                                 } else { | 
| 1138 |                                                         temp = new String[] { ProfileModifiers.CDC_1_1_FOUNDATION_1_1_NAME }; | 
| 1139 |                                                 } | 
| 1140 |                                         } | 
| 1141 |                                 } | 
| 1142 |                                 if (ProfileModifiers.isOSGi(values)) { | 
| 1143 |                                         if (ProfileModifiers.isOSGI_MINIMUM_1_0(values)) { | 
| 1144 |                                                 if (temp != null) { | 
| 1145 |                                                         int tempLength = temp.length; | 
| 1146 |                                                         System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength); | 
| 1147 |                                                         temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_0_NAME; | 
| 1148 |                                                 } else { | 
| 1149 |                                                         temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_0_NAME }; | 
| 1150 |                                                 } | 
| 1151 |                                         } else if (ProfileModifiers.isOSGI_MINIMUM_1_1(values)) { | 
| 1152 |                                                 if (temp != null) { | 
| 1153 |                                                         int tempLength = temp.length; | 
| 1154 |                                                         System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength); | 
| 1155 |                                                         temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_1_NAME; | 
| 1156 |                                                 } else { | 
| 1157 |                                                         temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_1_NAME }; | 
| 1158 |                                                 } | 
| 1159 |                                         } else { | 
| 1160 |                                                 // OSGI_MINIMUM_1_2 | 
| 1161 |                                                 if (temp != null) { | 
| 1162 |                                                         int tempLength = temp.length; | 
| 1163 |                                                         System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength); | 
| 1164 |                                                         temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_2_NAME; | 
| 1165 |                                                 } else { | 
| 1166 |                                                         temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_2_NAME }; | 
| 1167 |                                                 } | 
| 1168 |                                         } | 
| 1169 |                                 } | 
| 1170 |                 } | 
| 1171 |                 this.lowestEEs = temp; | 
| 1172 |                 return temp; | 
| 1173 |         } | 
| 1174 |          | 
| 1175 |         /* (non-Javadoc) | 
| 1176 |          * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getErrors() | 
| 1177 |          */ | 
| 1178 |         public synchronized ResolverError[] getErrors() throws CoreException { | 
| 1179 |                 ApiBaseline baseline = (ApiBaseline) getBaseline(); | 
| 1180 |                 if (this.fBundleDescription == null) { | 
| 1181 |                         baselineDisposed(baseline); | 
| 1182 |                 } | 
| 1183 |                 if (baseline != null) { | 
| 1184 |                         ResolverError[] resolverErrors = baseline.getState().getResolverErrors(this.fBundleDescription); | 
| 1185 |                         if (resolverErrors.length == 0) { | 
| 1186 |                                 return null; | 
| 1187 |                         } | 
| 1188 |                         return resolverErrors; | 
| 1189 |                 } | 
| 1190 |                 return null; | 
| 1191 |         } | 
| 1192 |          | 
| 1193 |         /** | 
| 1194 |          * @param baseline the baseline that is disposed | 
| 1195 |          * @throws CoreException with the baseline disposed information | 
| 1196 |          */ | 
| 1197 |         protected void baselineDisposed(IApiBaseline baseline) throws CoreException { | 
| 1198 |                 throw new CoreException( | 
| 1199 |                                 new Status( | 
| 1200 |                                                 IStatus.ERROR, | 
| 1201 |                                                 ApiPlugin.PLUGIN_ID, | 
| 1202 |                                                 ApiPlugin.REPORT_BASELINE_IS_DISPOSED, | 
| 1203 |                                                 NLS.bind(Messages.BundleApiComponent_baseline_disposed, baseline.getName()), | 
| 1204 |                                                 null)); | 
| 1205 |         } | 
| 1206 | } |