/** Creates request basing on the cache data. */
  private ClassLoaderRequest createRequestFromCache(String className) {
    String[] items = cache.getItemNames(className);
    if (items == null || items.length == 0) return null;

    List<ClassLoaderOffer> offers = new ArrayList<ClassLoaderOffer>();
    for (String itemName : items) offers.add(cache.createOfferByItemName(itemName));

    return new ClassLoaderRequest(className, offers.toArray(new ClassLoaderOffer[offers.size()]));
  }
  @Override
  public void close() {
    mq.close();

    if (cache != null) {
      cache.close();
      cache = null;
    }
  }
  /**
   * Returns an input stream for reading the specified remote resource.<br>
   *
   * @param name the resource name
   * @return an input stream for reading the resource, or {@code null} if the resource could not be
   *     found
   */
  protected InputStream getRemoteResourceAsStream(String name) {
    try {
      log.fine("Trying to load resource " + name + " from the class provider.");

      ClassLoaderRequest request = new ClassLoaderRequest(name);
      ClassLoaderResponse response = (ClassLoaderResponse) mq.requestResponse(request, id);
      ClassLoaderOffer offer = response.getAcceptedOffer();

      log.fine("As a response to loading " + name + " received offer " + offer);

      // If a remote resource was found, store it in the cache and return it
      if (offer != null && offer.hasFiles()) {
        cache.registerOffer(offer);
        useOffer(null, offer);
        String fileLocation = cache.getFileLocation(name, offer.getName());

        // we could have got null because of wrong platform-dependent
        // separator used by user. Let's try again with fixed one.
        // (linux can use windows-style separators for escaping,
        // therefore it's not a good idea to always replace them)
        if (fileLocation == null)
          fileLocation =
              cache.getFileLocation(
                  name.replace('/', File.separatorChar).replace('\\', File.separatorChar),
                  offer.getName());

        if (fileLocation != null) {
          return new FileInputStream(fileLocation);
        } else
          log.warning(
              "The received offer does not contain the file requested or the file could not be stored: cache returned NULL");
      }
    } catch (JMSException | TimeoutException e) {
      throw new JCloudScaleException(e);
    } catch (FileNotFoundException e) {
      // Ignore
    }
    return null;
  }
  private Class<?> useOffer(String className, ClassLoaderOffer offer) {
    try {
      if (offer == null) {
        log.severe(
            "Failed to use code to resolve class " + className + " as there's no offer provided.");
        return null;
      }
      Class<?> requestedClass = null;
      // we go through all files in this offer and register them within this classloader
      for (ClassLoaderFile file : offer.getFiles()) {
        switch (file.getType()) {
          case CLASS:
            if (super.findLoadedClass(file.getName()) != null) {
              log.info("Attempting to load already loaded class: " + file.getName());
              continue;
            }

            byte[] bytecode = file.getContent();
            if (bytecode
                == null) // if there's no content in transmitted file, we have to get it from the
                         // cache.
            bytecode = cache.getClassBytecode(file.getName(), offer.getName());

            if (bytecode == null) {
              log.severe(
                  "Failed to define class " + file.getName() + " from offer " + offer.getName());
              continue;
            }

            Class<?> clazz = defineClass(file.getName(), bytecode, 0, bytecode.length);
            if (clazz.getName().equals(className)) requestedClass = clazz;

            break;

          case JAR:
            String path = cache.getFileLocation(file.getName(), offer.getName());

            if (path == null) {
              log.severe(
                  "Failed to find file " + file.getName() + " from package " + offer.getName());
              continue;
            }

            try {
              addURL(new File(path).toURI().toURL());
            } catch (MalformedURLException e) {
              log.severe("Exception while registering file " + path + ":" + e.toString());
              continue;
            }
            break;

          case ROFILE:
            //					case RWFILE:
            if (!cache.deployFile(file.getName(), offer.getName()))
              log.severe(
                  "Failed to deploy required file "
                      + file.getName()
                      + ". Failed to write file to disk.");
            break;

          default:
            log.severe(
                "Unexpected type of file ("
                    + file.getType()
                    + ") provided in the offer. skipping.");
        }
      }

      if (className != null
          && requestedClass
              == null) { // this can happen as it is possible that the required class is in the jar
        byte[] bytecode = cache.getClassBytecode(className, offer.getName());
        if (bytecode != null) requestedClass = defineClass(className, bytecode, 0, bytecode.length);
      }

      usedOffers.add(offer.getName()); // if everything is fine, we add this offer as used.

      return requestedClass;
    } catch (Throwable ex) {
      log.severe(
          "Failed to use offer "
              + (offer != null ? offer.getName() : "NULL offer ")
              + " from the cache. Removing the offer and failing...");

      if (offer != null) cache.removeOfferByName(offer.getName());

      throw ex;
    }
  }
  /**
   * Loads class and all additional required information from the client.
   *
   * @param className The name of the class to load
   * @return The Class object of the required class.
   */
  private Class<?> loadClassData(String className) {
    long start = System.nanoTime();
    try {
      //
      // Creating request
      //
      ClassLoaderRequest request = createRequestFromCache(className);

      if (request == null) { // this class is not registered in cache
        log.fine(
            String.format(
                "Class %s cannot be found in cache, requesting code from the client.", className));
        request = new ClassLoaderRequest(className);
      } else
        log.fine(
            String.format(
                "Class %s exists in cache in a %s offer(s). Verifying if client has the same code.",
                className, request.getOffers().length));

      ClassLoaderResponse response = (ClassLoaderResponse) mq.requestResponse(request, id);

      if (response == null) {
        log.severe(
            "Client sent null response for class " + className + ". Something is wrong out there.");
        return null;
      }

      ClassLoaderOffer acceptedOffer = response.getAcceptedOffer();

      if (acceptedOffer == null
          || (!acceptedOffer.hasFiles()
              && !acceptedOffer
                  .hasName())) { // client does not like the stuff we sent and did not provide
                                 // anything instead. Shit, we're screwed.
        if (request.getOffers() == null || request.getOffers().length == 0)
          log.severe(
              String.format(
                  "Client did not provide code for class %s and no cached version available.",
                  className));
        else
          log.severe(
              String.format(
                  "Client did not provide code for class %s and did not selected cached version.",
                  className));

        return null;
      }

      if (!acceptedOffer
          .hasFiles()) // can't check whether the name is specified as it can be cache item update.
      { // no code from client, but offer is selected
        String selectedOffer = acceptedOffer.getName();
        log.fine(String.format("Client selected offer %s for class %s.", selectedOffer, className));
        return useOffer(className, request.getOfferByName(selectedOffer));
      } else { // client provided code.
        log.fine(
            String.format(
                "Client provided code for class %s within %s files. saving it to cache and using it.",
                className, acceptedOffer.getFiles().length));

        // updating the offer
        cache.registerOffer(acceptedOffer);

        // merging 2 offers together and using it.(alternative would be to create offer again from
        // cache, but not if NoCache is used)
        ClassLoaderOffer proposedOffer = request.getOfferByName(acceptedOffer.getName());
        if (proposedOffer != null) updateOfferFiles(acceptedOffer, proposedOffer.getFiles());

        return useOffer(className, acceptedOffer);
      }
    } catch (Exception e) {
      log.severe("Failed to load class " + className + ": " + e.toString());
      e.printStackTrace();
      return null;
    } finally {
      String msg =
          "REMOTE CLASS LOADER: loading of "
              + className
              + " took "
              + (System.nanoTime() - start) / 1000000
              + "ms.";
      log.fine(msg);
    }
  }
  /** Ensures that all additional files that this file requires are loaded. */
  private void ensureAdditionalClassesLoaded(Class<?> clazz) {
    if (!cache.isClassLoadedFromCache(clazz)) return;

    if (!hasFileDependencyAnnotation(clazz)) return;

    // we're here if annotation is present. We have to detect if all files are loaded.
    //
    // getting items where this class is registered.
    //
    String[] items = cache.getItemNames(clazz.getName());

    // finding the item that we used to instantiate this class.
    String usedItem = null;
    if (items != null)
      for (String item : items)
        if (usedOffers.contains(item)) {
          usedItem = item;
          break;
        }
    //
    // checking if files for this item were loaded.
    //
    ClassLoaderRequest request = null;
    if (usedItem != null) {
      String[] classesWithFiles = cache.getClassesWithFiles(usedItem);
      if (classesWithFiles != null && Arrays.asList(classesWithFiles).contains(clazz.getName()))
        return; // everything is fine, we loaded all necessary stuff.

      request =
          new ClassLoaderRequest(
              clazz.getName(), new ClassLoaderOffer[] {cache.createOfferByItemName(usedItem)});
    } else {
      log.info(
          "Class "
              + clazz.getName()
              + " has FileDependency annotation, but we could not detect the offer that this class was taken from. Asking from the client.");
      request = new ClassLoaderRequest(clazz.getName());
    }

    // files were not loaded. asking client for the data for our class.
    // Client will have to include files.
    try {
      ClassLoaderResponse response = (ClassLoaderResponse) mq.requestResponse(request, id);
      ClassLoaderOffer acceptedOffer = response.getAcceptedOffer();

      // updating the offer
      if (acceptedOffer.hasFiles()) {
        cache.registerOffer(acceptedOffer);
        // unpacking and using it.
        // we don't need class, just files. but let's do the whole sequence...
        useOffer(null, acceptedOffer);
      } else
        log.info(
            "Trying to load missing dependent files of class "
                + clazz.getName()
                + " we received offer without files. Nothing to load.");
    } catch (JMSException | TimeoutException e) {
      log.severe(
          "Failed to load additional files for class "
              + clazz.getName()
              + " . Exception occured: "
              + e);
      return;
    }
  }