BundleApiComponent.java :  » IDE-Eclipse » pde » org » eclipse » pde » api » tools » internal » model » Java Open Source

Java Open Source » IDE Eclipse » pde 
pde » org » eclipse » pde » api » tools » internal » model » BundleApiComponent.java
/*******************************************************************************
 * Copyright (c) 2007, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.api.tools.internal.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.BundleSpecification;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.service.resolver.HostSpecification;
import org.eclipse.osgi.service.resolver.ResolverError;
import org.eclipse.osgi.service.resolver.StateObjectFactory;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.pde.api.tools.internal.ApiBaselineManager;
import org.eclipse.pde.api.tools.internal.ApiDescription;
import org.eclipse.pde.api.tools.internal.ApiDescriptionProcessor;
import org.eclipse.pde.api.tools.internal.BundleVersionRange;
import org.eclipse.pde.api.tools.internal.CompositeApiDescription;
import org.eclipse.pde.api.tools.internal.IApiCoreConstants;
import org.eclipse.pde.api.tools.internal.RequiredComponentDescription;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.Factory;
import org.eclipse.pde.api.tools.internal.provisional.IApiAccess;
import org.eclipse.pde.api.tools.internal.provisional.IApiDescription;
import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
import org.eclipse.pde.api.tools.internal.provisional.IRequiredComponentDescription;
import org.eclipse.pde.api.tools.internal.provisional.ProfileModifiers;
import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IPackageDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
import org.eclipse.pde.api.tools.internal.util.SourceDefaultHandler;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.eclipse.pde.internal.core.TargetWeaver;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Implementation of an API component based on a bundle in the file system.
 * 
 * @since 1.0.0
 */
public class BundleApiComponent extends AbstractApiComponent {
  
  /**
   * Dictionary parsed from MANIFEST.MF
   */
  private Dictionary fManifest;
  
  /**
   * Manifest headers that are maintained after {@link BundleDescription} creation.
   * Only these headers are maintained in the manifest dictionary to reduce footprint.  
   */
  private static final String[] MANIFEST_HEADERS = new String[] {
    IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE,
    Constants.BUNDLE_CLASSPATH,
    Constants.BUNDLE_NAME,
    Constants.BUNDLE_VERSION
  };
  
  /**
   * Whether there is an underlying .api_description file
   */
  private boolean fHasApiDescription = false;
  
  /**
   * Root location of component in the file system
   */
  private String fLocation;
  
  /**
   * Underlying bundle description (OSGi model of a bundle)
   */
  private BundleDescription fBundleDescription;
  
  /**
   * Cached value for the lowest EEs
   */
  private String[] lowestEEs;

  /**
   * Constructs a new API component from the specified location in the file system
   * in the given profile.
   * 
   * @param profile owning profile
   * @param location directory or jar file
   * @exception CoreException if unable to create a component from the specified location
   */
  public BundleApiComponent(IApiBaseline profile, String location) throws CoreException {
    super(profile);
    fLocation = location;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.descriptors.AbstractApiComponent#dispose()
   */
  public void dispose() {
    try {
      super.dispose();
    } finally {
      synchronized(this) {
        fManifest = null;
        fBundleDescription = null;
      }
    }
  }
  
  /**
   * Returns this bundle's manifest as a dictionary.
   * 
   * @return manifest dictionary
   * @exception CoreException if something goes terribly wrong
   */
  protected synchronized Dictionary getManifest() throws CoreException {
    if(fManifest == null) {
      try {
        fManifest = (Dictionary) loadManifest(new File(fLocation));
      } catch (IOException e) {
        abort("Unable to load manifest due to IO error", e); //$NON-NLS-1$
      }
    }
    return fManifest;
  }

  /**
   * Reduce the manifest to only contain required headers after {@link BundleDescription} creation.
   */
  protected synchronized void doManifestCompaction() throws CoreException {
    Dictionary temp = fManifest;
    fManifest = new Hashtable(MANIFEST_HEADERS.length);
    for (int i = 0; i < MANIFEST_HEADERS.length; i++) {
      String header = MANIFEST_HEADERS[i];
      Object value = temp.get(header);
      if (value != null) {
        fManifest.put(header, value);
      }
    }
  }
  
  /**
   * Returns if the bundle at the specified location is a valid bundle or not.
   * Validity is determined via the existence of a readable manifest file
   * @param location
   * @return true if the bundle at the given location is valid false otherwise
   * @throws IOException
   */
  public boolean isValidBundle() throws CoreException {
    Dictionary manifest = getManifest();
    return manifest != null && (manifest.get(Constants.BUNDLE_NAME) != null && manifest.get(Constants.BUNDLE_VERSION) != null);
  }
  
  /**
   * Initializes component state from the underlying bundle for the given
   * state.
   * 
   * @param state PDE state
   * @throws CoreException on failure
   */
  protected synchronized void init(long bundleId) throws CoreException {
    try {
      Dictionary manifest = getManifest();
      if (isBinaryBundle() && ApiBaselineManager.WORKSPACE_API_BASELINE_ID.equals(getBaseline().getName())) {
        // must account for bundles in development mode - look for class files in output
        // folders rather than jars
        TargetWeaver.weaveManifest(manifest);
      }
      StateObjectFactory factory = StateObjectFactory.defaultFactory;
      fBundleDescription = factory.createBundleDescription(((ApiBaseline)getBaseline()).getState(), manifest, fLocation, bundleId);
      setName((String)getManifest().get(Constants.BUNDLE_NAME));
    } catch (BundleException e) {
      abort("Unable to create API component from specified location: " + fLocation, e); //$NON-NLS-1$
    }
    // compact manifest after initialization - only keep used headers
    doManifestCompaction();
  }
  
  /**
   * Returns whether this API component represents a binary bundle versus a project bundle.
   * 
   * @return whether this API component represents a binary bundle
   */
  protected boolean isBinaryBundle() {
    return true;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.AbstractApiComponent#createApiDescription()
   */
  protected IApiDescription createApiDescription() throws CoreException {
    BundleDescription[] fragments = getBundleDescription().getFragments();
    if (fragments.length == 0) {
      return createLocalApiDescription();
    }
    // build a composite description
    IApiDescription[] descriptions = new IApiDescription[fragments.length + 1];
    for (int i = 0; i < fragments.length; i++) {
      BundleDescription fragment = fragments[i];
      BundleApiComponent component = (BundleApiComponent) getBaseline().getApiComponent(fragment.getSymbolicName());
      descriptions[i + 1] = component.getApiDescription();
    }
    descriptions[0] = createLocalApiDescription();
    return new CompositeApiDescription(descriptions);
  }

  /**
   * Creates and returns this component's API description based on packages
   * supplied by this component, exported packages, and associated directives.
   * 
   * @return API description
   * @throws CoreException if unable to initialize 
   */
  protected IApiDescription createLocalApiDescription() throws CoreException {
    IApiDescription apiDesc = new ApiDescription(getId());
    // first mark all packages as internal
    initializeApiDescription(apiDesc, getBundleDescription(), getLocalPackageNames());
    try {
      String xml = loadApiDescription(new File(fLocation));
      setHasApiDescription(xml != null);
      if (xml != null) {
        ApiDescriptionProcessor.annotateApiSettings(null, apiDesc, xml);
      }
    } catch (IOException e) {
      abort("Unable to load .api_description file ", e); //$NON-NLS-1$
    }
    return apiDesc;
  }
  
  /**
   * Returns the names of all packages that originate from this bundle.
   * Does not include packages that originate from fragments or a host.
   * 
   * @return local package names
   * @throws CoreException
   */
  protected Set getLocalPackageNames() throws CoreException {
    Set names = new HashSet();
    IApiTypeContainer[] containers = getApiTypeContainers();
    IApiComponent comp = null;
    for (int i = 0; i < containers.length; i++) {
      comp = (IApiComponent) containers[i].getAncestor(IApiElement.COMPONENT);
      if (comp != null && comp.getId().equals(getId())) {
        String[] packageNames = containers[i].getPackageNames();
        for (int j = 0; j < packageNames.length; j++) {
          names.add(packageNames[j]);
        }
      }
    }
    return names;
  }  
  

  /**
   * Initializes the given API description based on package exports in the manifest.
   * The API description for a bundle only contains packages that originate from
   * this bundle (so a host will not contain API descriptions for packages that
   * originate from fragments). However, a host's API description will be represented
   * by a proxy that delegates to the host and all of its fragments to provide
   * a complete description of the host.
   * 
   * @param apiDesc API description to initialize
   * @param bundle the bundle to load from
   * @param packages the complete set of packages names originating from the backing
   *     component
   * @throws CoreException if an error occurs
   */
  public static void initializeApiDescription(IApiDescription apiDesc, BundleDescription bundle, Set packages) throws CoreException {
    Iterator iterator = packages.iterator();
    while (iterator.hasNext()) {
      String name = (String) iterator.next();
      apiDesc.setVisibility(Factory.packageDescriptor(name), VisibilityModifiers.PRIVATE);
    }
    // then process exported packages that originate from this bundle
    // considering host and fragment package exports
    List supplied = new ArrayList();
    ExportPackageDescription[] exportPackages = bundle.getExportPackages();
    addSuppliedPackages(packages, supplied, exportPackages);
    HostSpecification host = bundle.getHost();
    if (host != null) {
      BundleDescription[] hosts = host.getHosts();
      for (int i = 0; i < hosts.length; i++) {
        addSuppliedPackages(packages, supplied, hosts[i].getExportPackages());
      }
    }
    BundleDescription[] fragments = bundle.getFragments();
    for (int i = 0; i < fragments.length; i++) {
      addSuppliedPackages(packages, supplied, fragments[i].getExportPackages());
    }
    
    annotateExportedPackages(apiDesc, (ExportPackageDescription[]) supplied.toArray(new ExportPackageDescription[supplied.size()]));
  }

  /**
   * Adds package exports to the given list if the associated package originates
   * from this bundle.
   *   
   * @param packages names of packages supplied by this bundle
   * @param supplied list to append package exports to
   * @param exportPackages package exports to consider
   */
  protected static void addSuppliedPackages(Set packages, List supplied, ExportPackageDescription[] exportPackages) {
    for (int i = 0; i < exportPackages.length; i++) {
      ExportPackageDescription pkg = exportPackages[i];
      String name = pkg.getName();
      if (name.equals(".")) { //$NON-NLS-1$
        // translate . to default package
        name = Util.DEFAULT_PACKAGE_NAME;
      }
      if (packages.contains(name)) {
        supplied.add(pkg);
      }
    }
  }
  
  /**
   * Annotates the API description with exported packages.
   * 
   * @param apiDesc description to annotate
   * @param exportedPackages packages that are exported
   */
  protected static void annotateExportedPackages(IApiDescription apiDesc, ExportPackageDescription[] exportedPackages) {
    for(int i = 0; i < exportedPackages.length; i++) {
      ExportPackageDescription pkg = exportedPackages[i];
      boolean internal = ((Boolean) pkg.getDirective("x-internal")).booleanValue(); //$NON-NLS-1$
      String[] friends = (String[]) pkg.getDirective("x-friends"); //$NON-NLS-1$
      String pkgName = pkg.getName();
      if (pkgName.equals(".")) { //$NON-NLS-1$
        // default package
        pkgName = ""; //$NON-NLS-1$
      }
      IPackageDescriptor pkgDesc = Factory.packageDescriptor(pkgName);
      if(internal) {
        apiDesc.setVisibility(pkgDesc, VisibilityModifiers.PRIVATE);
      }
      if (friends != null) {
        apiDesc.setVisibility(pkgDesc, VisibilityModifiers.PRIVATE);
        for(int j = 0; j < friends.length; j++) {
          //annotate the api description for x-friends access levels
          apiDesc.setAccessLevel(
              Factory.componentDescriptor(friends[j]), 
              Factory.packageDescriptor(pkgName), 
              IApiAccess.FRIEND);
        }
      }
      if (!internal && friends == null) {
        //there could have been directives that have nothing to do with
        //visibility, so we need to add the package as API in that case
        apiDesc.setVisibility(pkgDesc, VisibilityModifiers.API);
      }
    }
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.AbstractApiComponent#createApiFilterStore()
   */
  protected IApiFilterStore createApiFilterStore() throws CoreException {
    //always return a new empty store since we do not support filtering from bundles
    return null;
  }
  
  /**
   * @see org.eclipse.pde.api.tools.internal.AbstractApiTypeContainer#createApiTypeContainers()
   */
  protected synchronized List createApiTypeContainers() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    List containers = new ArrayList(5);
    try {
      List all = new ArrayList();
      // build the classpath from bundle and all fragments
      all.add(this);
      boolean considerFragments = true;
      if (Util.ORG_ECLIPSE_SWT.equals(getId())) {
        // if SWT is a project to be built/analyzed don't consider its fragments
        considerFragments = !isApiEnabled();
      }
      if (considerFragments) { 
        BundleDescription[] fragments = fBundleDescription.getFragments();
        for (int i = 0; i < fragments.length; i++) {
          BundleDescription fragment = fragments[i];
          BundleApiComponent component = (BundleApiComponent) getBaseline().getApiComponent(fragment.getSymbolicName());
          if (component != null) {
            // force initialization of the fragment so we can retrieve its class file containers
            component.getApiTypeContainers();
            all.add(component);
          }
        }
      }
      Iterator iterator = all.iterator();
      Set entryNames = new HashSet(5);
      BundleApiComponent other = null;
      while (iterator.hasNext()) {
        BundleApiComponent component = (BundleApiComponent) iterator.next();
        String[] paths = getClasspathEntries(component.getManifest());
        for (int i = 0; i < paths.length; i++) {
          String path = paths[i];
          // don't re-process the same entry twice (except default entries ".")
          if (!(".".equals(path))) { //$NON-NLS-1$
            if (entryNames.contains(path)) {
              continue;
            }
          }
          IApiTypeContainer container = component.createApiTypeContainer(path);
          if (container == null) {
            for(Iterator iter = all.iterator(); iter.hasNext();) {
              other = (BundleApiComponent) iter.next();
              if (other != component) {
                container = other.createApiTypeContainer(path);
              }
            }
          }
          if (container != null) {
            containers.add(container);
            if (!(".".equals(path))) { //$NON-NLS-1$
              entryNames.add(path);
            }
          }
        }
      }
    } catch (BundleException e) {
      abort("Unable to parse bundle classpath", e); //$NON-NLS-1$
    } catch (IOException e) {
      abort("Unable to initialize class file containers", e); //$NON-NLS-1$
    }
    return containers;
  }
  
  /**
   * Returns whether this API component is enabled for API analysis by the API builder.
   * 
   * @return whether this API component is enabled for API analysis by the API builder.
   */
  protected boolean isApiEnabled() {
    return false;
  }
  
  /**
   * Returns classpath entries defined in the given manifest.
   * 
   * @param manifest
   * @return classpath entries as bundle relative paths
   * @throws BundleException
   */
  protected String[] getClasspathEntries(Dictionary manifest) throws BundleException {
    ManifestElement[] classpath = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, (String) manifest.get(Constants.BUNDLE_CLASSPATH));
    String elements[] = null;
    if (classpath == null) {
      // default classpath is '.'
      elements = new String[]{"."}; //$NON-NLS-1$
    } else {
      elements = new String[classpath.length];
      for (int i = 0; i < classpath.length; i++) {
        elements[i] = classpath[i].getValue();
      }
    }
    return elements;
  }
  
  /**
   * Creates and returns an {@link IApiTypeContainer} at the specified path in
   * this bundle, or <code>null</code> if the {@link IApiTypeContainer} does not
   * exist. The path is the name (path) of entries specified by the
   * <code>Bundle-ClassPath:</code> header.
   * 
   * @param path relative path to a class file container in this bundle
   * @return {@link IApiTypeContainer} or <code>null</code>
   * @exception IOException
   */
  protected IApiTypeContainer createApiTypeContainer(String path) throws IOException, CoreException {
    File bundle = new File(fLocation);
    if (bundle.isDirectory()) {
      // bundle is folder
      File entry = new File(bundle, path);
      if (entry.exists()) {
        if (entry.isFile()) {
          return new ArchiveApiTypeContainer(this, entry.getCanonicalPath());
        } else {
          return new DirectoryApiTypeContainer(this, entry.getCanonicalPath());
        }
      }
    } else {
      // bundle is jar'd
      ZipFile zip = null;
      try {
        if (path.equals(".")) { //$NON-NLS-1$
          return new ArchiveApiTypeContainer(this, fLocation);
        } else {
          // TODO: use temporary space from OSGi if in a framework
          zip = new ZipFile(fLocation);
          ZipEntry entry = zip.getEntry(path);
          if (entry != null) {
            InputStream inputStream = null;
            File tempFile;
            FileOutputStream outputStream = null;
            try {
              inputStream = zip.getInputStream(entry);
              tempFile = File.createTempFile("api", "tmp"); //$NON-NLS-1$ //$NON-NLS-2$
              tempFile.deleteOnExit();
              outputStream = new FileOutputStream(tempFile);
              byte[] bytes = new byte[8096];
              while (inputStream.available() > 0) {
                int read = inputStream.read(bytes);
                if (read > 0) {
                  outputStream.write(bytes, 0, read);
                }
              }
            } finally {
              if (inputStream != null) {
                try {
                  inputStream.close();
                } catch(IOException e) {
                  ApiPlugin.log(e);
                }
              }
              if (outputStream != null) {
                try {
                  outputStream.close();
                } catch(IOException e) {
                  ApiPlugin.log(e);
                }
              }
            }
            return new ArchiveApiTypeContainer(this, tempFile.getCanonicalPath());
          }
        }
      } finally {
        if (zip != null) {
          zip.close();
        }
      }
    }
    return null;
  }
    
  /**
   * Parses a bunlde's manifest into a dictionary. The bundle may be in a jar
   * or in a directory at the specified location.
   * 
   * @param bundleLocation root location of the bundle
   * @return bundle manifest dictionary or <code>null</code> if none
   * @throws IOException if unable to parse
   */
  protected Map loadManifest(File bundleLocation) throws IOException {
    ZipFile jarFile = null;
    InputStream manifestStream = null;
    String extension = new Path(bundleLocation.getName()).getFileExtension();
    try {
      if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
        jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ);
        ZipEntry manifestEntry = jarFile.getEntry(JarFile.MANIFEST_NAME);
        if (manifestEntry != null) {
          manifestStream = jarFile.getInputStream(manifestEntry);
        }
      } else {
        File file = new File(bundleLocation, JarFile.MANIFEST_NAME);
        if (file.exists())
          manifestStream = new FileInputStream(file);
      }
      if (manifestStream == null) {
        return null;
      }
      return ManifestElement.parseBundleManifest(manifestStream, new Hashtable(10));
    } catch (BundleException e) {
      ApiPlugin.log(e);
    } finally {
      closingZipFileAndStream(manifestStream, jarFile);
    }
    return null;
  }
  
  /**
   * Reads and returns this bunlde's manifest in a Manifest object.
   * The bundle may be in a jar or in a directory at the specified location.
   * 
   * @param bundleLocation root location of the bundle
   * @return manifest or <code>null</code> if not present
   * @throws IOException if unable to parse
   */
  protected Manifest readManifest(File bundleLocation) throws IOException {
    ZipFile jarFile = null;
    InputStream manifestStream = null;
    try {
      String extension = new Path(bundleLocation.getName()).getFileExtension();
      if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
        jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ);
        ZipEntry manifestEntry = jarFile.getEntry(JarFile.MANIFEST_NAME);
        if (manifestEntry != null) {
          manifestStream = jarFile.getInputStream(manifestEntry);
        }
      } else {
        File file = new File(bundleLocation, JarFile.MANIFEST_NAME);
        if (file.exists())
          manifestStream = new FileInputStream(file);
      }
      if (manifestStream == null) {
        return null;
      }
      return new Manifest(manifestStream);
    } finally {
      closingZipFileAndStream(manifestStream, jarFile);
    }
  }

  void closingZipFileAndStream(InputStream stream, ZipFile jarFile) {
    try {
      if (stream != null) {
        stream.close();
      }
    } catch (IOException e) {
      ApiPlugin.log(e);
    }
    try {
      if (jarFile != null) {
        jarFile.close();
      }
    } catch (IOException e) {
      ApiPlugin.log(e);
    }
  }
  
  /**
   * Reads and returns the file contents corresponding to the given file name.
   * The bundle may be in a jar or in a directory at the specified location.
   * 
   * @param xmlFileName the given file name
   * @param bundleLocation the root location of the bundle
   * @return the file contents or <code>null</code> if not present
   */
  protected String readFileContents(String xmlFileName, File bundleLocation) {
    ZipFile jarFile = null;
    InputStream stream = null;
    try {
      String extension = new Path(bundleLocation.getName()).getFileExtension();
      if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
        jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ);
        ZipEntry manifestEntry = jarFile.getEntry(xmlFileName);
        if (manifestEntry != null) {
          stream = jarFile.getInputStream(manifestEntry);
        }
      } else {
        File file = new File(bundleLocation, xmlFileName);
        if (file.exists()) {
          stream = new FileInputStream(file);
        }
      }
      if (stream == null) {
        return null;
      }
      return new String(Util.getInputStreamAsCharArray(stream, -1, IApiCoreConstants.UTF_8));
    } catch(IOException e) {
      //TODO abort
      ApiPlugin.log(e);
    } finally {
      closingZipFileAndStream(stream, jarFile);
    }
    return null;
  }

  /**
   * Parses a bundle's .api_description XML into a string. The file may be in a jar
   * or in a directory at the specified location.
   * 
   * @param bundleLocation root location of the bundle
   * @return API description XML as a string or <code>null</code> if none
   * @throws IOException if unable to parse
   */
  protected String loadApiDescription(File bundleLocation) throws IOException {
    ZipFile jarFile = null;
    InputStream stream = null;
    String contents = null;
    try {
      String extension = new Path(bundleLocation.getName()).getFileExtension();
      if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
        jarFile = new ZipFile(bundleLocation, ZipFile.OPEN_READ);
        ZipEntry manifestEntry = jarFile.getEntry(IApiCoreConstants.API_DESCRIPTION_XML_NAME);
        if (manifestEntry != null) {
          // new file is present
          stream = jarFile.getInputStream(manifestEntry);
        }
      } else {
        File file = new File(bundleLocation, IApiCoreConstants.API_DESCRIPTION_XML_NAME);
        if (file.exists()) {
          // use new file
          stream = new FileInputStream(file);
        }
      }
      if (stream == null) {
        return null;
      }
      char[] charArray = Util.getInputStreamAsCharArray(stream, -1, IApiCoreConstants.UTF_8);
      contents = new String(charArray);
    } finally {
      closingZipFileAndStream(stream, jarFile);
    }
    return contents;
  }
  
  
  /**
   * Returns a URL describing a file inside a bundle.
   * 
   * @param bundleLocation root location of the bundle. May be a
   *  directory or a file (jar)
   * @param filePath bundle relative path to desired file
   * @return URL to the file
   * @throws MalformedURLException 
   */
  protected URL getFileInBundle(File bundleLocation, String filePath) throws MalformedURLException {
    String extension = new Path(bundleLocation.getName()).getFileExtension();
    StringBuffer urlSt = new StringBuffer();
    if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
      urlSt.append("jar:file:"); //$NON-NLS-1$
      urlSt.append(bundleLocation.getAbsolutePath());
      urlSt.append("!/"); //$NON-NLS-1$
      urlSt.append(filePath);
    } else {
      urlSt.append("file:"); //$NON-NLS-1$
      urlSt.append(bundleLocation.getAbsolutePath());
      urlSt.append(File.separatorChar);
      urlSt.append(filePath);
    }  
    return new URL(urlSt.toString());
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getExecutionEnvironments()
   */
  public synchronized String[] getExecutionEnvironments() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription.getExecutionEnvironments();
  }

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getId()
   */
  public synchronized String getId() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription.getSymbolicName();
  }

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getRequiredComponents()
   */
  public synchronized IRequiredComponentDescription[] getRequiredComponents() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    BundleSpecification[] requiredBundles = fBundleDescription.getRequiredBundles();
    IRequiredComponentDescription[] req = new IRequiredComponentDescription[requiredBundles.length];
    for (int i = 0; i < requiredBundles.length; i++) {
      BundleSpecification bundle = requiredBundles[i];
      req[i] = new RequiredComponentDescription(bundle.getName(),
          new BundleVersionRange(bundle.getVersionRange()),
          bundle.isOptional(),
          bundle.isExported());
    }
    return req;
  }

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.manifest.IApiComponent#getVersion()
   */
  public synchronized String getVersion() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription.getVersion().toString();
  }
  
  /**
   * Returns this component's bundle description.
   * 
   * @return bundle description
   */
  public synchronized BundleDescription getBundleDescription() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    if (fBundleDescription != null) {
      try {
        StringBuffer buffer = new StringBuffer();
        buffer.append(fBundleDescription.toString());
        buffer.append(" - "); //$NON-NLS-1$
        buffer.append("[fragment: ").append(isFragment()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$
        buffer.append("[host: ").append(fBundleDescription.getFragments().length > 0).append("] "); //$NON-NLS-1$ //$NON-NLS-2$
        buffer.append("[system bundle: ").append(isSystemComponent()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$
        buffer.append("[source bundle: ").append(isSourceComponent()).append("] "); //$NON-NLS-1$ //$NON-NLS-2$
        return buffer.toString();
      }
      catch(CoreException ce) {} 
    }
    return super.toString();
  }

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.model.component.IApiComponent#getLocation()
   */
  public String getLocation() {
    return fLocation;
  }

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.model.component.IApiComponent#isSystemComponent()
   */
  public boolean isSystemComponent() {
    return false;
  }
  
  /**
   * Returns a boolean option from the map or the default value if not present.
   * 
   * @param options option map
   * @param optionName option name
   * @param defaultValue default value for option if not present
   * @return boolean value
   */
  protected boolean getBooleanOption(Map options, String optionName, boolean defaultValue) {
    Boolean optionB = (Boolean)options.get(optionName);
    if (optionB != null) {
      return optionB.booleanValue();
    }
    return defaultValue;
  }
  
  /* (non-Javadoc)
   * @see IApiComponent#isSourceComponent()
   */
  public synchronized boolean isSourceComponent() throws CoreException {
    if (this.fManifest == null) {
      baselineDisposed();
    }
    ManifestElement[] sourceBundle = null;
    try {
      sourceBundle = ManifestElement.parseHeader(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE, (String) fManifest.get(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE));
    } catch (BundleException e) {
      // ignore
    }
    if (sourceBundle != null) {
      // this is a source bundle with the new format
      return true;
    }
    // check for the old format
    String pluginXMLContents = readFileContents(IApiCoreConstants.PLUGIN_XML_NAME,new File(getLocation()));
    if (pluginXMLContents != null) {
      if (containsSourceExtensionPoint(pluginXMLContents)) {
        return true;
      }
    }
    // check if it contains a fragment.xml with the appropriate extension point
    pluginXMLContents = readFileContents(IApiCoreConstants.FRAGMENT_XML_NAME,new File(getLocation()));
    if (pluginXMLContents != null) {
      if (containsSourceExtensionPoint(pluginXMLContents)) {
        return true;
      }
    }
    // parse XML contents to find extension points
    return false;
  }

  /**
   * Check if the given source contains an source extension point.
   * 
   * @param pluginXMLContents the given file contents
   * @return true if it contains a source extension point, false otherwise
   */
  private boolean containsSourceExtensionPoint(String pluginXMLContents) {
    SAXParserFactory factory = null;
    try {
      factory = SAXParserFactory.newInstance();
    } catch (FactoryConfigurationError e) {
      return false;
    }
    SAXParser saxParser = null;
    try {
      saxParser = factory.newSAXParser();
    } catch (ParserConfigurationException e) {
      // ignore
    } catch (SAXException e) {
      // ignore
    }

    if (saxParser == null) {
      return false;
    }

    // Parse
    InputSource inputSource = new InputSource(new BufferedReader(new StringReader(pluginXMLContents)));
    try {
      SourceDefaultHandler defaultHandler = new SourceDefaultHandler();
      saxParser.parse(inputSource, defaultHandler);
      return defaultHandler.isSource();
    } catch (SAXException e) {
      // ignore
    } catch (IOException e) {
      // ignore
    }
    return false;
  }  

  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.IApiComponent#isFragment()
   */
  public synchronized boolean isFragment() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription.getHost() != null;
  }

  /**
   * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getHost()
   */
  public synchronized IApiComponent getHost() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    HostSpecification host = fBundleDescription.getHost();
    if(host != null) {
      return getBaseline().getApiComponent(host.getName());
    }
    return null;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.IApiComponent#hasFragments()
   */
  public synchronized boolean hasFragments() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    return fBundleDescription.getFragments().length != 0;
  }
  
  /**
   * Sets whether this bundle has an underlying API description file.
   * 
   * @param hasApiDescription whether this bundle has an underlying API description file
   */
  protected void setHasApiDescription(boolean hasApiDescription) {
    fHasApiDescription = hasApiDescription;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.provisional.IApiComponent#hasApiDescription()
   */
  public boolean hasApiDescription() {
    // ensure initialized
    try {
      getApiDescription();
    } catch (CoreException e) {
    }
    return fHasApiDescription;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getLowestEEs()
   */
  public String[] getLowestEEs() throws CoreException {
    if (this.lowestEEs != null) return this.lowestEEs;
    String[] temp = null;
    String[] executionEnvironments = this.getExecutionEnvironments();
    int length = executionEnvironments.length;
    switch(length) {
      case 0 :
        return null;
      case 1 :
        temp = new String[] { executionEnvironments[0] };
        break;
      default :
        int values = ProfileModifiers.NO_PROFILE_VALUE;
        for (int i = 0; i < length; i++) {
          values |= ProfileModifiers.getValue(executionEnvironments[i]);
        }
        if (ProfileModifiers.isJRE(values)) {
          if (ProfileModifiers.isJRE_1_1(values)) {
            temp = new String[] { ProfileModifiers.JRE_1_1_NAME };
          } else if (ProfileModifiers.isJ2SE_1_2(values)) {
            temp = new String[] { ProfileModifiers.J2SE_1_2_NAME };
          } else if (ProfileModifiers.isJ2SE_1_3(values)) {
            temp = new String[] { ProfileModifiers.J2SE_1_3_NAME };
          } else if (ProfileModifiers.isJ2SE_1_4(values)) {
            temp = new String[] { ProfileModifiers.J2SE_1_4_NAME };
          } else if (ProfileModifiers.isJ2SE_1_5(values)) {
            temp = new String[] { ProfileModifiers.J2SE_1_5_NAME };
          } else {
            // this is 1.6
            temp = new String[] { ProfileModifiers.JAVASE_1_6_NAME };
          }
        }
        if (ProfileModifiers.isCDC_Foundation(values)) {
          if (ProfileModifiers.isCDC_1_0_FOUNDATION_1_0(values)) {
            if (temp != null) {
              temp = new String[] { temp[0], ProfileModifiers.CDC_1_0_FOUNDATION_1_0_NAME };
            } else {
              temp = new String[] { ProfileModifiers.CDC_1_0_FOUNDATION_1_0_NAME };
            }
          } else {
            if (temp != null) {
              temp = new String[] { temp[0], ProfileModifiers.CDC_1_1_FOUNDATION_1_1_NAME };
            } else {
              temp = new String[] { ProfileModifiers.CDC_1_1_FOUNDATION_1_1_NAME };
            }
          }
        }
        if (ProfileModifiers.isOSGi(values)) {
          if (ProfileModifiers.isOSGI_MINIMUM_1_0(values)) {
            if (temp != null) {
              int tempLength = temp.length;
              System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength);
              temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_0_NAME;
            } else {
              temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_0_NAME };
            }
          } else if (ProfileModifiers.isOSGI_MINIMUM_1_1(values)) {
            if (temp != null) {
              int tempLength = temp.length;
              System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength);
              temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_1_NAME;
            } else {
              temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_1_NAME };
            }
          } else {
            // OSGI_MINIMUM_1_2
            if (temp != null) {
              int tempLength = temp.length;
              System.arraycopy(temp, 0, (temp = new String[tempLength + 1]), 0, tempLength);
              temp[tempLength] = ProfileModifiers.OSGI_MINIMUM_1_2_NAME;
            } else {
              temp = new String[] { ProfileModifiers.OSGI_MINIMUM_1_2_NAME };
            }
          }
        }
    }
    this.lowestEEs = temp;
    return temp;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent#getErrors()
   */
  public synchronized ResolverError[] getErrors() throws CoreException {
    if (this.fBundleDescription == null) {
      baselineDisposed();
    }
    IApiElement ancestor = getAncestor(IApiElement.BASELINE);
    if (ancestor != null) {
      if (ancestor instanceof ApiBaseline) {
        ApiBaseline baseline = (ApiBaseline) ancestor;
        ResolverError[] resolverErrors = baseline.getState().getResolverErrors(this.fBundleDescription);
        if (resolverErrors.length == 0) {
          return null;
        }
        return resolverErrors;
      }
    }
    return null;
  }
  
  /**
   * @throws CoreException with the baseline disposed information
   */
  protected void baselineDisposed() throws CoreException {
    throw new CoreException(
        new Status(
            IStatus.ERROR,
            ApiPlugin.PLUGIN_ID,
            ApiPlugin.REPORT_BASELINE_IS_DISPOSED,
            ApiPlugin.BASELINE_IS_DISPOSED,
            null));
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.