public ByteBuffer run(Retriever retriever) {
      if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL)) return null;

      HTTPRetriever htr = (HTTPRetriever) retriever;
      if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
        // Mark tile as missing to avoid excessive attempts
        MercatorTiledImageLayer.this.levels.markResourceAbsent(tile);
        return null;
      }

      if (htr.getResponseCode() != HttpURLConnection.HTTP_OK) return null;

      URLRetriever r = (URLRetriever) retriever;
      ByteBuffer buffer = r.getBuffer();

      String suffix = WWIO.makeSuffixForMimeType(htr.getContentType());
      if (suffix == null) {
        return null; // TODO: log error
      }

      String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
      path += suffix;

      final File outFile = WorldWind.getDataFileStore().newFile(path);
      if (outFile == null) return null;

      try {
        WWIO.saveBuffer(buffer, outFile);
        return buffer;
      } catch (IOException e) {
        e.printStackTrace(); // TODO: log error
        return null;
      }
    }
  /**
   * Resolves a reference to a local element identified by address and identifier, where {@code
   * linkBase} identifies a document, including the current document, and {@code linkRef} is the id
   * of the desired element.
   *
   * <p>If {@code linkBase} refers to a local COLLADA file and {@code linkRef} is non-null, the
   * return value is the element identified by {@code linkRef}. If {@code linkRef} is null, the
   * return value is a parsed {@link ColladaRoot} for the COLLADA file identified by {@code
   * linkBase}. Otherwise, {@code linkBase} is returned.
   *
   * @param linkBase the address of the document containing the requested element.
   * @param linkRef the element's identifier.
   * @return the requested element, or null if the element is not found.
   * @throws IllegalArgumentException if the address is null.
   */
  protected Object resolveLocalReference(String linkBase, String linkRef) {
    if (linkBase == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      File file = new File(linkBase);

      if (!file.exists()) return null;

      // Determine whether the file is a COLLADA document. If not, just return the file path.
      if (!WWIO.isContentType(file, ColladaConstants.COLLADA_MIME_TYPE))
        return file.toURI().toString();

      // Attempt to open and parse the COLLADA file.
      ColladaRoot refRoot = ColladaRoot.createAndParse(file);
      // An exception is thrown if parsing fails, so no need to check for null.

      // Add the parsed file to the session cache so it doesn't have to be parsed again.
      WorldWind.getSessionCache().put(linkBase, refRoot);

      // Now check the newly opened COLLADA file for the referenced item, if a reference was
      // specified.
      if (linkRef != null) return refRoot.getItemByID(linkRef);
      else return refRoot;
    } catch (Exception e) {
      String message =
          Logging.getMessage("generic.UnableToResolveReference", linkBase + "/" + linkRef);
      Logging.logger().warning(message);
      return null;
    }
  }
  /**
   * Resolves a reference to a remote element identified by address and identifier, where {@code
   * linkBase} identifies a remote document, and {@code linkRef} is the id of the desired element.
   * This method retrieves resources asynchronously using the {@link
   * gov.nasa.worldwind.cache.FileStore}.
   *
   * <p>The return value is null if the file is not yet available in the FileStore. If {@code
   * linkBase} refers to a COLLADA file and {@code linkRef} is non-null, the return value is the
   * element identified by {@code linkRef}. If {@code linkBase} refers to a COLLADA file and {@code
   * linkRef} is null, the return value is a parsed {@link ColladaRoot} for the COLLADA file
   * identified by {@code linkBase}. Otherwise the return value is a {@link URL} to the file in the
   * file cache.
   *
   * @param linkBase the address of the document containing the requested element.
   * @param linkRef the element's identifier.
   * @return URL to the requested file, parsed ColladaRoot, or COLLADA element. Returns null if the
   *     document is not yet available in the FileStore.
   * @throws IllegalArgumentException if the {@code linkBase} is null.
   */
  public Object resolveRemoteReference(String linkBase, String linkRef) {
    if (linkBase == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      // See if it's in the cache. If not, requestFile will start another thread to retrieve it and
      // return null.
      URL url = WorldWind.getDataFileStore().requestFile(linkBase);
      if (url == null) return null;

      // It's in the cache. If it's a COLLADA file try to parse it so we can search for the
      // specified reference.
      // If it's not COLLADA, just return the url for the cached file.
      String contentType = WorldWind.getDataFileStore().getContentType(linkBase);
      if (contentType == null) {
        String suffix = WWIO.getSuffix(linkBase.split(";")[0]); // strip of trailing garbage
        if (!WWUtil.isEmpty(suffix)) contentType = WWIO.makeMimeTypeForSuffix(suffix);
      }

      if (!this.canParseContentType(contentType)) return url;

      // If the file is a COLLADA document, attempt to open it. We can't open it as a File with
      // createAndParse
      // because the ColladaRoot that will be created needs to have the remote address in order to
      // resolve any
      // relative references within it.
      ColladaRoot refRoot = this.parseCachedColladaFile(url, linkBase);

      // Add the parsed file to the session cache so it doesn't have to be parsed again.
      WorldWind.getSessionCache().put(linkBase, refRoot);

      // Now check the newly opened COLLADA file for the referenced item, if a reference was
      // specified.
      if (linkRef != null) return refRoot.getItemByID(linkRef);
      else return refRoot;
    } catch (Exception e) {
      String message =
          Logging.getMessage("generic.UnableToResolveReference", linkBase + "/" + linkRef);
      Logging.logger().warning(message);
      return null;
    }
  }
    public ComposeImageTile(Sector sector, String mimeType, Level level, int width, int height)
        throws IOException {
      super(sector, level, -1, -1); // row and column aren't used and need to signal that

      this.width = width;
      this.height = height;

      this.file =
          File.createTempFile(WWIO.DELETE_ON_EXIT_PREFIX, WWIO.makeSuffixForMimeType(mimeType));
    }
  /**
   * Create a new <code>ColladaRoot</code> for a {@link URL}.
   *
   * @param docSource the URL of the document.
   * @throws IllegalArgumentException if the document source is null.
   * @throws IOException if an error occurs while reading the Collada document.
   */
  public ColladaRoot(URL docSource) throws IOException {
    super(ColladaConstants.COLLADA_NAMESPACE);

    if (docSource == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    URLConnection conn = docSource.openConnection();
    this.colladaDoc = new ColladaInputStream(conn.getInputStream(), WWIO.makeURI(docSource));

    this.initialize();
  }
  private BufferedImage requestImage(MercatorTextureTile tile, String mimeType)
      throws URISyntaxException {
    String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
    String suffix = WWIO.makeSuffixForMimeType(mimeType);
    String path = pathBase + suffix;
    URL url = WorldWind.getDataFileStore().findFile(path, false);

    if (url == null) // image is not local
    return null;

    if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime())) {
      // The file has expired. Delete it.
      WorldWind.getDataFileStore().removeFile(url);
      String message = Logging.getMessage("generic.DataFileExpired", url);
      Logging.logger().fine(message);
    } else {
      try {
        File imageFile = new File(url.toURI());
        BufferedImage image = ImageIO.read(imageFile);
        if (image == null) {
          String message = Logging.getMessage("generic.ImageReadFailed", imageFile);
          throw new RuntimeException(message);
        }

        this.levels.unmarkResourceAbsent(tile);
        return image;
      } catch (IOException e) {
        // Assume that something's wrong with the file and delete it.
        gov.nasa.worldwind.WorldWind.getDataFileStore().removeFile(url);
        this.levels.markResourceAbsent(tile);
        String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
        Logging.logger().info(message);
      }
    }

    return null;
  }
  /**
   * Resolves a reference to a local or remote file or element. If the link refers to an element in
   * the current document, this method returns that element. If the link refers to a remote
   * document, this method will initiate asynchronous retrieval of the document, and return a URL of
   * the downloaded document in the file cache, if it is available locally. If the link identifies a
   * COLLADA document, the document will be returned as a parsed ColladaRoot.
   *
   * @param link the address of the document or element to resolve. This may be a full URL, a URL
   *     fragment that identifies an element in the current document ("#myElement"), or a URL and a
   *     fragment identifier ("http://server.com/model.dae#myElement").
   * @return the requested element, or null if the element is not found.
   * @throws IllegalArgumentException if the address is null.
   */
  public Object resolveReference(String link) {
    if (link == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      String[] linkParts = link.split("#");
      String linkBase = linkParts[0];
      String linkRef = linkParts.length > 1 ? linkParts[1] : null;

      // See if it's a reference to an internal element.
      if (WWUtil.isEmpty(linkBase) && !WWUtil.isEmpty(linkRef)) return this.getItemByID(linkRef);

      // Interpret the path relative to the current document.
      String path = this.getSupportFilePath(linkBase);
      if (path == null) path = linkBase;

      // See if it's an already found and parsed COLLADA file.
      Object o = WorldWind.getSessionCache().get(path);
      if (o != null && o instanceof ColladaRoot)
        return linkRef != null ? ((ColladaRoot) o).getItemByID(linkRef) : o;

      URL url = WWIO.makeURL(path);
      if (url == null) {
        // See if the reference can be resolved to a local file.
        o = this.resolveLocalReference(path, linkRef);
      }

      // If we didn't find a local file, treat it as a remote reference.
      if (o == null) o = this.resolveRemoteReference(path, linkRef);

      if (o != null) return o;

      // If the reference was not resolved as a remote reference, look for a local element
      // identified by the
      // reference string. This handles the case of malformed internal references that omit the #
      // sign at the
      // beginning of the reference.
      return this.getItemByID(link);
    } catch (Exception e) {
      String message = Logging.getMessage("generic.UnableToResolveReference", link);
      Logging.logger().warning(message);
    }

    return null;
  }
  /**
   * Open and parse the specified file expressed as a file: URL..
   *
   * @param url the URL of the file to open, expressed as a URL with a scheme of "file".
   * @param linkBase the original address of the document if the file is a retrieved and cached
   *     file.
   * @return A {@code ColladaRoot} representing the file's COLLADA contents.
   * @throws IOException if an I/O error occurs during opening and parsing.
   * @throws XMLStreamException if a server parsing error is encountered.
   */
  protected ColladaRoot parseCachedColladaFile(URL url, String linkBase)
      throws IOException, XMLStreamException {
    ColladaDoc colladaDoc;

    InputStream refStream = url.openStream();

    colladaDoc = new ColladaInputStream(refStream, WWIO.makeURI(linkBase));

    try {
      ColladaRoot refRoot = new ColladaRoot(colladaDoc);
      refRoot.parse(); // also closes the URL's stream
      return refRoot;
    } catch (XMLStreamException e) {
      refStream.close(); // parsing failed, so explicitly close the stream
      throw e;
    }
  }
  /**
   * Determine if a host is reachable by attempting to resolve the host name, and then attempting to
   * open a connection.
   *
   * @param hostName Name of the host to connect to.
   * @return {@code true} if a the host is reachable, {@code false} if the host name cannot be
   *     resolved, or if opening a connection to the host fails.
   */
  protected static boolean isHostReachable(String hostName) {
    try {
      // Assume host is unreachable if we can't get its dns entry without getting an exception
      //noinspection ResultOfMethodCallIgnored
      InetAddress.getByName(hostName);
    } catch (UnknownHostException e) {
      String message = Logging.getMessage("NetworkStatus.UnreachableTestHost", hostName);
      Logging.verbose(message);
      return false;
    } catch (Exception e) {
      String message = Logging.getMessage("NetworkStatus.ExceptionTestingHost", hostName);
      Logging.verbose(message);
      return false;
    }

    // Was able to get internet address, but host still might not be reachable because the address
    // might have been
    // cached earlier when it was available. So need to try something else.

    URLConnection connection = null;
    try {
      URL url = new URL("http://" + hostName);
      Proxy proxy = WWIO.configureProxy();
      if (proxy != null) connection = url.openConnection(proxy);
      else connection = url.openConnection();

      connection.setConnectTimeout(2000);
      connection.setReadTimeout(2000);
      String ct = connection.getContentType();
      if (ct != null) return true;
    } catch (IOException e) {
      String message = Logging.getMessage("NetworkStatus.ExceptionTestingHost", hostName);
      Logging.info(message);
    } finally {
      if (connection instanceof HttpURLConnection) ((HttpURLConnection) connection).disconnect();
    }

    return false;
  }
  /**
   * Creates a Collada root for an untyped source. The source must be either a {@link File} or a
   * {@link String} identifying either a file path or a {@link URL}. Null is returned if the source
   * type is not recognized.
   *
   * @param docSource either a {@link File} or a {@link String} identifying a file path or {@link
   *     URL}.
   * @return a new {@link ColladaRoot} for the specified source, or null if the source type is not
   *     supported.
   * @throws IllegalArgumentException if the source is null.
   * @throws IOException if an error occurs while reading the source.
   */
  public static ColladaRoot create(Object docSource) throws IOException {
    if (docSource == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (docSource instanceof File) {
      return new ColladaRoot((File) docSource);
    } else if (docSource instanceof URL) {
      return new ColladaRoot((URL) docSource);
    } else if (docSource instanceof String) {
      File file = new File((String) docSource);
      if (file.exists()) return new ColladaRoot(file);

      URL url = WWIO.makeURL(docSource);
      if (url != null) return new ColladaRoot(url);
    } else if (docSource instanceof InputStream) {
      return new ColladaRoot((InputStream) docSource);
    }

    return null;
  }