<commit_before>package com.intellij.codeInsight.daemon.impl.quickfix;

import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.j2ee.openapi.impl.ExternalResourceManagerImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.xml.XmlEntityRefImpl;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.net.IOExceptionDialog;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;

import javax.swing.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * @author mike
 */
public class FetchExtResourceAction extends BaseIntentionAction {
  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.FetchDtdAction");
  private static final @NonNls String HTML_MIME = "text/html";
  private static final @NonNls String HTTP_PROTOCOL = "http://";
  private static final @NonNls String HTTPS_PROTOCOL = "https://";
  private static final @NonNls String FTP_PROTOCOL = "ftp://";
  private static final @NonNls String FETCHING_THREAD_ID = "Fetching Thread";
  private static final @NonNls String EXT_RESOURCES_FOLDER = "extResources";

  public boolean isAvailable(Project project, Editor editor, PsiFile file) {
    if (!(file instanceof XmlFile)) return false;

    int offset = editor.getCaretModel().getOffset();
    String uri = findUri(file, offset);

    if (uri == null) return false;

    XmlFile xmlFile = XmlUtil.findXmlFile(file, uri);
    if (xmlFile != null) return false;

    if (!uri.startsWith(HTTP_PROTOCOL) && !uri.startsWith(FTP_PROTOCOL) && !uri.startsWith(HTTPS_PROTOCOL)) return false;

    setText(QuickFixBundle.message("fetch.external.resource"));
    return true;
  }

  public String getFamilyName() {
    return QuickFixBundle.message("fetch.external.resource");
  }

  public static String findUrl(PsiFile file, int offset, String uri) {
    final PsiElement currentElement = file.findElementAt(offset);
    final XmlAttribute attribute = PsiTreeUtil.getParentOfType(currentElement, XmlAttribute.class);

    if (attribute != null) {
      final XmlTag tag = PsiTreeUtil.getParentOfType(currentElement, XmlTag.class);

      if (tag != null) {
        final String prefix = tag.getPrefixByNamespace(XmlUtil.XML_SCHEMA_INSTANCE_URI);
        if (prefix != null) {
          final String attrValue = tag.getAttributeValue(XmlUtil.SCHEMA_LOCATION_ATT, XmlUtil.XML_SCHEMA_INSTANCE_URI);
          if (attrValue != null) {
            final StringTokenizer tokenizer = new StringTokenizer(attrValue);

            while(tokenizer.hasMoreElements()) {
              if (uri.equals(tokenizer.nextToken())) {
                if (!tokenizer.hasMoreElements()) return uri;
                final String url = tokenizer.nextToken();

                return url.startsWith(HTTP_PROTOCOL) ? url:uri;
              }

              if (!tokenizer.hasMoreElements()) return uri;
              tokenizer.nextToken(); // skip file location
            }
          }
        }
      }
    }
    return uri;
  }

  public static String findUri(PsiFile file, int offset) {
    PsiElement currentElement = file.findElementAt(offset);
    PsiElement element = PsiTreeUtil.getParentOfType(currentElement, XmlDoctype.class);
    if (element != null) {
      return ((XmlDoctype)element).getDtdUri();
    }

    XmlAttribute attribute = PsiTreeUtil.getParentOfType(currentElement, XmlAttribute.class);
    if(attribute == null) return null;

    if (attribute.isNamespaceDeclaration()) {
      String uri = attribute.getValue();
      PsiElement parent = attribute.getParent();

      if (uri != null && parent instanceof XmlTag && ((XmlTag)parent).getNSDescriptor(uri, true) == null) {
        return uri;
      }
    } else if (attribute.getNamespace().equals(XmlUtil.XML_SCHEMA_INSTANCE_URI)) {
      String location = attribute.getValue();

      if (attribute.getLocalName().equals(XmlUtil.NO_NAMESPACE_SCHEMA_LOCATION_ATT)) {
        if (XmlUtil.findXmlFile(file,location) == null) return location;
      } else if (attribute.getLocalName().equals(XmlUtil.SCHEMA_LOCATION_ATT)) {
        StringTokenizer tokenizer = new StringTokenizer(location);
        int offsetInAttr = offset - attribute.getValueElement().getTextOffset();

        while(tokenizer.hasMoreElements()) {
          tokenizer.nextToken(); // skip namespace
          if (!tokenizer.hasMoreElements()) return null;
          String url = tokenizer.nextToken();

          int index = location.indexOf(url);
          if (index <= offsetInAttr && index + url.length() >= offsetInAttr ) {
            return url;
          }
        }
      }
    }

    return null;
  }

  class FetchingResourceIOException extends IOException {
    private String url;

    FetchingResourceIOException(Throwable cause, String url) {
      initCause(cause);
      this.url = url;
    }
  }

  public void invoke(final Project project, Editor editor, PsiFile file) {
    int offset = editor.getCaretModel().getOffset();
    PsiDocumentManager.getInstance(project).commitAllDocuments();

    final String uri = findUri(file, offset);
    if (uri == null) return;

    final String url = findUrl(file, offset, uri);

    final ProgressWindow progressWindow = new ProgressWindow(true, project);
    progressWindow.setTitle(QuickFixBundle.message("fetching.resource.title"));
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      new Thread(new Runnable() {
        public void run() {
          ProgressManager.getInstance().runProcess(new Runnable() {
            public void run() {
              do {
                try {
                  HttpConfigurable.getInstance().prepareURL(url);
                  fetchDtd(project, uri, url);
                  break;
                }
                catch (IOException ex) {
                  String urlWithProblems = url;
                  String message = QuickFixBundle.message("error.fetching.title");
                  IOException cause = ex;

                  if (ex instanceof FetchingResourceIOException) {
                    urlWithProblems = ((FetchingResourceIOException)ex).url;
                    cause = (IOException)ex.getCause();
                    if (!url.equals(urlWithProblems)) message = QuickFixBundle.message("error.fetching.dependent.resource.title");
                  }

                  if (!IOExceptionDialog.showErrorDialog(cause, message, QuickFixBundle.message("error.fetching.resource", urlWithProblems))) {
                    break;
                  }
                  else {
                    continue;
                  }
                }
              }
              while (true);
            }
          }, progressWindow);
        }
      }, FETCHING_THREAD_ID).start();
    }
  }

  private void fetchDtd(final Project project, final String dtdUrl, final String url) throws IOException {
    final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();

    final String extResourcesPath = getExternalResourcesPath();<commit_msg>GUI is locked when trying to fetch external resource that doesn't exist (IDEADEV-10616)<commit_after>package com.intellij.codeInsight.daemon.impl.quickfix;

import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.j2ee.openapi.impl.ExternalResourceManagerImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.xml.XmlEntityRefImpl;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.net.IOExceptionDialog;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;

import javax.swing.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * @author mike
 */
public class FetchExtResourceAction extends BaseIntentionAction {
  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.FetchDtdAction");
  private static final @NonNls String HTML_MIME = "text/html";
  private static final @NonNls String HTTP_PROTOCOL = "http://";
  private static final @NonNls String HTTPS_PROTOCOL = "https://";
  private static final @NonNls String FTP_PROTOCOL = "ftp://";
  private static final @NonNls String FETCHING_THREAD_ID = "Fetching Thread";
  private static final @NonNls String EXT_RESOURCES_FOLDER = "extResources";

  public boolean isAvailable(Project project, Editor editor, PsiFile file) {
    if (!(file instanceof XmlFile)) return false;

    int offset = editor.getCaretModel().getOffset();
    String uri = findUri(file, offset);

    if (uri == null) return false;

    XmlFile xmlFile = XmlUtil.findXmlFile(file, uri);
    if (xmlFile != null) return false;

    if (!uri.startsWith(HTTP_PROTOCOL) && !uri.startsWith(FTP_PROTOCOL) && !uri.startsWith(HTTPS_PROTOCOL)) return false;

    setText(QuickFixBundle.message("fetch.external.resource"));
    return true;
  }

  public String getFamilyName() {
    return QuickFixBundle.message("fetch.external.resource");
  }

  public static String findUrl(PsiFile file, int offset, String uri) {
    final PsiElement currentElement = file.findElementAt(offset);
    final XmlAttribute attribute = PsiTreeUtil.getParentOfType(currentElement, XmlAttribute.class);

    if (attribute != null) {
      final XmlTag tag = PsiTreeUtil.getParentOfType(currentElement, XmlTag.class);

      if (tag != null) {
        final String prefix = tag.getPrefixByNamespace(XmlUtil.XML_SCHEMA_INSTANCE_URI);
        if (prefix != null) {
          final String attrValue = tag.getAttributeValue(XmlUtil.SCHEMA_LOCATION_ATT, XmlUtil.XML_SCHEMA_INSTANCE_URI);
          if (attrValue != null) {
            final StringTokenizer tokenizer = new StringTokenizer(attrValue);

            while(tokenizer.hasMoreElements()) {
              if (uri.equals(tokenizer.nextToken())) {
                if (!tokenizer.hasMoreElements()) return uri;
                final String url = tokenizer.nextToken();

                return url.startsWith(HTTP_PROTOCOL) ? url:uri;
              }

              if (!tokenizer.hasMoreElements()) return uri;
              tokenizer.nextToken(); // skip file location
            }
          }
        }
      }
    }
    return uri;
  }

  public static String findUri(PsiFile file, int offset) {
    PsiElement currentElement = file.findElementAt(offset);
    PsiElement element = PsiTreeUtil.getParentOfType(currentElement, XmlDoctype.class);
    if (element != null) {
      return ((XmlDoctype)element).getDtdUri();
    }

    XmlAttribute attribute = PsiTreeUtil.getParentOfType(currentElement, XmlAttribute.class);
    if(attribute == null) return null;

    if (attribute.isNamespaceDeclaration()) {
      String uri = attribute.getValue();
      PsiElement parent = attribute.getParent();

      if (uri != null && parent instanceof XmlTag && ((XmlTag)parent).getNSDescriptor(uri, true) == null) {
        return uri;
      }
    } else if (attribute.getNamespace().equals(XmlUtil.XML_SCHEMA_INSTANCE_URI)) {
      String location = attribute.getValue();

      if (attribute.getLocalName().equals(XmlUtil.NO_NAMESPACE_SCHEMA_LOCATION_ATT)) {
        if (XmlUtil.findXmlFile(file,location) == null) return location;
      } else if (attribute.getLocalName().equals(XmlUtil.SCHEMA_LOCATION_ATT)) {
        StringTokenizer tokenizer = new StringTokenizer(location);
        int offsetInAttr = offset - attribute.getValueElement().getTextOffset();

        while(tokenizer.hasMoreElements()) {
          tokenizer.nextToken(); // skip namespace
          if (!tokenizer.hasMoreElements()) return null;
          String url = tokenizer.nextToken();

          int index = location.indexOf(url);
          if (index <= offsetInAttr && index + url.length() >= offsetInAttr ) {
            return url;
          }
        }
      }
    }

    return null;
  }

  static class FetchingResourceIOException extends IOException {
    private String url;

    FetchingResourceIOException(Throwable cause, String url) {
      initCause(cause);
      this.url = url;
    }
  }

  static class FetchingResourceProblemRuntimeWrapper extends RuntimeException {
    FetchingResourceProblemRuntimeWrapper(FetchingResourceIOException cause) {
      super(cause);
    }
  }

  public void invoke(final Project project, Editor editor, PsiFile file) {
    int offset = editor.getCaretModel().getOffset();
    PsiDocumentManager.getInstance(project).commitAllDocuments();

    final String uri = findUri(file, offset);
    if (uri == null) return;

    final String url = findUrl(file, offset, uri);

    final ProgressWindow progressWindow = new ProgressWindow(true, project);
    progressWindow.setTitle(QuickFixBundle.message("fetching.resource.title"));
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      new Thread(new Runnable() {
        public void run() {
          while(true) {
            try {
              ProgressManager.getInstance().runProcess(new Runnable() {
                  public void run() {
                    try {
                      HttpConfigurable.getInstance().prepareURL(url);
                      fetchDtd(project, uri, url);
                    }
                    catch (IOException ex) {
                      FetchingResourceIOException exceptionDescribingIOProblem;

                      if (ex instanceof FetchingResourceIOException) {
                        exceptionDescribingIOProblem = (FetchingResourceIOException)ex;
                      } else {
                        exceptionDescribingIOProblem = new FetchingResourceIOException(ex, url);
                      }

                      throw new FetchingResourceProblemRuntimeWrapper(exceptionDescribingIOProblem);
                    }

                  }
                }, progressWindow);
            }
            catch (FetchingResourceProblemRuntimeWrapper e) {
              String message = QuickFixBundle.message("error.fetching.title");
              FetchingResourceIOException ioproblem = (FetchingResourceIOException)e.getCause();

              if (!url.equals(ioproblem.url)) {
                message = QuickFixBundle.message("error.fetching.dependent.resource.title");
              }

              if (!IOExceptionDialog.showErrorDialog(
                (IOException)ioproblem.getCause(),
                message,
                QuickFixBundle.message("error.fetching.resource", ioproblem.url))
              ) {
                break; // cancel fetching
              }
              else {
                continue;  // try another time
              }
            }
            break; // success fetching
          }
        }
      }, FETCHING_THREAD_ID).start();
    }
  }

  private void fetchDtd(final Project project, final String dtdUrl, final String url) throws IOException {
    final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();

    final String extResourcesPath = getExternalResourcesPath();
{'fraction_non_alphanumeric': 0.10075250836120402, 'fraction_numerical': 0.0004180602006688963, 'mean_word_length': 3.0194431108977438, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7594457', 'n_tokens_mistral': 4692, 'n_tokens_neox': 4546, 'n_words': 959}