Package org.eclipse.osgi.internal.loader

Source Code of org.eclipse.osgi.internal.loader.BundleLoader

/*******************************************************************************
* Copyright (c) 2004, 2010 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.osgi.internal.loader;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.osgi.framework.adaptor.*;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.internal.core.*;
import org.eclipse.osgi.framework.internal.core.Constants;
import org.eclipse.osgi.framework.util.KeyedElement;
import org.eclipse.osgi.framework.util.KeyedHashSet;
import org.eclipse.osgi.internal.loader.buddy.PolicyHandler;
import org.eclipse.osgi.internal.resolver.StateBuilder;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.*;
import org.osgi.framework.wiring.BundleWiring;

/**
* This object is responsible for all classloader delegation for a bundle.
* It represents the loaded state of the bundle.  BundleLoader objects
* are created lazily; care should be taken not to force the creation
* of a BundleLoader unless it is necessary.
* @see org.eclipse.osgi.internal.loader.BundleLoaderProxy
*/
public class BundleLoader implements ClassLoaderDelegate {
  public final static String DEFAULT_PACKAGE = "."; //$NON-NLS-1$
  public final static String JAVA_PACKAGE = "java."; //$NON-NLS-1$
  public final static byte FLAG_IMPORTSINIT = 0x01;
  public final static byte FLAG_HASDYNAMICIMPORTS = 0x02;
  public final static byte FLAG_HASDYNAMICEIMPORTALL = 0x04;
  public final static byte FLAG_CLOSED = 0x08;
  public final static byte FLAG_LAZYTRIGGER = 0x10;

  public final static ClassContext CLASS_CONTEXT = AccessController.doPrivileged(new PrivilegedAction<ClassContext>() {
    public ClassContext run() {
      return new ClassContext();
    }
  });
  public final static ClassLoader FW_CLASSLOADER = getClassLoader(Framework.class);

  private static final int PRE_CLASS = 1;
  private static final int POST_CLASS = 2;
  private static final int PRE_RESOURCE = 3;
  private static final int POST_RESOURCE = 4;
  private static final int PRE_RESOURCES = 5;
  private static final int POST_RESOURCES = 6;
  private static final int PRE_LIBRARY = 7;
  private static final int POST_LIBRARY = 8;

  private static final boolean USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK = "true".equals(BundleLoaderProxy.secureAction.getProperty("osgi.classloader.singleThreadLoads")); //$NON-NLS-1$//$NON-NLS-2$
  private static final List<Object[]> waitingList = USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK ? new ArrayList<Object[]>(0) : null;
  private static Object lockThread;
  private static int lockCount = 0;

  /* the proxy */
  final private BundleLoaderProxy proxy;
  /* Bundle object */
  final BundleHost bundle;
  final private PolicyHandler policy;
  /* List of package names that are exported by this BundleLoader */
  final private Collection<String> exportedPackages;
  final private Collection<String> substitutedPackages;
  /* List of required bundle BundleLoaderProxy objects */
  final BundleLoaderProxy[] requiredBundles;
  /* List of indexes into the requiredBundles list of reexported bundles */
  final int[] reexportTable;
  /* cache of required package sources. Key is packagename, value is PackageSource */
  final private KeyedHashSet requiredSources;

  // note that the following non-final must be access using synchronization
  /* cache of imported packages. Key is packagename, Value is PackageSource */
  private KeyedHashSet importedSources;
  /* If not null, list of package stems to import dynamically. */
  private String[] dynamicImportPackageStems;
  /* If not null, list of package names to import dynamically. */
  private String[] dynamicImportPackages;
  /* loader flags */
  private byte loaderFlags = 0;
  /* The is the BundleClassLoader for the bundle */
  private BundleClassLoader classloader;
  private ClassLoader parent;

  /**
   * Returns the package name from the specified class name.
   * The returned package is dot seperated.
   *
   * @param name   Name of a class.
   * @return Dot separated package name or null if the class
   *         has no package name.
   */
  public final static String getPackageName(String name) {
    if (name != null) {
      int index = name.lastIndexOf('.'); /* find last period in class name */
      if (index > 0)
        return name.substring(0, index);
    }
    return DEFAULT_PACKAGE;
  }

  /**
   * Returns the package name from the specified resource name.
   * The returned package is dot seperated.
   *
   * @param name   Name of a resource.
   * @return Dot separated package name or null if the resource
   *         has no package name.
   */
  public final static String getResourcePackageName(String name) {
    if (name != null) {
      /* check for leading slash*/
      int begin = ((name.length() > 1) && (name.charAt(0) == '/')) ? 1 : 0;
      int end = name.lastIndexOf('/'); /* index of last slash */
      if (end > begin)
        return name.substring(begin, end).replace('/', '.');
    }
    return DEFAULT_PACKAGE;
  }

  /**
   * BundleLoader runtime constructor. This object is created lazily
   * when the first request for a resource is made to this bundle.
   *
   * @param bundle Bundle object for this loader.
   * @param proxy the BundleLoaderProxy for this loader.
   * @exception org.osgi.framework.BundleException
   */
  protected BundleLoader(BundleHost bundle, BundleLoaderProxy proxy) throws BundleException {
    this.bundle = bundle;
    this.proxy = proxy;
    try {
      bundle.getBundleData().open(); /* make sure the BundleData is open */
    } catch (IOException e) {
      throw new BundleException(Msg.BUNDLE_READ_EXCEPTION, e);
    }
    BundleDescription description = proxy.getBundleDescription();
    // init the require bundles list.
    BundleDescription[] required = description.getResolvedRequires();
    if (required.length > 0) {
      // get a list of re-exported symbolic names
      Set<String> reExportSet = new HashSet<String>(required.length);
      BundleSpecification[] requiredSpecs = description.getRequiredBundles();
      if (requiredSpecs != null && requiredSpecs.length > 0)
        for (int i = 0; i < requiredSpecs.length; i++)
          if (requiredSpecs[i].isExported())
            reExportSet.add(requiredSpecs[i].getName());

      requiredBundles = new BundleLoaderProxy[required.length];
      int[] reexported = new int[required.length];
      int reexportIndex = 0;
      for (int i = 0; i < required.length; i++) {
        requiredBundles[i] = getLoaderProxy(required[i]);
        if (reExportSet.contains(required[i].getSymbolicName()))
          reexported[reexportIndex++] = i;
      }
      if (reexportIndex > 0) {
        reexportTable = new int[reexportIndex];
        System.arraycopy(reexported, 0, reexportTable, 0, reexportIndex);
      } else {
        reexportTable = null;
      }
      requiredSources = new KeyedHashSet(10, false);
    } else {
      requiredBundles = null;
      reexportTable = null;
      requiredSources = null;
    }

    // init the provided packages set
    ExportPackageDescription[] exports = description.getSelectedExports();
    if (exports != null && exports.length > 0) {
      exportedPackages = Collections.synchronizedCollection(exports.length > 10 ? new HashSet<String>(exports.length) : new ArrayList<String>(exports.length));
      initializeExports(exports, exportedPackages);
    } else {
      exportedPackages = Collections.synchronizedCollection(new ArrayList<String>(0));
    }

    ExportPackageDescription substituted[] = description.getSubstitutedExports();
    if (substituted.length > 0) {
      substitutedPackages = substituted.length > 10 ? new HashSet<String>(substituted.length) : new ArrayList<String>(substituted.length);
      for (int i = 0; i < substituted.length; i++)
        substitutedPackages.add(substituted[i].getName());
    } else {
      substitutedPackages = null;
    }

    //This is the fastest way to access to the description for fragments since the hostdescription.getFragments() is slow
    BundleFragment[] fragmentObjects = bundle.getFragments();
    BundleDescription[] fragments = new BundleDescription[fragmentObjects == null ? 0 : fragmentObjects.length];
    for (int i = 0; i < fragments.length; i++)
      fragments[i] = fragmentObjects[i].getBundleDescription();
    // init the dynamic imports tables
    if (description.hasDynamicImports())
      addDynamicImportPackage(description.getImportPackages());
    // ...and its fragments
    for (int i = 0; i < fragments.length; i++)
      if (fragments[i].isResolved() && fragments[i].hasDynamicImports())
        addDynamicImportPackage(fragments[i].getImportPackages());

    //Initialize the policy handler
    String buddyList = null;
    try {
      buddyList = bundle.getBundleData().getManifest().get(Constants.BUDDY_LOADER);
    } catch (BundleException e) {
      // do nothing; buddyList == null
    }
    policy = buddyList != null ? new PolicyHandler(this, buddyList, bundle.getFramework().getPackageAdmin()) : null;
    if (policy != null)
      policy.open(bundle.getFramework().getSystemBundleContext());
  }

  private void initializeExports(ExportPackageDescription[] exports, Collection<String> exportNames) {
    for (int i = 0; i < exports.length; i++) {
      if (proxy.forceSourceCreation(exports[i])) {
        if (!exportNames.contains(exports[i].getName())) {
          // must force filtered and reexport sources to be created early
          // to prevent lazy normal package source creation.
          // We only do this for the first export of a package name.
          proxy.createPackageSource(exports[i], true);
        }
      }
      exportNames.add(exports[i].getName());
    }
  }

  public synchronized KeyedHashSet getImportedSources(KeyedHashSet visited) {
    if ((loaderFlags & FLAG_IMPORTSINIT) != 0)
      return importedSources;
    BundleDescription bundleDesc = proxy.getBundleDescription();
    ExportPackageDescription[] packages = bundleDesc.getResolvedImports();
    if (packages != null && packages.length > 0) {
      if (importedSources == null)
        importedSources = new KeyedHashSet(packages.length, false);
      for (int i = 0; i < packages.length; i++) {
        if (packages[i].getExporter() == bundleDesc)
          continue; // ignore imports resolved to this bundle
        PackageSource source = createExportPackageSource(packages[i], visited);
        if (source != null)
          importedSources.add(source);
      }
    }
    loaderFlags |= FLAG_IMPORTSINIT;
    return importedSources;
  }

  public synchronized boolean isLazyTriggerSet() {
    return (loaderFlags & FLAG_LAZYTRIGGER) != 0;
  }

  public void setLazyTrigger() throws BundleException {
    synchronized (this) {
      loaderFlags |= FLAG_LAZYTRIGGER;
    }
    BundleLoaderProxy.secureAction.start(bundle, Bundle.START_TRANSIENT | BundleHost.LAZY_TRIGGER);
  }

  final PackageSource createExportPackageSource(ExportPackageDescription export, KeyedHashSet visited) {
    BundleLoaderProxy exportProxy = getLoaderProxy(export.getExporter());
    if (exportProxy == null)
      // TODO log error!!
      return null;
    PackageSource requiredSource = exportProxy.getBundleLoader().findRequiredSource(export.getName(), visited);
    PackageSource exportSource = exportProxy.createPackageSource(export, false);
    if (requiredSource == null)
      return exportSource;
    return createMultiSource(export.getName(), new PackageSource[] {requiredSource, exportSource});
  }

  private static PackageSource createMultiSource(String packageName, PackageSource[] sources) {
    if (sources.length == 1)
      return sources[0];
    List<SingleSourcePackage> sourceList = new ArrayList<SingleSourcePackage>(sources.length);
    for (int i = 0; i < sources.length; i++) {
      SingleSourcePackage[] innerSources = sources[i].getSuppliers();
      for (int j = 0; j < innerSources.length; j++)
        if (!sourceList.contains(innerSources[j]))
          sourceList.add(innerSources[j]);
    }
    return new MultiSourcePackage(packageName, sourceList.toArray(new SingleSourcePackage[sourceList.size()]));
  }

  /*
   * get the loader proxy for a bundle description
   */
  public final BundleLoaderProxy getLoaderProxy(BundleDescription source) {
    Object userObject = source.getUserObject();
    if (!(userObject instanceof BundleLoaderProxy)) {
      // may need to force the proxy to be created
      long exportingID = source.getBundleId();
      BundleHost exportingBundle = (BundleHost) bundle.getFramework().getBundle(exportingID);
      if (exportingBundle == null)
        return null;
      userObject = exportingBundle.getLoaderProxy();
    }
    return (BundleLoaderProxy) userObject;
  }

  public BundleLoaderProxy getLoaderProxy() {
    return proxy;
  }

  /*
   * Close the the BundleLoader.
   *
   */
  synchronized void close() {
    if ((loaderFlags & FLAG_CLOSED) != 0)
      return;
    if (classloader != null)
      classloader.close();
    if (policy != null)
      policy.close(bundle.getFramework().getSystemBundleContext());
    loaderFlags |= FLAG_CLOSED; /* This indicates the BundleLoader is destroyed */
  }

  /**
   * This method loads a class from the bundle.  The class is searched for in the
   * same manner as it would if it was being loaded from a bundle (i.e. all
   * hosts, fragments, import, required bundles and local resources are searched.
   *
   * @param      name     the name of the desired Class.
   * @return     the resulting Class
   * @exception  java.lang.ClassNotFoundException  if the class definition was not found.
   */
  final public Class<?> loadClass(String name) throws ClassNotFoundException {
    BundleClassLoader bcl = createClassLoader();
    // The instanceof check here is just to be safe.  The javadoc contract stated in BundleClassLoader
    // mandate that BundleClassLoaders be an instance of ClassLoader.
    if (name.length() > 0 && name.charAt(0) == '[' && bcl instanceof ClassLoader)
      return Class.forName(name, false, (ClassLoader) bcl);
    return bcl.loadClass(name);
  }

  /**
   * This method gets a resource from the bundle.  The resource is searched
   * for in the same manner as it would if it was being loaded from a bundle
   * (i.e. all hosts, fragments, import, required bundles and
   * local resources are searched).
   *
   * @param name the name of the desired resource.
   * @return the resulting resource URL or null if it does not exist.
   */
  final URL getResource(String name) {
    return createClassLoader().getResource(name);
  }

  public final synchronized ClassLoader getParentClassLoader() {
    if (parent != null)
      return parent;
    createClassLoader();
    return parent;
  }

  final public synchronized BundleClassLoader createClassLoader() {
    if (classloader != null)
      return classloader;
    String[] classpath;
    try {
      classpath = bundle.getBundleData().getClassPath();
    } catch (BundleException e) {
      // no classpath
      classpath = new String[0];
      bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
    }
    if (classpath == null) {
      // no classpath
      classpath = new String[0];
      bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(Msg.BUNDLE_NO_CLASSPATH_MATCH, BundleException.MANIFEST_ERROR));
    }
    BundleClassLoader bcl = createBCLPrevileged(bundle.getProtectionDomain(), classpath);
    parent = getParentPrivileged(bcl);
    classloader = bcl;
    return classloader;
  }

  /**
   * Finds a class local to this bundle.  Only the classloader for this bundle is searched.
   * @param name The name of the class to find.
   * @return The loaded Class or null if the class is not found.
   * @throws ClassNotFoundException
   */
  Class<?> findLocalClass(String name) throws ClassNotFoundException {
    if (Debug.DEBUG_LOADER)
      Debug.println("BundleLoader[" + this + "].findLocalClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    try {
      Class<?> clazz = createClassLoader().findLocalClass(name);
      if (Debug.DEBUG_LOADER && clazz != null)
        Debug.println("BundleLoader[" + this + "] found local class " + name); //$NON-NLS-1$ //$NON-NLS-2$
      return clazz;
    } catch (ClassNotFoundException e) {
      if (e instanceof StatusException) {
        if ((((StatusException) e).getStatusCode() & StatusException.CODE_ERROR) != 0)
          throw e;
      }
      return null;
    }
  }

  /**
   * Finds the class for a bundle.  This method is used for delegation by the bundle's classloader.
   */
  public Class<?> findClass(String name) throws ClassNotFoundException {
    return findClass(name, true);
  }

  Class<?> findClass(String name, boolean checkParent) throws ClassNotFoundException {
    ClassLoader parentCL = getParentClassLoader();
    if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
      // 1) if startsWith "java." delegate to parent and terminate search
      // we want to throw ClassNotFoundExceptions if a java.* class cannot be loaded from the parent.
      return parentCL.loadClass(name);
    try {
      if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
        lock(createClassLoader());
      return findClassInternal(name, checkParent, parentCL);
    } finally {
      if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
        unlock();
    }
  }

  private Class<?> findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
    if (Debug.DEBUG_LOADER)
      Debug.println("BundleLoader[" + this + "].loadBundleClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    String pkgName = getPackageName(name);
    boolean bootDelegation = false;
    // follow the OSGi delegation model
    if (checkParent && parentCL != null && bundle.getFramework().isBootDelegationPackage(pkgName))
      // 2) if part of the bootdelegation list then delegate to parent and continue of failure
      try {
        return parentCL.loadClass(name);
      } catch (ClassNotFoundException cnfe) {
        // we want to continue
        bootDelegation = true;
      }
    Class<?> result = null;
    try {
      result = (Class<?>) searchHooks(name, PRE_CLASS);
    } catch (ClassNotFoundException e) {
      throw e;
    } catch (FileNotFoundException e) {
      // will not happen
    }
    if (result != null)
      return result;
    // 3) search the imported packages
    PackageSource source = findImportedSource(pkgName, null);
    if (source != null) {
      // 3) found import source terminate search at the source
      result = source.loadClass(name);
      if (result != null)
        return result;
      throw new ClassNotFoundException(name);
    }
    // 4) search the required bundles
    source = findRequiredSource(pkgName, null);
    if (source != null)
      // 4) attempt to load from source but continue on failure
      result = source.loadClass(name);
    // 5) search the local bundle
    if (result == null)
      result = findLocalClass(name);
    if (result != null)
      return result;
    // 6) attempt to find a dynamic import source; only do this if a required source was not found
    if (source == null) {
      source = findDynamicSource(pkgName);
      if (source != null) {
        result = source.loadClass(name);
        if (result != null)
          return result;
        // must throw CNFE if dynamic import source does not have the class
        throw new ClassNotFoundException(name);
      }
    }

    if (result == null)
      try {
        result = (Class<?>) searchHooks(name, POST_CLASS);
      } catch (ClassNotFoundException e) {
        throw e;
      } catch (FileNotFoundException e) {
        // will not happen
      }
    // do buddy policy loading
    if (result == null && policy != null)
      result = policy.doBuddyClassLoading(name);
    if (result != null)
      return result;
    // hack to support backwards compatibiility for bootdelegation
    // or last resort; do class context trick to work around VM bugs
    if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
      // we don't need to continue if a CNFE is thrown here.
      try {
        return parentCL.loadClass(name);
      } catch (ClassNotFoundException e) {
        // we want to generate our own exception below
      }
    throw new ClassNotFoundException(name);
  }

  @SuppressWarnings("unchecked")
  private <E> E searchHooks(String name, int type) throws ClassNotFoundException, FileNotFoundException {
    ClassLoaderDelegateHook[] delegateHooks = bundle.getFramework().getDelegateHooks();
    if (delegateHooks == null)
      return null;
    E result = null;
    for (int i = 0; i < delegateHooks.length && result == null; i++) {
      switch (type) {
        case PRE_CLASS :
          result = (E) delegateHooks[i].preFindClass(name, createClassLoader(), bundle.getBundleData());
          break;
        case POST_CLASS :
          result = (E) delegateHooks[i].postFindClass(name, createClassLoader(), bundle.getBundleData());
          break;
        case PRE_RESOURCE :
          result = (E) delegateHooks[i].preFindResource(name, createClassLoader(), bundle.getBundleData());
          break;
        case POST_RESOURCE :
          result = (E) delegateHooks[i].postFindResource(name, createClassLoader(), bundle.getBundleData());
          break;
        case PRE_RESOURCES :
          result = (E) delegateHooks[i].preFindResources(name, createClassLoader(), bundle.getBundleData());
          break;
        case POST_RESOURCES :
          result = (E) delegateHooks[i].postFindResources(name, createClassLoader(), bundle.getBundleData());
          break;
        case PRE_LIBRARY :
          result = (E) delegateHooks[i].preFindLibrary(name, createClassLoader(), bundle.getBundleData());
          break;
        case POST_LIBRARY :
          result = (E) delegateHooks[i].postFindLibrary(name, createClassLoader(), bundle.getBundleData());
          break;
      }
    }
    return result;
  }

  private boolean isRequestFromVM() {
    if (bundle.getFramework().isBootDelegationPackage("*") || !bundle.getFramework().contextBootDelegation) //$NON-NLS-1$
      return false;
    // works around VM bugs that require all classloaders to have access to parent packages
    Class<?>[] context = CLASS_CONTEXT.getClassContext();
    if (context == null || context.length < 2)
      return false;
    // skip the first class; it is the ClassContext class
    for (int i = 1; i < context.length; i++)
      // find the first class in the context which is not BundleLoader or instanceof ClassLoader
      if (context[i] != BundleLoader.class && !ClassLoader.class.isAssignableFrom(context[i])) {
        // only find in parent if the class is not "Class" (Class#forName case) or if the class is not loaded with a BundleClassLoader
        ClassLoader cl = getClassLoader(context[i]);
        if (cl != FW_CLASSLOADER) { // extra check incase an adaptor adds another class into the stack besides an instance of ClassLoader
          if (Class.class != context[i] && !(cl instanceof BundleClassLoader))
            return true;
          break;
        }
      }
    return false;
  }

  private static ClassLoader getClassLoader(final Class<?> clazz) {
    if (System.getSecurityManager() == null)
      return clazz.getClassLoader();
    return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
      public ClassLoader run() {
        return clazz.getClassLoader();
      }
    });
  }

  /**
   * Finds the resource for a bundle.  This method is used for delegation by the bundle's classloader.
   */
  public URL findResource(String name) {
    return findResource(name, true);
  }

  URL findResource(String name, boolean checkParent) {
    if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
      name = name.substring(1); /* remove leading slash before search */
    String pkgName = getResourcePackageName(name);
    boolean bootDelegation = false;
    ClassLoader parentCL = getParentClassLoader();
    // follow the OSGi delegation model
    // First check the parent classloader for system resources, if it is a java resource.
    if (checkParent && parentCL != null) {
      if (pkgName.startsWith(JAVA_PACKAGE))
        // 1) if startsWith "java." delegate to parent and terminate search
        // we never delegate java resource requests past the parent
        return parentCL.getResource(name);
      else if (bundle.getFramework().isBootDelegationPackage(pkgName)) {
        // 2) if part of the bootdelegation list then delegate to parent and continue of failure
        URL result = parentCL.getResource(name);
        if (result != null)
          return result;
        bootDelegation = true;
      }
    }

    URL result = null;
    try {
      result = (URL) searchHooks(name, PRE_RESOURCE);
    } catch (FileNotFoundException e) {
      return null;
    } catch (ClassNotFoundException e) {
      // will not happen
    }
    if (result != null)
      return result;
    // 3) search the imported packages
    PackageSource source = findImportedSource(pkgName, null);
    if (source != null)
      // 3) found import source terminate search at the source
      return source.getResource(name);
    // 4) search the required bundles
    source = findRequiredSource(pkgName, null);
    if (source != null)
      // 4) attempt to load from source but continue on failure
      result = source.getResource(name);
    // 5) search the local bundle
    if (result == null)
      result = findLocalResource(name);
    if (result != null)
      return result;
    // 6) attempt to find a dynamic import source; only do this if a required source was not found
    if (source == null) {
      source = findDynamicSource(pkgName);
      if (source != null)
        // must return the result of the dynamic import and do not continue
        return source.getResource(name);
    }

    if (result == null)
      try {
        result = (URL) searchHooks(name, POST_RESOURCE);
      } catch (FileNotFoundException e) {
        return null;
      } catch (ClassNotFoundException e) {
        // will not happen
      }
    // do buddy policy loading
    if (result == null && policy != null)
      result = policy.doBuddyResourceLoading(name);
    if (result != null)
      return result;
    // hack to support backwards compatibiility for bootdelegation
    // or last resort; do class context trick to work around VM bugs
    if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
      // we don't need to continue if the resource is not found here
      return parentCL.getResource(name);
    return result;
  }

  /**
   * Finds the resources for a bundle.  This  method is used for delegation by the bundle's classloader.
   */
  public Enumeration<URL> findResources(String name) throws IOException {
    // do not delegate to parent because ClassLoader#getResources already did and it is final!!
    if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
      name = name.substring(1); /* remove leading slash before search */
    String pkgName = getResourcePackageName(name);
    Enumeration<URL> result = null;
    try {
      result = searchHooks(name, PRE_RESOURCES);
    } catch (ClassNotFoundException e) {
      // will not happen
    } catch (FileNotFoundException e) {
      return null;
    }
    if (result != null)
      return result;
    // start at step 3 because of the comment above about ClassLoader#getResources
    // 3) search the imported packages
    PackageSource source = findImportedSource(pkgName, null);
    if (source != null)
      // 3) found import source terminate search at the source
      return source.getResources(name);
    // 4) search the required bundles
    source = findRequiredSource(pkgName, null);
    if (source != null)
      // 4) attempt to load from source but continue on failure
      result = source.getResources(name);

    // 5) search the local bundle
    // compound the required source results with the local ones
    Enumeration<URL> localResults = findLocalResources(name);
    result = compoundEnumerations(result, localResults);
    // 6) attempt to find a dynamic import source; only do this if a required source was not found
    if (result == null && source == null) {
      source = findDynamicSource(pkgName);
      if (source != null)
        return source.getResources(name);
    }
    if (result == null)
      try {
        result = searchHooks(name, POST_RESOURCES);
      } catch (ClassNotFoundException e) {
        // will not happen
      } catch (FileNotFoundException e) {
        return null;
      }
    if (policy != null) {
      Enumeration<URL> buddyResult = policy.doBuddyResourcesLoading(name);
      result = compoundEnumerations(result, buddyResult);
    }
    return result;
  }

  private boolean isSubPackage(String parentPackage, String subPackage) {
    String prefix = (parentPackage.length() == 0 || parentPackage.equals(DEFAULT_PACKAGE)) ? "" : parentPackage + '.'; //$NON-NLS-1$
    return subPackage.startsWith(prefix);
  }

  public Collection<String> listResources(String path, String filePattern, int options) {
    String pkgName = getResourcePackageName(path.endsWith("/") ? path : path + '/'); //$NON-NLS-1$
    if ((path.length() > 1) && (path.charAt(0) == '/')) /* if name has a leading slash */
      path = path.substring(1); /* remove leading slash before search */
    boolean subPackages = (options & BundleWiring.LISTRESOURCES_RECURSE) != 0;
    List<String> packages = new ArrayList<String>();
    // search imported package names
    KeyedHashSet importSources = getImportedSources(null);
    if (importSources != null) {
      KeyedElement[] imports = importSources.elements();
      for (KeyedElement keyedElement : imports) {
        String id = ((PackageSource) keyedElement).getId();
        if (id.equals(pkgName) || (subPackages && isSubPackage(pkgName, id)))
          packages.add(id);
      }
    }

    // now add package names from required bundles
    if (requiredBundles != null) {
      KeyedHashSet visited = new KeyedHashSet(false);
      visited.add(bundle); // always add ourselves so we do not recurse back to ourselves
      for (BundleLoaderProxy requiredProxy : requiredBundles) {
        BundleLoader requiredLoader = requiredProxy.getBundleLoader();
        requiredLoader.addProvidedPackageNames(requiredProxy.getSymbolicName(), pkgName, packages, subPackages, visited);
      }
    }

    boolean localSearch = (options & BundleWiring.LISTRESOURCES_LOCAL) != 0;
    List<String> result = new ArrayList<String>();
    Set<String> importedPackages = new HashSet<String>(0);
    for (String name : packages) {
      // look for import source
      PackageSource externalSource = findImportedSource(name, null);
      if (externalSource != null) {
        // record this package is imported
        importedPackages.add(name);
      } else {
        // look for require bundle source
        externalSource = findRequiredSource(name, null);
      }
      // only add the content of the external source if this is not a localSearch
      if (externalSource != null && !localSearch) {
        String packagePath = name.replace('.', '/');
        Collection<String> externalResources = externalSource.listResources(packagePath, filePattern);
        for (String resource : externalResources) {
          if (!result.contains(resource)) // prevent duplicates; could happen if the package is split or exporter has fragments/multiple jars
            result.add(resource);
        }
      }
    }

    // now search locally
    Collection<String> localResources = createClassLoader().listLocalResources(path, filePattern, options);
    for (String resource : localResources) {
      String resourcePkg = getResourcePackageName(resource);
      if (!importedPackages.contains(resourcePkg) && !result.contains(resource))
        result.add(resource);
    }
    return result;
  }

  /*
   * This method is used by Bundle.getResources to do proper parent delegation.
   */
  public Enumeration<URL> getResources(String name) throws IOException {
    if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
      name = name.substring(1); /* remove leading slash before search */
    String pkgName = getResourcePackageName(name);
    // follow the OSGi delegation model
    // First check the parent classloader for system resources, if it is a java resource.
    Enumeration<URL> result = null;
    if (pkgName.startsWith(JAVA_PACKAGE) || bundle.getFramework().isBootDelegationPackage(pkgName)) {
      // 1) if startsWith "java." delegate to parent and terminate search
      // 2) if part of the bootdelegation list then delegate to parent and continue of failure
      ClassLoader parentCL = getParentClassLoader();
      result = parentCL == null ? null : parentCL.getResources(name);
      if (pkgName.startsWith(JAVA_PACKAGE))
        return result;
    }
    return compoundEnumerations(result, findResources(name));
  }

  public static <E> Enumeration<E> compoundEnumerations(Enumeration<E> list1, Enumeration<E> list2) {
    if (list2 == null || !list2.hasMoreElements())
      return list1;
    if (list1 == null || !list1.hasMoreElements())
      return list2;
    List<E> compoundResults = new ArrayList<E>();
    while (list1.hasMoreElements())
      compoundResults.add(list1.nextElement());
    while (list2.hasMoreElements()) {
      E item = list2.nextElement();
      if (!compoundResults.contains(item)) //don't add duplicates
        compoundResults.add(item);
    }
    return Collections.enumeration(compoundResults);
  }

  /**
   * Finds a resource local to this bundle.  Only the classloader for this bundle is searched.
   * @param name The name of the resource to find.
   * @return The URL to the resource or null if the resource is not found.
   */
  URL findLocalResource(final String name) {
    return createClassLoader().findLocalResource(name);
  }

  /**
   * Returns an Enumeration of URLs representing all the resources with
   * the given name. Only the classloader for this bundle is searched.
   *
   * @param  name the resource name
   * @return an Enumeration of URLs for the resources
   */
  Enumeration<URL> findLocalResources(String name) {
    return createClassLoader().findLocalResources(name);
  }

  /**
   * Returns the absolute path name of a native library.
   *
   * @param      name   the library name
   * @return     the absolute path of the native library or null if not found
   */
  public String findLibrary(final String name) {
    if (System.getSecurityManager() == null)
      return findLocalLibrary(name);
    return AccessController.doPrivileged(new PrivilegedAction<String>() {
      public String run() {
        return findLocalLibrary(name);
      }
    });
  }

  final String findLocalLibrary(final String name) {
    String result = null;
    try {
      result = (String) searchHooks(name, PRE_LIBRARY);
    } catch (FileNotFoundException e) {
      return null;
    } catch (ClassNotFoundException e) {
      // will not happen
    }
    if (result != null)
      return result;
    result = bundle.getBundleData().findLibrary(name);
    if (result != null)
      return result;

    // look in fragments imports ...
    BundleFragment[] fragments = bundle.getFragments();
    if (fragments != null)
      for (int i = 0; i < fragments.length; i++) {
        result = fragments[i].getBundleData().findLibrary(name);
        if (result != null)
          return result;
      }
    try {
      return (String) searchHooks(name, POST_LIBRARY);
    } catch (FileNotFoundException e) {
      return null; // this is not necessary; but being consistent in case another step is added below
    } catch (ClassNotFoundException e) {
      // will not happen
    }
    return null;
  }

  /*
   * Return the bundle we are associated with.
   */
  public final AbstractBundle getBundle() {
    return bundle;
  }

  private BundleClassLoader createBCLPrevileged(final BundleProtectionDomain pd, final String[] cp) {
    // Create the classloader as previleged code if security manager is present.
    if (System.getSecurityManager() == null)
      return createBCL(pd, cp);

    return AccessController.doPrivileged(new PrivilegedAction<BundleClassLoader>() {
      public BundleClassLoader run() {
        return createBCL(pd, cp);
      }
    });

  }

  BundleClassLoader createBCL(final BundleProtectionDomain pd, final String[] cp) {
    BundleClassLoader bcl = bundle.getBundleData().createClassLoader(BundleLoader.this, pd, cp);
    // attach existing fragments to classloader
    BundleFragment[] fragments = bundle.getFragments();
    if (fragments != null)
      for (int i = 0; i < fragments.length; i++) {
        try {
          bcl.attachFragment(fragments[i].getBundleData(), fragments[i].getProtectionDomain(), fragments[i].getBundleData().getClassPath());
        } catch (BundleException be) {
          bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
        }
      }

    // finish the initialization of the classloader.
    bcl.initialize();
    return bcl;
  }

  /**
   * Return a string representation of this loader.
   * @return String
   */
  public final String toString() {
    BundleData result = bundle.getBundleData();
    return result == null ? "BundleLoader.bundledata == null!" : result.toString(); //$NON-NLS-1$
  }

  /**
   * Return true if the target package name matches
   * a name in the DynamicImport-Package manifest header.
   *
   * @param pkgname The name of the requested class' package.
   * @return true if the package should be imported.
   */
  private final synchronized boolean isDynamicallyImported(String pkgname) {
    if (this instanceof SystemBundleLoader)
      return false; // system bundle cannot dynamically import
    // must check for startsWith("java.") to satisfy R3 section 4.7.2
    if (pkgname.startsWith("java.")) //$NON-NLS-1$
      return true;

    /* quick shortcut check */
    if ((loaderFlags & FLAG_HASDYNAMICIMPORTS) == 0)
      return false;

    /* "*" shortcut */
    if ((loaderFlags & FLAG_HASDYNAMICEIMPORTALL) != 0)
      return true;

    /* match against specific names */
    if (dynamicImportPackages != null)
      for (int i = 0; i < dynamicImportPackages.length; i++)
        if (pkgname.equals(dynamicImportPackages[i]))
          return true;

    /* match against names with trailing wildcards */
    if (dynamicImportPackageStems != null)
      for (int i = 0; i < dynamicImportPackageStems.length; i++)
        if (pkgname.startsWith(dynamicImportPackageStems[i]))
          return true;

    return false;
  }

  final void addExportedProvidersFor(String symbolicName, String packageName, List<PackageSource> result, KeyedHashSet visited) {
    if (!visited.add(bundle))
      return;

    // See if we locally provide the package.
    PackageSource local = null;
    if (isExportedPackage(packageName))
      local = proxy.getPackageSource(packageName);
    else if (isSubstitutedExport(packageName)) {
      result.add(findImportedSource(packageName, visited));
      return; // should not continue to required bundles in this case
    }
    // Must search required bundles that are exported first.
    if (requiredBundles != null) {
      int size = reexportTable == null ? 0 : reexportTable.length;
      int reexportIndex = 0;
      for (int i = 0; i < requiredBundles.length; i++) {
        if (local != null) {
          // always add required bundles first if we locally provide the package
          // This allows a bundle to provide a package from a required bundle without
          // re-exporting the whole required bundle.
          requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
        } else if (reexportIndex < size && reexportTable[reexportIndex] == i) {
          reexportIndex++;
          requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
        }
      }
    }

    // now add the locally provided package.
    if (local != null && local.isFriend(symbolicName))
      result.add(local);
  }

  final void addProvidedPackageNames(String symbolicName, String packageName, List<String> result, boolean subPackages, KeyedHashSet visitied) {
    if (!visitied.add(bundle))
      return;
    for (String exported : exportedPackages) {
      if (exported.equals(packageName) || (subPackages && isSubPackage(packageName, exported))) {
        if (!result.contains(exported))
          result.add(exported);
      }
    }
    if (substitutedPackages != null)
      for (String substituted : substitutedPackages) {
        if (substituted.equals(packageName) || (subPackages && isSubPackage(packageName, substituted))) {
          if (!result.contains(substituted))
            result.add(substituted);
        }
      }
    if (requiredBundles != null) {
      int size = reexportTable == null ? 0 : reexportTable.length;
      int reexportIndex = 0;
      for (int i = 0; i < requiredBundles.length; i++) {
        if (reexportIndex < size && reexportTable[reexportIndex] == i) {
          reexportIndex++;
          requiredBundles[i].getBundleLoader().addProvidedPackageNames(symbolicName, packageName, result, subPackages, visitied);
        }
      }
    }
  }

  final boolean isExportedPackage(String name) {
    return exportedPackages.contains(name);
  }

  final boolean isSubstitutedExport(String name) {
    return substitutedPackages == null ? false : substitutedPackages.contains(name);
  }

  private void addDynamicImportPackage(ImportPackageSpecification[] packages) {
    if (packages == null)
      return;
    List<String> dynamicImports = new ArrayList<String>(packages.length);
    for (int i = 0; i < packages.length; i++)
      if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(packages[i].getDirective(Constants.RESOLUTION_DIRECTIVE)))
        dynamicImports.add(packages[i].getName());
    if (dynamicImports.size() > 0)
      addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()]));
  }

  /**
   * Adds a list of DynamicImport-Package manifest elements to the dynamic
   * import tables of this BundleLoader.  Duplicate packages are checked and
   * not added again.  This method is not thread safe.  Callers should ensure
   * synchronization when calling this method.
   * @param packages the DynamicImport-Package elements to add.
   */
  private void addDynamicImportPackage(String[] packages) {
    if (packages == null)
      return;

    loaderFlags |= FLAG_HASDYNAMICIMPORTS;
    int size = packages.length;
    List<String> stems;
    if (dynamicImportPackageStems == null) {
      stems = new ArrayList<String>(size);
    } else {
      stems = new ArrayList<String>(size + dynamicImportPackageStems.length);
      for (int i = 0; i < dynamicImportPackageStems.length; i++) {
        stems.add(dynamicImportPackageStems[i]);
      }
    }

    List<String> names;
    if (dynamicImportPackages == null) {
      names = new ArrayList<String>(size);
    } else {
      names = new ArrayList<String>(size + dynamicImportPackages.length);
      for (int i = 0; i < dynamicImportPackages.length; i++) {
        names.add(dynamicImportPackages[i]);
      }
    }

    for (int i = 0; i < size; i++) {
      String name = packages[i];
      if (isDynamicallyImported(name))
        continue;
      if (name.equals("*")) { /* shortcut *///$NON-NLS-1$
        loaderFlags |= FLAG_HASDYNAMICEIMPORTALL;
        return;
      }

      if (name.endsWith(".*")) //$NON-NLS-1$
        stems.add(name.substring(0, name.length() - 1));
      else
        names.add(name);
    }

    size = stems.size();
    if (size > 0)
      dynamicImportPackageStems = stems.toArray(new String[size]);

    size = names.size();
    if (size > 0)
      dynamicImportPackages = names.toArray(new String[size]);
  }

  /**
   * Adds a list of DynamicImport-Package manifest elements to the dynamic
   * import tables of this BundleLoader.  Duplicate packages are checked and
   * not added again.
   * @param packages the DynamicImport-Package elements to add.
   */
  public final synchronized void addDynamicImportPackage(ManifestElement[] packages) {
    if (packages == null)
      return;
    List<String> dynamicImports = new ArrayList<String>(packages.length);
    List<ImportPackageSpecification> dynamicImportSpecs = new ArrayList<ImportPackageSpecification>(packages.length);
    for (ManifestElement dynamicImportElement : packages) {
      String[] names = dynamicImportElement.getValueComponents();
      for (String name : names)
        dynamicImports.add(name);
      StateBuilder.addImportPackages(dynamicImportElement, dynamicImportSpecs, 2, true);
    }
    if (dynamicImports.size() > 0) {
      addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()]));
      BundleDescription revision = getLoaderProxy().getBundleDescription();
      State state = revision.getContainingState();
      state.addDynamicImportPackages(revision, dynamicImportSpecs.toArray(new ImportPackageSpecification[dynamicImportSpecs.size()]));
    }
  }

  synchronized public void attachFragment(BundleFragment fragment) throws BundleException {
    ExportPackageDescription[] exports = proxy.getBundleDescription().getSelectedExports();
    if (classloader == null) {
      initializeExports(exports, exportedPackages);
      return;
    }
    String[] classpath = fragment.getBundleData().getClassPath();
    if (classpath != null)
      classloader.attachFragment(fragment.getBundleData(), fragment.getProtectionDomain(), classpath);
    initializeExports(exports, exportedPackages);
  }

  /*
   * Finds a packagesource that is either imported or required from another bundle.
   * This will not include an local package source
   */
  private PackageSource findSource(String pkgName) {
    if (pkgName == null)
      return null;
    PackageSource result = findImportedSource(pkgName, null);
    if (result != null)
      return result;
    // Note that dynamic imports are not checked to avoid aggressive wiring (bug 105779) 
    return findRequiredSource(pkgName, null);
  }

  private PackageSource findImportedSource(String pkgName, KeyedHashSet visited) {
    KeyedHashSet imports = getImportedSources(visited);
    if (imports == null)
      return null;
    synchronized (imports) {
      return (PackageSource) imports.getByKey(pkgName);
    }
  }

  private PackageSource findDynamicSource(String pkgName) {
    if (isDynamicallyImported(pkgName)) {
      ExportPackageDescription exportPackage = bundle.getFramework().getAdaptor().getState().linkDynamicImport(proxy.getBundleDescription(), pkgName);
      if (exportPackage != null) {
        PackageSource source = createExportPackageSource(exportPackage, null);
        synchronized (this) {
          if (importedSources == null)
            importedSources = new KeyedHashSet(false);
        }
        synchronized (importedSources) {
          importedSources.add(source);
        }
        return source;
      }
    }
    return null;
  }

  private PackageSource findRequiredSource(String pkgName, KeyedHashSet visited) {
    if (requiredBundles == null)
      return null;
    synchronized (requiredSources) {
      PackageSource result = (PackageSource) requiredSources.getByKey(pkgName);
      if (result != null)
        return result.isNullSource() ? null : result;
    }
    if (visited == null)
      visited = new KeyedHashSet(false);
    visited.add(bundle); // always add ourselves so we do not recurse back to ourselves
    List<PackageSource> result = new ArrayList<PackageSource>(3);
    for (int i = 0; i < requiredBundles.length; i++) {
      BundleLoader requiredLoader = requiredBundles[i].getBundleLoader();
      requiredLoader.addExportedProvidersFor(proxy.getSymbolicName(), pkgName, result, visited);
    }
    // found some so cache the result for next time and return
    PackageSource source;
    if (result.size() == 0) {
      // did not find it in our required bundles lets record the failure
      // so we do not have to do the search again for this package.
      source = NullPackageSource.getNullPackageSource(pkgName);
    } else if (result.size() == 1) {
      // if there is just one source, remember just the single source
      source = result.get(0);
    } else {
      // if there was more than one source, build a multisource and cache that.
      PackageSource[] srcs = result.toArray(new PackageSource[result.size()]);
      source = createMultiSource(pkgName, srcs);
    }
    synchronized (requiredSources) {
      requiredSources.add(source);
    }
    return source.isNullSource() ? null : source;
  }

  /*
   * Gets the package source for the pkgName.  This will include the local package source
   * if the bundle exports the package.  This is used to compare the PackageSource of a
   * package from two different bundles.
   */
  public final PackageSource getPackageSource(String pkgName) {
    PackageSource result = findSource(pkgName);
    if (!isExportedPackage(pkgName))
      return result;
    // if the package is exported then we need to get the local source
    PackageSource localSource = proxy.getPackageSource(pkgName);
    if (result == null)
      return localSource;
    if (localSource == null)
      return result;
    return createMultiSource(pkgName, new PackageSource[] {result, localSource});
  }

  private ClassLoader getParentPrivileged(final BundleClassLoader bcl) {
    if (System.getSecurityManager() == null)
      return bcl.getParent();

    return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
      public ClassLoader run() {
        return bcl.getParent();
      }
    });
  }

  static final class ClassContext extends SecurityManager {
    // need to make this method public
    public Class<?>[] getClassContext() {
      return super.getClassContext();
    }
  }

  /*
   * see bug 121737
   * To ensure that we do not enter a deadly embrace between classloader cycles
   * we attempt to obtain a global lock before do normal osgi delegation.
   * This approach ensures that only one thread has a classloader locked at a time
   */
  private static void lock(Object loader) {
    Thread currentThread = Thread.currentThread();
    boolean interrupted = false;
    synchronized (loader) {
      if (tryLock(currentThread, loader))
        return; // this thread has the lock

      do {
        try {
          // we wait on the loader object here to release its lock incase we have it.
          // we do not way to wait while holding this lock because that will cause deadlock
          loader.wait();
        } catch (InterruptedException e) {
          interrupted = true;
          // we still want to try again
        }
      } while (!tryLock(currentThread));
    }
    if (interrupted)
      currentThread.interrupt();
  }

  /*
   * returns true if this thread can obtain the global lock or already has the lock;
   * otherwise this loader and thread are added to the waitingList
   */
  private synchronized static boolean tryLock(Thread currentThread, Object loader) {
    if (lockThread == currentThread) {
      lockCount++;
      return true;
    }
    if (lockThread == null) {
      lockCount++;
      lockThread = currentThread;
      return true;
    }
    waitingList.add(new Object[] {currentThread, loader});
    return false;
  }

  /*
   * returns true if this thread already has the global lock
   */
  private synchronized static boolean tryLock(Thread currentThread) {
    if (lockThread == currentThread) {
      lockCount++;
      return true;
    }
    return false;
  }

  /*
   * unlocks the global lock and notifies the first waiting thread that they
   * now have the lock
   */
  private static void unlock() {
    Thread waitingThread = null;
    Object loader = null;
    synchronized (BundleLoader.class) {
      lockCount--;
      if (lockCount != 0)
        return;

      if (waitingList.isEmpty()) {
        lockThread = null;
        return;
      }

      Object[] waiting = waitingList.get(0);
      waitingThread = (Thread) waiting[0];
      loader = waiting[1];
    }
    synchronized (loader) {
      synchronized (BundleLoader.class) {
        lockThread = waitingThread;
        waitingList.remove(0);
        loader.notifyAll();
      }
    }
  }

  static public void closeBundleLoader(BundleLoaderProxy proxy) {
    if (proxy == null)
      return;
    // First close the BundleLoader
    BundleLoader loader = proxy.getBasicBundleLoader();
    if (loader != null)
      loader.close();
    proxy.setStale();
    // if proxy is not null then make sure to unset user object
    // associated with the proxy in the state
    BundleDescription description = proxy.getBundleDescription();
    // must set it back to the bundle object; not null
    // need to make sure the user object is a BundleReference
    description.setUserObject(proxy.getBundleData());
  }
}
TOP

Related Classes of org.eclipse.osgi.internal.loader.BundleLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.