/**
   * Returns the package of the specified completion.
   *
   * @param c The completion.
   * @param desc The description text being parsed. Used for errors if we cannot determine the
   *     package.
   * @return The package.
   */
  private String getPackage(Completion c, String desc) {

    String pkg = null;

    if (c instanceof JSClassCompletion) {
      pkg = ((JSClassCompletion) c).getPackageName();
    }
    if (c instanceof JSCompletion) {
      String definedIn = ((JSCompletion) c).getEnclosingClassName(true);
      if (definedIn != null) {
        int lastDot = definedIn.lastIndexOf('.');
        if (lastDot > -1) {
          pkg = definedIn.substring(0, lastDot);
        }
      }
    } else {
      Logger.logError(
          "Can't determine package from completion type: "
              + c.getClass()
              + " ("
              + c.toString()
              + ") - href: "
              + desc);
    }

    return pkg;
  }
  protected void paintComponent(Graphics g) {

    // Set up rendering hints to look as close to native as possible
    Graphics2D g2d = (Graphics2D) g;
    Object old = null;

    // First, try to use the rendering hint set that is "native".
    Map hints = (Map) getToolkit().getDesktopProperty("awt.font.desktophints");
    if (hints != null) {
      old = g2d.getRenderingHints();
      g2d.addRenderingHints(hints);
    }
    // If a "native" set isn't found, just turn on standard text AA.
    else {
      old = g2d.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
      g2d.setRenderingHint(
          RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }

    //		if (jsc!=null) {
    //			setText(null); // Stop "Foobar" from being painted
    //		}

    // We never paint "selection" around the icon, to imitate Eclipse
    final int iconW = 18;
    int h = getHeight();
    if (!selected) {
      g.setColor(getBackground());
      g.fillRect(0, 0, getWidth(), h);
    } else {
      g.setColor(altBG != null && evenRow ? altBG : list.getBackground());
      g.fillRect(0, 0, iconW, h);
      g.setColor(getBackground()); // Selection color
      g.fillRect(iconW, 0, getWidth() - iconW, h);
    }
    if (getIcon() != null) {
      int y = (h - getIcon().getIconHeight()) / 2;
      getIcon().paintIcon(this, g, 0, y);
    }

    int x = getX() + iconW + 2;
    g.setColor(selected ? list.getSelectionForeground() : list.getForeground());
    if (jsc != null && !simpleText) {
      jsc.rendererText(g, x, g.getFontMetrics().getHeight(), selected);
    } else {
      Completion c = jsc != null ? (Completion) jsc : nonJavaCompletion;
      if (c != null) {
        g.drawString(c.toString(), x, g.getFontMetrics().getHeight());
      }
    }

    // Restore rendering hints appropriately.
    if (hints != null) {
      g2d.addRenderingHints((Map) old);
    } else {
      g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, old);
    }
  }
  /**
   * Returns the class for a completion (class completions return the class itself, member
   * completions return the enclosing class).
   */
  private String getClass(Completion c, String desc) {

    String clazz = null;

    if (c instanceof JSClassCompletion) {
      clazz = ((JSClassCompletion) c).getClassName(true);
    } else if (c instanceof JSCompletion) {
      JSCompletion jsc = (JSCompletion) c;
      clazz = jsc.getEnclosingClassName(true);
    } else {
      Logger.logError(
          "Can't determine class from completion type: "
              + c.getClass()
              + " ("
              + c.toString()
              + ") - href: "
              + desc);
    }

    return clazz;
  }
  /** {@inheritDoc} */
  public void urlClicked(HyperlinkEvent e, Completion c, DescWindowCallback callback) {

    // A "real" URL (starts with http://, for example) should be opened
    // in the system browser, not the completion description window.
    URL url = e.getURL();
    if (url != null) {
      // Try loading in external browser (Java 6+ only).
      try {
        Util.browse(new URI(url.toString()));
      } catch (/*IO*/ URISyntaxException ioe) {
        UIManager.getLookAndFeel().provideErrorFeedback(null);
        ioe.printStackTrace();
      }
      return;
    }

    // A relative path URL (no leading "http://") results in a null URL.
    // Class should be in the same package as the one we're currently
    // viewing.  Example:  java.lang.String class documentation
    String desc = e.getDescription();
    Logger.log(desc);
    if (desc != null) {

      if (isRelativeUrl(desc)) {
        int ext = desc.indexOf(".htm");
        if (ext > -1) {

          // Could be <a href="Character.html#section"> link.  A
          // popular href format is "../../util/Formatter.html#syntax".
          // We must determine "relative" package location.
          String anchor = getAnchor(desc);
          String clazz = desc.substring(0, ext);
          int backups = 0;
          while (clazz.startsWith("../")) {
            backups++;
            clazz = clazz.substring(3);
          }
          clazz = clazz.replace('/', '.');

          String pkg = getPackage(c, desc);
          if (pkg != null) {
            clazz = doBackups(pkg, backups) + "." + clazz;
            JavaScriptLanguageSupport jls = getJavaScriptLanguageSupport();
            ClassFile cf = jls.getJarManager().getClassEntry(clazz);
            if (cf != null) {
              JSClassCompletion cc = new JSClassCompletion(c.getProvider(), cf);
              callback.showSummaryFor(cc, anchor);
            }
          }
        }
      }

      // Could be format "com.mycompany.pkg.MyClass", with optional
      // #method() (for example, @see's).
      else {

        JavaScriptLanguageSupport jls = getJavaScriptLanguageSupport();

        String clazz = desc;
        String member = null;
        int pound = desc.indexOf('#');
        if (pound > -1) { // TODO: Handle properly
          member = clazz.substring(pound + 1);
          clazz = clazz.substring(0, pound);
        }

        // Just a class name, i.e. "String", "java.util.regex.Pattern".
        if (member == null) {
          boolean guessedPackage = false;
          if (clazz.indexOf('.') == -1) {
            String pkg = getPackage(c, desc);
            if (pkg != null) {
              clazz = pkg + "." + clazz;
            }
            guessedPackage = true;
          }
          ClassFile cf = jls.getJarManager().getClassEntry(clazz);
          if (cf == null && guessedPackage) {
            // Wasn't in the same package as "c", try java.lang
            int lastDot = clazz.lastIndexOf('.');
            clazz = "java.lang." + clazz.substring(lastDot + 1);
            cf = jls.getJarManager().getClassEntry(clazz);
          }
          if (cf != null) {
            JSClassCompletion cc = new JSClassCompletion(c.getProvider(), cf);
            callback.showSummaryFor(cc, null);
          } else {
            UIManager.getLookAndFeel().provideErrorFeedback(null);
            Logger.log("Unknown class: " + clazz);
          }
        }

        // Member specified, such as "String#format(...)",
        // "java.util.regex.Pattern.compile(...)", or "#method()".
        else {

          boolean guessedPackage = false;

          if (pound == 0) { // Member of this class (i.e. "#foobar(bas)")
            // "clazz" was incorrect previously in this case
            clazz = getClass(c, desc);
          } else { // i.e. "String#CASE_INSENSITIVE_ORDER"
            // If no package specified, assume clazz is in the same
            // package as the currently displayed completion.
            if (clazz.indexOf('.') == -1) {
              String pkg = getPackage(c, desc);
              if (pkg != null) {
                clazz = pkg + "." + clazz;
              }
              guessedPackage = true;
            }
          }

          ClassFile cf = clazz != null ? jls.getJarManager().getClassEntry(clazz) : null;
          if (cf == null && guessedPackage) {
            // Wasn't in the same package as "c", try java.lang
            int lastDot = clazz.lastIndexOf('.');
            clazz = "java.lang." + clazz.substring(lastDot + 1);
            cf = jls.getJarManager().getClassEntry(clazz);
          }

          if (cf != null) {

            Completion memberCompletion = null;
            int lparen = member.indexOf('(');
            if (lparen == -1) { // A field, or method with args omitted
              FieldInfo fi = cf.getFieldInfoByName(member);
              if (fi != null) { // Try fields first, it's most likely
                memberCompletion = new JSFieldCompletion(c.getProvider(), fi);
              } else { // Try methods second
                List miList = cf.getMethodInfoByName(member, -1);
                if (miList != null && miList.size() > 0) {
                  MethodInfo mi = (MethodInfo) miList.get(0); // Just show the first if multiple
                  memberCompletion = new JSFunctionCompletion(c.getProvider(), mi);
                }
              }
            } else {
              String[] args = getArgs(member);
              String methodName = member.substring(0, lparen);
              List miList = cf.getMethodInfoByName(methodName, args.length);
              if (miList != null && miList.size() > 0) {
                if (miList.size() > 1) {
                  // TODO: Pick correct overload based on args
                  Logger.log("Multiple overload support not yet implemented");
                } else {
                  MethodInfo mi = (MethodInfo) miList.get(0);
                  memberCompletion = new JSFunctionCompletion(c.getProvider(), mi);
                }
              }
            }

            if (memberCompletion != null) {
              callback.showSummaryFor(memberCompletion, null);
            }

          } else {
            UIManager.getLookAndFeel().provideErrorFeedback(null);
            Logger.logError("Unknown class: " + clazz + " (href: " + desc + ")");
          }
        }
      }
    }
  }