// returns (injected psi, leaf element at the offset, language of the leaf element)
  // since findElementAt() is expensive, we trying to reuse its result
  @NotNull
  private static Trinity<PsiElement, PsiElement, Language> tryOffset(
      @NotNull PsiFile hostFile, final int offset, @NotNull PsiDocumentManager documentManager) {
    FileViewProvider provider = hostFile.getViewProvider();
    Language leafLanguage = null;
    PsiElement leafElement = null;
    for (Language language : provider.getLanguages()) {
      PsiElement element = provider.findElementAt(offset, language);
      if (element != null) {
        if (leafLanguage == null) {
          leafLanguage = language;
          leafElement = element;
        }
        PsiElement injected = findInside(element, hostFile, offset, documentManager);
        if (injected != null) return Trinity.create(injected, element, language);
      }
      // maybe we are at the border between two psi elements, then try to find injection at the end
      // of the left element
      if (offset != 0 && (element == null || element.getTextRange().getStartOffset() == offset)) {
        PsiElement leftElement = provider.findElementAt(offset - 1, language);
        if (leftElement != null && leftElement.getTextRange().getEndOffset() == offset) {
          PsiElement injected = findInside(leftElement, hostFile, offset, documentManager);
          if (injected != null) return Trinity.create(injected, element, language);
        }
      }
    }

    return Trinity.create(null, leafElement, leafLanguage);
  }
示例#2
0
 @Nullable(
     "null means no luck, otherwise it's tuple(guessed encoding, hint about content if was unable to guess, BOM)")
 public static Trinity<Charset, CharsetToolkit.GuessedEncoding, byte[]> guessFromContent(
     @NotNull VirtualFile virtualFile, @NotNull byte[] content, int length) {
   Charset defaultCharset =
       ObjectUtils.notNull(
           EncodingManager.getInstance().getEncoding(virtualFile, true),
           CharsetToolkit.getDefaultSystemCharset());
   CharsetToolkit toolkit = GUESS_UTF ? new CharsetToolkit(content, defaultCharset) : null;
   String detectedFromBytes = null;
   try {
     if (GUESS_UTF) {
       toolkit.setEnforce8Bit(true);
       Charset charset = toolkit.guessFromBOM();
       if (charset != null) {
         detectedFromBytes = AUTO_DETECTED_FROM_BOM;
         byte[] bom =
             ObjectUtils.notNull(CharsetToolkit.getMandatoryBom(charset), CharsetToolkit.UTF8_BOM);
         return Trinity.create(charset, null, bom);
       }
       CharsetToolkit.GuessedEncoding guessed = toolkit.guessFromContent(length);
       if (guessed == CharsetToolkit.GuessedEncoding.VALID_UTF8) {
         detectedFromBytes = "auto-detected from bytes";
         return Trinity.create(
             CharsetToolkit.UTF8_CHARSET, guessed, null); // UTF detected, ignore all directives
       }
       if (guessed == CharsetToolkit.GuessedEncoding.SEVEN_BIT) {
         return Trinity.create(null, guessed, null);
       }
     }
     return null;
   } finally {
     setCharsetWasDetectedFromBytes(virtualFile, detectedFromBytes);
   }
 }
  private static PsiFile createFileCopy(PsiFile file, long caret, long selEnd) {
    final VirtualFile virtualFile = file.getVirtualFile();
    boolean mayCacheCopy =
        file.isPhysical()
            &&
            // we don't want to cache code fragment copies even if they appear to be physical
            virtualFile != null
            && virtualFile.isInLocalFileSystem();
    long combinedOffsets = caret + (selEnd << 32);
    if (mayCacheCopy) {
      final Trinity<PsiFile, Document, Long> cached =
          SoftReference.dereference(file.getUserData(FILE_COPY_KEY));
      if (cached != null
          && cached.first.getClass().equals(file.getClass())
          && isCopyUpToDate(cached.second, cached.first)) {
        final PsiFile copy = cached.first;
        if (copy.getViewProvider().getModificationStamp()
                > file.getViewProvider().getModificationStamp()
            && cached.third.longValue() != combinedOffsets) {
          // the copy PSI might have some caches that are not cleared on its modification because
          // there are no events in the copy
          //   so, clear all the caches
          // hopefully it's a rare situation that the user invokes completion in different parts of
          // the file
          //   without modifying anything physical in between
          ((PsiModificationTrackerImpl) file.getManager().getModificationTracker()).incCounter();
        }
        final Document document = cached.second;
        assert document != null;
        file.putUserData(
            FILE_COPY_KEY,
            new SoftReference<Trinity<PsiFile, Document, Long>>(
                Trinity.create(copy, document, combinedOffsets)));

        Document originalDocument = file.getViewProvider().getDocument();
        assert originalDocument != null;
        assert originalDocument.getTextLength() == file.getTextLength() : originalDocument;
        document.setText(originalDocument.getImmutableCharSequence());
        return copy;
      }
    }

    final PsiFile copy = (PsiFile) file.copy();
    if (mayCacheCopy) {
      final Document document = copy.getViewProvider().getDocument();
      assert document != null;
      file.putUserData(
          FILE_COPY_KEY,
          new SoftReference<Trinity<PsiFile, Document, Long>>(
              Trinity.create(copy, document, combinedOffsets)));
    }
    return copy;
  }
  // todo [roma] regexp
  @Nullable
  static Trinity<TextRange, TextRange, TextRange> parseExceptionLine(final String line) {
    int startIdx;
    if (line.startsWith(AT_PREFIX)) {
      startIdx = 0;
    } else {
      startIdx = line.indexOf(STANDALONE_AT);
      if (startIdx < 0) {
        startIdx = line.indexOf(AT_PREFIX);
      }

      if (startIdx < 0) {
        startIdx = -1;
      }
    }

    final int lparenIdx = line.indexOf('(', startIdx);
    if (lparenIdx < 0) return null;
    final int dotIdx = line.lastIndexOf('.', lparenIdx);
    if (dotIdx < 0 || dotIdx < startIdx) return null;

    final int rparenIdx = line.indexOf(')', lparenIdx);
    if (rparenIdx < 0) return null;

    // class, method, link
    return Trinity.create(
        new TextRange(
            startIdx + 1 + (startIdx >= 0 ? AT.length() : 0), handleSpaces(line, dotIdx, -1, true)),
        new TextRange(
            handleSpaces(line, dotIdx + 1, 1, true), handleSpaces(line, lparenIdx + 1, -1, true)),
        new TextRange(lparenIdx, rparenIdx));
  }
 private void startListening(
     @NotNull final PsiModifierListOwner expectedOwner,
     @NotNull final String expectedAnnotationFQName,
     final boolean expectedSuccessful) {
   startListening(
       Arrays.asList(Trinity.create(expectedOwner, expectedAnnotationFQName, expectedSuccessful)));
 }
 @NotNull
 private Trinity<String[], VirtualFile[], VirtualFile[]> cacheThings() {
   Trinity<String[], VirtualFile[], VirtualFile[]> result;
   if (myList.isEmpty()) {
     result = EMPTY;
   } else {
     VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]);
     List<VirtualFile> cachedFiles = new ArrayList<VirtualFile>(vf.length);
     List<String> cachedUrls = new ArrayList<String>(vf.length);
     List<VirtualFile> cachedDirectories = new ArrayList<VirtualFile>(vf.length / 3);
     boolean allFilesAreDirs = true;
     for (VirtualFilePointer v : vf) {
       VirtualFile file = v.getFile();
       String url = v.getUrl();
       cachedUrls.add(url);
       if (file != null) {
         cachedFiles.add(file);
         if (file.isDirectory()) {
           cachedDirectories.add(file);
         } else {
           allFilesAreDirs = false;
         }
       }
     }
     VirtualFile[] directories = VfsUtilCore.toVirtualFileArray(cachedDirectories);
     VirtualFile[] files =
         allFilesAreDirs ? directories : VfsUtilCore.toVirtualFileArray(cachedFiles);
     String[] urlsArray = ArrayUtil.toStringArray(cachedUrls);
     result = Trinity.create(urlsArray, files, directories);
   }
   myCachedThings = result;
   myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount();
   return result;
 }
  public void initMarkers(Place shreds) {
    SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject);
    int curOffset = -1;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      final RangeMarker rangeMarker =
          myNewDocument.createRangeMarker(
              shred.getRange().getStartOffset() + shred.getPrefix().length(),
              shred.getRange().getEndOffset() - shred.getSuffix().length());
      final TextRange rangeInsideHost = shred.getRangeInsideHost();
      PsiLanguageInjectionHost host = shred.getHost();
      RangeMarker origMarker =
          myOrigDocument.createRangeMarker(
              rangeInsideHost.shiftRight(host.getTextRange().getStartOffset()));
      SmartPsiElementPointer<PsiLanguageInjectionHost> elementPointer =
          smartPointerManager.createSmartPsiElementPointer(host);
      Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> markers =
          Trinity.<RangeMarker, RangeMarker, SmartPsiElementPointer>create(
              origMarker, rangeMarker, elementPointer);
      myMarkers.add(markers);

      origMarker.setGreedyToRight(true);
      rangeMarker.setGreedyToRight(true);
      if (origMarker.getStartOffset() > curOffset) {
        origMarker.setGreedyToLeft(true);
        rangeMarker.setGreedyToLeft(true);
      }
      curOffset = origMarker.getEndOffset();
    }
    initGuardedBlocks(shreds);
  }
 public Trinity<List<LookupElement>, Iterable<List<LookupElement>>, Boolean> getModelSnapshot() {
   synchronized (lock) {
     final List<LookupElement> sorted = new ArrayList<LookupElement>(mySortedItems);
     final Iterable<List<LookupElement>> groups = myRelevanceClassifier.classify(sorted);
     boolean changed = lastAccess != stamp;
     lastAccess = stamp;
     return Trinity.create(sorted, groups, changed);
   }
 }
  public void testAnnotateLibrary() throws Throwable {

    addDefaultLibrary();
    myFixture.configureByFiles("lib/p/TestPrimitive.java", "content/anno/p/annotations.xml");
    myFixture.configureByFiles("lib/p/Test.java");
    final PsiFile file = myFixture.getFile();
    final Editor editor = myFixture.getEditor();

    final IntentionAction fix = myFixture.findSingleIntention("Annotate method 'get' as @NotNull");
    assertTrue(fix.isAvailable(myProject, editor, file));

    // expecting other @Nullable annotations to be removed, and default @NotNull to be added
    List<Trinity<PsiModifierListOwner, String, Boolean>> expectedSequence =
        new ArrayList<Trinity<PsiModifierListOwner, String, Boolean>>();
    for (String notNull : NullableNotNullManager.getInstance(myProject).getNullables()) {
      expectedSequence.add(Trinity.create(getOwner(), notNull, false));
    }
    expectedSequence.add(Trinity.create(getOwner(), AnnotationUtil.NOT_NULL, true));
    startListening(expectedSequence);
    new WriteCommandAction(myProject) {
      @Override
      protected void run(final Result result) throws Throwable {
        fix.invoke(myProject, editor, file);
      }
    }.execute();

    FileDocumentManager.getInstance().saveAllDocuments();

    final PsiElement psiElement = file.findElementAt(editor.getCaretModel().getOffset());
    assertNotNull(psiElement);
    final PsiModifierListOwner listOwner =
        PsiTreeUtil.getParentOfType(psiElement, PsiModifierListOwner.class);
    assertNotNull(listOwner);
    assertNotNull(
        ExternalAnnotationsManager.getInstance(myProject)
            .findExternalAnnotation(listOwner, AnnotationUtil.NOT_NULL));
    stopListeningAndCheckEvents();

    myFixture.checkResultByFile(
        "content/anno/p/annotations.xml",
        "content/anno/p/annotationsAnnotateLibrary_after.xml",
        false);
  }
 @NotNull
 private GroovyResolveResult[] doPolyResolveWithCaching(
     final boolean incompleteCode, final boolean genericsMatter) {
   final InferenceContext context = TypeInferenceHelper.getCurrentContext();
   final Trinity<?, ?, ?> key =
       Trinity.create(this, context, Pair.create(incompleteCode, genericsMatter));
   final GroovyResolveResult[] value =
       RecursionManager.doPreventingRecursion(
           key,
           true,
           new Computable<GroovyResolveResult[]>() {
             @Override
             public GroovyResolveResult[] compute() {
               return doPolyResolve(incompleteCode, genericsMatter);
             }
           });
   return value == null ? GroovyResolveResult.EMPTY_ARRAY : value;
 }
 @NotNull
 private Pair<Boolean, GroovyResolveResult[]> resolveByShape(
     final boolean allVariants, @Nullable final GrExpression upToArgument) {
   LOG.assertTrue(allVariants || upToArgument == null);
   final Trinity key =
       Trinity.create(
           TypeInferenceHelper.getCurrentContext(), this, Pair.create(allVariants, upToArgument));
   final Pair<Boolean, GroovyResolveResult[]> result =
       RecursionManager.doPreventingRecursion(
           key,
           true,
           new Computable<Pair<Boolean, GroovyResolveResult[]>>() {
             @Override
             public Pair<Boolean, GroovyResolveResult[]> compute() {
               return doResolveByShape(allVariants, upToArgument);
             }
           });
   return result == null ? Pair.create(false, GroovyResolveResult.EMPTY_ARRAY) : result;
 }
  @Nullable
  private <TRef extends PsiReference, TResult> TResult resolve(
      @NotNull final TRef ref,
      @NotNull final AbstractResolver<TRef, TResult> resolver,
      boolean needToPreventRecursion,
      final boolean incompleteCode,
      boolean isPoly,
      boolean isPhysical) {
    ProgressIndicatorProvider.checkCanceled();
    if (isPhysical) {
      ApplicationManager.getApplication().assertReadAccessAllowed();
    }

    int index = getIndex(isPhysical, incompleteCode, isPoly);
    ConcurrentMap<TRef, TResult> map = getMap(index);
    TResult result = map.get(ref);
    if (result != null) {
      return result;
    }

    RecursionGuard.StackStamp stamp = myGuard.markStack();
    result =
        needToPreventRecursion
            ? myGuard.doPreventingRecursion(
                Trinity.create(ref, incompleteCode, isPoly),
                true,
                new Computable<TResult>() {
                  @Override
                  public TResult compute() {
                    return resolver.resolve(ref, incompleteCode);
                  }
                })
            : resolver.resolve(ref, incompleteCode);
    PsiElement element =
        result instanceof ResolveResult ? ((ResolveResult) result).getElement() : null;
    LOG.assertTrue(element == null || element.isValid(), result);

    if (stamp.mayCacheNow()) {
      cache(ref, map, result);
    }
    return result;
  }
示例#13
0
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();

    if (myVarIndex.size() != myVarSizes.size()) {
      return "inconsistent";
    }

    List<Trinity<DeclarationDescriptor, Integer, Integer>> descriptors = Lists.newArrayList();

    for (Object descriptor0 : myVarIndex.keys()) {
      DeclarationDescriptor descriptor = (DeclarationDescriptor) descriptor0;
      int varIndex = myVarIndex.get(descriptor);
      int varSize = myVarSizes.get(descriptor);
      descriptors.add(Trinity.create(descriptor, varIndex, varSize));
    }

    Collections.sort(
        descriptors,
        new Comparator<Trinity<DeclarationDescriptor, Integer, Integer>>() {
          @Override
          public int compare(
              Trinity<DeclarationDescriptor, Integer, Integer> left,
              Trinity<DeclarationDescriptor, Integer, Integer> right) {
            return left.second - right.second;
          }
        });

    sb.append("size=").append(myMaxIndex);

    boolean first = true;
    for (Trinity<DeclarationDescriptor, Integer, Integer> t : descriptors) {
      if (!first) {
        sb.append(", ");
      }
      first = false;
      sb.append(t.first).append(",i=").append(t.second).append(",s=").append(t.third);
    }

    return sb.toString();
  }
    @NotNull
    public DfaVariableValue createVariableValue(
        @NotNull PsiModifierListOwner myVariable,
        @Nullable PsiType varType,
        boolean isNegated,
        @Nullable DfaVariableValue qualifier) {
      Trinity<Boolean, String, DfaVariableValue> key =
          Trinity.create(isNegated, ((PsiNamedElement) myVariable).getName(), qualifier);
      for (DfaVariableValue aVar : myExistingVars.get(key)) {
        if (aVar.hardEquals(myVariable, varType, isNegated, qualifier)) return aVar;
      }

      DfaVariableValue result =
          new DfaVariableValue(myVariable, varType, isNegated, myFactory, qualifier);
      myExistingVars.putValue(key, result);
      while (qualifier != null) {
        qualifier.myDependents.add(result);
        qualifier = qualifier.getQualifier();
      }
      return result;
    }
  private static PsiFile insertDummyIdentifierIfNeeded(
      PsiFile file, final int startOffset, final int endOffset, String replacement) {
    Document originalDocument = file.getViewProvider().getDocument();
    assert originalDocument != null;

    if (replacement.isEmpty()
        && PsiDocumentManager.getInstance(file.getProject()).isCommitted(originalDocument)) {
      return file;
    }

    ConcurrentFactoryMap<Trinity<Integer, Integer, String>, PsiFile> map =
        CachedValuesManager.getCachedValue(
            file,
            () ->
                CachedValueProvider.Result.create(
                    new ConcurrentFactoryMap<Trinity<Integer, Integer, String>, PsiFile>() {
                      @Nullable
                      @Override
                      protected PsiFile create(Trinity<Integer, Integer, String> key) {
                        PsiFile copy = (PsiFile) file.copy();

                        final Document document = copy.getViewProvider().getDocument();
                        assert document != null;

                        document.setText(
                            originalDocument
                                .getImmutableCharSequence()); // original file might be uncommitted
                        document.replaceString(key.first, key.second, key.third);
                        PsiDocumentManager.getInstance(copy.getProject()).commitDocument(document);
                        return copy;
                      }
                    },
                    file,
                    originalDocument));

    return map.get(Trinity.create(startOffset, endOffset, replacement));
  }
示例#16
0
  private static Trinity<PsiElement, TextRange, Value> getSelectedExpression(
      final Project project, final Editor editor, final Point point, final ValueHintType type) {
    final Ref<PsiElement> selectedExpression = Ref.create(null);
    final Ref<TextRange> currentRange = Ref.create(null);
    final Ref<Value> preCalculatedValue = Ref.create(null);

    PsiDocumentManager.getInstance(project)
        .commitAndRunReadAction(
            new Runnable() {
              public void run() {
                // Point -> offset
                final int offset = calculateOffset(editor, point);

                PsiFile psiFile =
                    PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());

                if (psiFile == null || !psiFile.isValid()) return;

                int selectionStart = editor.getSelectionModel().getSelectionStart();
                int selectionEnd = editor.getSelectionModel().getSelectionEnd();

                if ((type == ValueHintType.MOUSE_CLICK_HINT
                        || type == ValueHintType.MOUSE_ALT_OVER_HINT)
                    && (selectionStart <= offset && offset <= selectionEnd)) {
                  PsiElement ctx =
                      (selectionStart > 0)
                          ? psiFile.findElementAt(selectionStart - 1)
                          : psiFile.findElementAt(selectionStart);
                  try {
                    String text = editor.getSelectionModel().getSelectedText();
                    if (text != null && ctx != null) {
                      selectedExpression.set(
                          JVMElementFactories.getFactory(ctx.getLanguage(), project)
                              .createExpressionFromText(text, ctx));
                      currentRange.set(
                          new TextRange(
                              editor.getSelectionModel().getSelectionStart(),
                              editor.getSelectionModel().getSelectionEnd()));
                    }
                  } catch (IncorrectOperationException ignored) {
                  }
                }

                if (currentRange.get() == null) {
                  PsiElement elementAtCursor = psiFile.findElementAt(offset);
                  if (elementAtCursor == null) {
                    return;
                  }
                  Pair<PsiElement, TextRange> pair =
                      findExpression(
                          elementAtCursor,
                          type == ValueHintType.MOUSE_CLICK_HINT
                              || type == ValueHintType.MOUSE_ALT_OVER_HINT);
                  if (pair == null) {
                    if (type == ValueHintType.MOUSE_OVER_HINT) {
                      final DebuggerSession debuggerSession =
                          DebuggerManagerEx.getInstanceEx(project)
                              .getContext()
                              .getDebuggerSession();
                      if (debuggerSession != null && debuggerSession.isPaused()) {
                        final Pair<Method, Value> lastExecuted =
                            debuggerSession.getProcess().getLastExecutedMethod();
                        if (lastExecuted != null) {
                          final Method method = lastExecuted.getFirst();
                          if (method != null) {
                            final Pair<PsiElement, TextRange> expressionPair =
                                findExpression(elementAtCursor, true);
                            if (expressionPair != null
                                && expressionPair.getFirst() instanceof PsiMethodCallExpression) {
                              final PsiMethodCallExpression methodCallExpression =
                                  (PsiMethodCallExpression) expressionPair.getFirst();
                              final PsiMethod psiMethod = methodCallExpression.resolveMethod();
                              if (psiMethod != null) {
                                final JVMName jvmSignature = JVMNameUtil.getJVMSignature(psiMethod);
                                try {
                                  if (method.name().equals(psiMethod.getName())
                                      && method
                                          .signature()
                                          .equals(
                                              jvmSignature.getName(debuggerSession.getProcess()))) {
                                    pair = expressionPair;
                                    preCalculatedValue.set(lastExecuted.getSecond());
                                  }
                                } catch (EvaluateException ignored) {
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                  if (pair == null) {
                    return;
                  }
                  selectedExpression.set(pair.getFirst());
                  currentRange.set(pair.getSecond());
                }
              }
            });
    return Trinity.create(selectedExpression.get(), currentRange.get(), preCalculatedValue.get());
  }
 @Override
 public void addMessage(PsiElement context, String message, @NotNull ErrorType type) {
   messages.add(Trinity.create(context, message, type));
 }
/** @author dsl */
public class VirtualFilePointerContainerImpl extends TraceableDisposable
    implements VirtualFilePointerContainer, Disposable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer");

  @NotNull
  private final List<VirtualFilePointer> myList = ContainerUtil.createLockFreeCopyOnWriteList();

  @NotNull private final VirtualFilePointerManager myVirtualFilePointerManager;
  @NotNull private final Disposable myParent;
  private final VirtualFilePointerListener myListener;
  private volatile Trinity<String[], VirtualFile[], VirtualFile[]> myCachedThings;
  private volatile long myTimeStampOfCachedThings = -1;
  @NonNls public static final String URL_ATTR = "url";
  private boolean myDisposed;
  private static final boolean TRACE_CREATION =
      LOG.isDebugEnabled() || ApplicationManager.getApplication().isUnitTestMode();

  public VirtualFilePointerContainerImpl(
      @NotNull VirtualFilePointerManager manager,
      @NotNull Disposable parentDisposable,
      @Nullable VirtualFilePointerListener listener) {
    //noinspection HardCodedStringLiteral
    super(
        TRACE_CREATION && !ApplicationInfoImpl.isInPerformanceTest()
            ? new Throwable(
                "parent = '"
                    + parentDisposable
                    + "' ("
                    + parentDisposable.getClass()
                    + "); listener="
                    + listener)
            : null);
    myVirtualFilePointerManager = manager;
    myParent = parentDisposable;
    myListener = listener;
  }

  @Override
  public void readExternal(@NotNull final Element rootChild, @NotNull final String childElements)
      throws InvalidDataException {
    final List urls = rootChild.getChildren(childElements);
    for (Object url : urls) {
      Element pathElement = (Element) url;
      final String urlAttribute = pathElement.getAttributeValue(URL_ATTR);
      if (urlAttribute == null) throw new InvalidDataException("path element without url");
      add(urlAttribute);
    }
  }

  @Override
  public void writeExternal(
      @NotNull final Element element, @NotNull final String childElementName) {
    for (VirtualFilePointer pointer : myList) {
      String url = pointer.getUrl();
      final Element rootPathElement = new Element(childElementName);
      rootPathElement.setAttribute(URL_ATTR, url);
      element.addContent(rootPathElement);
    }
  }

  @Override
  public void moveUp(@NotNull String url) {
    int index = indexOf(url);
    if (index <= 0) return;
    dropCaches();
    ContainerUtil.swapElements(myList, index - 1, index);
  }

  @Override
  public void moveDown(@NotNull String url) {
    int index = indexOf(url);
    if (index < 0 || index + 1 >= myList.size()) return;
    dropCaches();
    ContainerUtil.swapElements(myList, index, index + 1);
  }

  private int indexOf(@NotNull final String url) {
    for (int i = 0; i < myList.size(); i++) {
      final VirtualFilePointer pointer = myList.get(i);
      if (url.equals(pointer.getUrl())) {
        return i;
      }
    }

    return -1;
  }

  @Override
  public void killAll() {
    myList.clear();
  }

  @Override
  public void add(@NotNull VirtualFile file) {
    assert !myDisposed;
    dropCaches();
    final VirtualFilePointer pointer = create(file);
    myList.add(pointer);
  }

  @Override
  public void add(@NotNull String url) {
    assert !myDisposed;
    dropCaches();
    final VirtualFilePointer pointer = create(url);
    myList.add(pointer);
  }

  @Override
  public void remove(@NotNull VirtualFilePointer pointer) {
    assert !myDisposed;
    dropCaches();
    final boolean result = myList.remove(pointer);
    LOG.assertTrue(result);
  }

  @Override
  @NotNull
  public List<VirtualFilePointer> getList() {
    assert !myDisposed;
    return Collections.unmodifiableList(myList);
  }

  @Override
  public void addAll(@NotNull VirtualFilePointerContainer that) {
    assert !myDisposed;
    dropCaches();

    List<VirtualFilePointer> thatList = ((VirtualFilePointerContainerImpl) that).myList;
    for (final VirtualFilePointer pointer : thatList) {
      myList.add(duplicate(pointer));
    }
  }

  private void dropCaches() {
    myTimeStampOfCachedThings =
        -1; // make it never equal to myVirtualFilePointerManager.getModificationCount()
    myCachedThings = EMPTY;
  }

  @Override
  @NotNull
  public String[] getUrls() {
    return getOrCache().first;
  }

  @NotNull
  private Trinity<String[], VirtualFile[], VirtualFile[]> getOrCache() {
    assert !myDisposed;
    long timeStamp = myTimeStampOfCachedThings;
    Trinity<String[], VirtualFile[], VirtualFile[]> cached = myCachedThings;
    return timeStamp == myVirtualFilePointerManager.getModificationCount() ? cached : cacheThings();
  }

  private static final Trinity<String[], VirtualFile[], VirtualFile[]> EMPTY =
      Trinity.create(
          ArrayUtil.EMPTY_STRING_ARRAY, VirtualFile.EMPTY_ARRAY, VirtualFile.EMPTY_ARRAY);

  @NotNull
  private Trinity<String[], VirtualFile[], VirtualFile[]> cacheThings() {
    Trinity<String[], VirtualFile[], VirtualFile[]> result;
    if (myList.isEmpty()) {
      result = EMPTY;
    } else {
      VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]);
      List<VirtualFile> cachedFiles = new ArrayList<VirtualFile>(vf.length);
      List<String> cachedUrls = new ArrayList<String>(vf.length);
      List<VirtualFile> cachedDirectories = new ArrayList<VirtualFile>(vf.length / 3);
      boolean allFilesAreDirs = true;
      for (VirtualFilePointer v : vf) {
        VirtualFile file = v.getFile();
        String url = v.getUrl();
        cachedUrls.add(url);
        if (file != null) {
          cachedFiles.add(file);
          if (file.isDirectory()) {
            cachedDirectories.add(file);
          } else {
            allFilesAreDirs = false;
          }
        }
      }
      VirtualFile[] directories = VfsUtilCore.toVirtualFileArray(cachedDirectories);
      VirtualFile[] files =
          allFilesAreDirs ? directories : VfsUtilCore.toVirtualFileArray(cachedFiles);
      String[] urlsArray = ArrayUtil.toStringArray(cachedUrls);
      result = Trinity.create(urlsArray, files, directories);
    }
    myCachedThings = result;
    myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount();
    return result;
  }

  @Override
  @NotNull
  public VirtualFile[] getFiles() {
    return getOrCache().second;
  }

  @Override
  @NotNull
  public VirtualFile[] getDirectories() {
    return getOrCache().third;
  }

  @Override
  @Nullable
  public VirtualFilePointer findByUrl(@NotNull String url) {
    assert !myDisposed;
    for (VirtualFilePointer pointer : myList) {
      if (url.equals(pointer.getUrl())) return pointer;
    }
    return null;
  }

  @Override
  public void clear() {
    dropCaches();
    killAll();
  }

  @Override
  public int size() {
    return myList.size();
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof VirtualFilePointerContainerImpl)) return false;

    final VirtualFilePointerContainerImpl virtualFilePointerContainer =
        (VirtualFilePointerContainerImpl) o;

    return myList.equals(virtualFilePointerContainer.myList);
  }

  public int hashCode() {
    return myList.hashCode();
  }

  protected VirtualFilePointer create(@NotNull VirtualFile file) {
    return myVirtualFilePointerManager.create(file, myParent, myListener);
  }

  protected VirtualFilePointer create(@NotNull String url) {
    return myVirtualFilePointerManager.create(url, myParent, myListener);
  }

  protected VirtualFilePointer duplicate(@NotNull VirtualFilePointer virtualFilePointer) {
    return myVirtualFilePointerManager.duplicate(virtualFilePointer, myParent, myListener);
  }

  @NotNull
  @NonNls
  @Override
  public String toString() {
    return "VFPContainer: " + myList /*+"; parent:"+myParent*/;
  }

  @Override
  @NotNull
  public VirtualFilePointerContainer clone(@NotNull Disposable parent) {
    return clone(parent, null);
  }

  @Override
  @NotNull
  public VirtualFilePointerContainer clone(
      @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) {
    assert !myDisposed;
    VirtualFilePointerContainer clone =
        myVirtualFilePointerManager.createContainer(parent, listener);
    for (VirtualFilePointer pointer : myList) {
      clone.add(pointer.getUrl());
    }
    return clone;
  }

  @Override
  public void dispose() {
    assert !myDisposed;
    myDisposed = true;
    kill(null);
  }
}
  @Override
  public void startRunProfile(
      @NotNull final RunProfileStarter starter,
      @NotNull final RunProfileState state,
      @NotNull final ExecutionEnvironment environment) {
    final Project project = environment.getProject();
    RunContentDescriptor reuseContent = getContentManager().getReuseContent(environment);
    if (reuseContent != null) {
      reuseContent.setExecutionId(environment.getExecutionId());
      environment.setContentToReuse(reuseContent);
    }

    final Executor executor = environment.getExecutor();
    project
        .getMessageBus()
        .syncPublisher(EXECUTION_TOPIC)
        .processStartScheduled(executor.getId(), environment);

    Runnable startRunnable;
    startRunnable =
        () -> {
          if (project.isDisposed()) {
            return;
          }

          RunProfile profile = environment.getRunProfile();
          boolean started = false;
          try {
            project
                .getMessageBus()
                .syncPublisher(EXECUTION_TOPIC)
                .processStarting(executor.getId(), environment);

            final RunContentDescriptor descriptor = starter.execute(state, environment);
            if (descriptor != null) {
              final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor>
                  trinity =
                      Trinity.create(
                          descriptor, environment.getRunnerAndConfigurationSettings(), executor);
              myRunningConfigurations.add(trinity);
              Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity));
              getContentManager()
                  .showRunContent(executor, descriptor, environment.getContentToReuse());
              final ProcessHandler processHandler = descriptor.getProcessHandler();
              if (processHandler != null) {
                if (!processHandler.isStartNotified()) {
                  processHandler.startNotify();
                }
                project
                    .getMessageBus()
                    .syncPublisher(EXECUTION_TOPIC)
                    .processStarted(executor.getId(), environment, processHandler);
                started = true;

                ProcessExecutionListener listener =
                    new ProcessExecutionListener(
                        project, executor.getId(), environment, processHandler, descriptor);
                processHandler.addProcessListener(listener);

                // Since we cannot guarantee that the listener is added before process handled is
                // start notified,
                // we have to make sure the process termination events are delivered to the clients.
                // Here we check the current process state and manually deliver events, while
                // the ProcessExecutionListener guarantees each such event is only delivered once
                // either by this code, or by the ProcessHandler.

                boolean terminating = processHandler.isProcessTerminating();
                boolean terminated = processHandler.isProcessTerminated();
                if (terminating || terminated) {
                  listener.processWillTerminate(
                      new ProcessEvent(processHandler), false /*doesn't matter*/);

                  if (terminated) {
                    //noinspection ConstantConditions
                    int exitCode = processHandler.getExitCode();
                    listener.processTerminated(new ProcessEvent(processHandler, exitCode));
                  }
                }
              }
              environment.setContentToReuse(descriptor);
            }
          } catch (ProcessCanceledException e) {
            LOG.info(e);
          } catch (ExecutionException e) {
            ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), profile, e);
            LOG.info(e);
          } finally {
            if (!started) {
              project
                  .getMessageBus()
                  .syncPublisher(EXECUTION_TOPIC)
                  .processNotStarted(executor.getId(), environment);
            }
          }
        };

    if (ApplicationManager.getApplication().isUnitTestMode() && !myForceCompilationInTests) {
      startRunnable.run();
    } else {
      compileAndRun(
          () -> TransactionGuard.submitTransaction(project, startRunnable),
          environment,
          state,
          () -> {
            if (!project.isDisposed()) {
              project
                  .getMessageBus()
                  .syncPublisher(EXECUTION_TOPIC)
                  .processNotStarted(executor.getId(), environment);
            }
          });
    }
  }