Optimized deferred output processing memory consumption(removed excessive new String...
authorDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Mon, 11 Apr 2011 16:50:04 +0000 (20:50 +0400)
committerDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Mon, 11 Apr 2011 17:54:03 +0000 (21:54 +0400)
platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java
platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java

index 8d0b691364a1d5f11f8e5fb05ac39e8c098c9874..d49efffeb8b8986b726f76e8beaefdea2d98936d 100644 (file)
@@ -39,7 +39,7 @@ import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo;
  * <p/>
  * <b>Note:</b> basically this class consists of functionality that is cut from {@link ConsoleViewImpl} in order to make it possible
  * to cover it by tests.
- * 
+ *
  * @author Denis Zhdanov
  * @since 4/5/11 5:26 PM
  */
@@ -54,9 +54,9 @@ public class ConsoleBuffer {
    * <p/>
    * Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract.
    */
-  private final Deque<StringBuilder>        myDeferredOutput                  = new ArrayDeque<StringBuilder>();
+  private final Deque<StringBuilder> myDeferredOutput = new ArrayDeque<StringBuilder>();
   private final Set<ConsoleViewContentType> myContentTypesToNotStripOnCycling = new HashSet<ConsoleViewContentType>();
-  
+
   /**
    * Main console usage scenario assumes the following:
    * <pre>
@@ -85,8 +85,8 @@ public class ConsoleBuffer {
    * to be moved. Current constant defines default size of that small buffers.
    */
 
-  private final int     myCyclicBufferSize;
-  private final int     myCyclicBufferUnitSize;
+  private final int myCyclicBufferSize;
+  private final int myCyclicBufferUnitSize;
   private final boolean myUseCyclicBuffer;
 
   /**
@@ -94,7 +94,9 @@ public class ConsoleBuffer {
    */
   private int myDeferredOutputLength;
 
-  /** Buffer for deferred stdin and stderr output. */
+  /**
+   * Buffer for deferred stdin and stderr output.
+   */
   private StringBuffer myDeferredUserInput = new StringBuffer();
 
   /**
@@ -120,7 +122,7 @@ public class ConsoleBuffer {
     final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size");
     return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty);
   }
-  
+
   private static int getCycleBufferSize() {
     final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
     if (cycleBufferSizeProperty == null) return 1024 * 1024;
@@ -143,7 +145,7 @@ public class ConsoleBuffer {
   public boolean isEmpty() {
     return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0);
   }
-  
+
   public int getLength() {
     return myDeferredOutputLength;
   }
@@ -168,19 +170,16 @@ public class ConsoleBuffer {
     return myDeferredOutput;
   }
 
-  public String getText() {
-    if (myDeferredOutput.size() > 1) {
+  public StringBuilder[] getText() {
+    if (myDeferredOutput.size() > 0) {
       final StringBuilder buffer = new StringBuilder();
       for (StringBuilder builder : myDeferredOutput) {
         buffer.append(builder);
       }
-      return buffer.toString();
-    }
-    else if (myDeferredOutput.size() == 1) {
-      return myDeferredOutput.getFirst().substring(0);
+      return myDeferredOutput.toArray(new StringBuilder[myDeferredOutput.size()]);
     }
     else {
-      return "";
+      return new StringBuilder[0];
     }
   }
 
@@ -190,14 +189,14 @@ public class ConsoleBuffer {
    * <p/>
    * {@link ConsoleViewContentType#USER_INPUT} is considered to be such a type by default, however, it's possible to overwrite that
    * via the current method.
-   * 
-   * @param types       content types that should not be stripped during the buffer's cycling
+   *
+   * @param types content types that should not be stripped during the buffer's cycling
    */
   public void setContentTypesToNotStripOnCycling(@NotNull Collection<ConsoleViewContentType> types) {
     myContentTypesToNotStripOnCycling.clear();
     myContentTypesToNotStripOnCycling.addAll(types);
   }
-  
+
   public void clear() {
     if (myUseCyclicBuffer) {
       myDeferredOutput.clear();
@@ -213,7 +212,7 @@ public class ConsoleBuffer {
     myDeferredUserInput = new StringBuffer();
     myDeferredTokens.clear();
   }
-  
+
   @Nullable
   public String cutFirstUserInputLine() {
     final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length());
@@ -238,16 +237,16 @@ public class ConsoleBuffer {
   public void replaceUserText(int startOffset, int endOffset, String text) {
     myDeferredUserInput.replace(startOffset, endOffset, text);
   }
-  
+
   /**
    * Asks current buffer to store given text of the given type.
-   * 
-   * @param s               text to store
-   * @param contentType     type of the given text
-   * @param info            hyperlink info for the given text (if any)
-   * @return                text that is actually stored (there is a possible case that the buffer is full and given text's type
-   *                        is considered to have lower priority than the stored one, hence, it's better to drop given text completely
-   *                        or partially) and number of existed symbols removed during storing the given data
+   *
+   * @param s           text to store
+   * @param contentType type of the given text
+   * @param info        hyperlink info for the given text (if any)
+   * @return text that is actually stored (there is a possible case that the buffer is full and given text's type
+   *         is considered to have lower priority than the stored one, hence, it's better to drop given text completely
+   *         or partially) and number of existed symbols removed during storing the given data
    */
   @NotNull
   public Pair<String, Integer> print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
@@ -272,7 +271,7 @@ public class ConsoleBuffer {
     myDeferredTypes.add(contentType);
 
     s = StringUtil.convertLineSeparators(s, !SystemInfo.isMac);
-    
+
     myDeferredOutputLength += s.length();
     StringBuilder bufferToUse;
     if (myDeferredOutput.isEmpty()) {
@@ -286,7 +285,7 @@ public class ConsoleBuffer {
       if (bufferToUse.length() >= myCyclicBufferUnitSize) {
         myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize));
       }
-      
+
       if (bufferToUse.length() < myCyclicBufferUnitSize) {
         int numberOfSymbolsToAdd = Math.min(myCyclicBufferUnitSize - bufferToUse.length(), s.length() - offset);
         bufferToUse.append(s.substring(offset, offset + numberOfSymbolsToAdd));
@@ -297,7 +296,7 @@ public class ConsoleBuffer {
     if (contentType == ConsoleViewContentType.USER_INPUT) {
       myDeferredUserInput.append(s);
     }
-    
+
     ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens);
     return new Pair<String, Integer>(s, trimmedSymbolsNumber);
   }
@@ -360,7 +359,7 @@ public class ConsoleBuffer {
   //    }
   //  }
   //}
-  
+
   /**
    * IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and
    * pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process
@@ -371,8 +370,8 @@ public class ConsoleBuffer {
    * to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of
    * text is received from the underlying process and trims existing text buffer if necessary.
    *
-   * @param numberOfNewSymbols    number of symbols read from the managed process output
-   * @return                      number of newly read symbols that should be accepted
+   * @param numberOfNewSymbols number of symbols read from the managed process output
+   * @return number of newly read symbols that should be accepted
    */
   @SuppressWarnings({"ForLoopReplaceableByForEach"})
   private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) {
@@ -392,13 +391,13 @@ public class ConsoleBuffer {
     }
 
     Context context = new Context(numberOfSymbolsToRemove);
-    
+
     TIntArrayList indicesOfTokensToRemove = new TIntArrayList();
     for (int i = 0; i < myDeferredTokens.size(); i++) {
       TokenInfo tokenInfo = myDeferredTokens.get(i);
       tokenInfo.startOffset -= context.removedSymbolsNumber;
       tokenInfo.endOffset -= context.removedSymbolsNumber;
-      
+
       if (!context.canContinueProcessing()) {
         // Just update token offsets.
         myDeferredTypes.add(tokenInfo.contentType);
@@ -435,8 +434,9 @@ public class ConsoleBuffer {
       TokenInfo tokenInfo = myDeferredTokens.get(0);
       if (tokenInfo.startOffset > 0) {
         final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo();
-        myDeferredTokens.add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo)
-                                                      : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset));
+        myDeferredTokens
+          .add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo)
+                                        : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset));
         myDeferredTypes.add(ConsoleViewContentType.USER_INPUT);
       }
     }
@@ -482,7 +482,7 @@ public class ConsoleBuffer {
       }
     }
   }
-  
+
   private int remove(@NotNull Context context, int tokenLength) {
     int removedSymbolsNumber = 0;
     int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber;
@@ -518,7 +518,7 @@ public class ConsoleBuffer {
     }
     return removedSymbolsNumber;
   }
-  
+
   private final class Context {
 
     public final int numberOfSymbolsToRemove;
@@ -537,11 +537,11 @@ public class ConsoleBuffer {
         currentBuffer = null;
       }
     }
-    
+
     public boolean canContinueProcessing() {
       return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null;
     }
-    
+
     public boolean nextBuffer() {
       if (iterator.hasNext()) {
         currentBuffer = iterator.next();
@@ -551,9 +551,9 @@ public class ConsoleBuffer {
       return false;
     }
   }
-  
+
   @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
-  private  void dumpDeferredOutput() {
+  private void dumpDeferredOutput() {
     if (!DEBUG_PROCESSING) {
       return;
     }
@@ -567,7 +567,7 @@ public class ConsoleBuffer {
     }
     log("-----------------------------------------------------------------------------------------------------");
   }
-  
+
   @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"})
   private static void log(Object o) {
     //try {
index c410e9239c925d9d59655a8e415f64dec93f25ef..2b6345156e859582fd541d669e7dc9a34e90ae40 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.intellij.execution.impl;
 
+import com.google.common.collect.Lists;
 import com.intellij.codeInsight.navigation.IncrementalSearchHandler;
 import com.intellij.execution.ConsoleFolding;
 import com.intellij.execution.ExecutionBundle;
@@ -69,7 +70,6 @@ import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.LineTokenizer;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.pom.Navigatable;
 import com.intellij.psi.PsiDocumentManager;
@@ -102,7 +102,7 @@ import java.util.List;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
-  
+
   private @NonNls String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
   private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
 
@@ -475,7 +475,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
 
   private void printHyperlink(String s, ConsoleViewContentType contentType, HyperlinkInfo info) {
     synchronized (LOCK) {
-      Pair<String,Integer> pair = myBuffer.print(s, contentType, info);
+      Pair<String, Integer> pair = myBuffer.print(s, contentType, info);
       s = pair.first;
       myContentSize += s.length() - pair.second;
 
@@ -492,7 +492,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
   }
 
   protected void beforeExternalAddContentToDocument(int length, ConsoleViewContentType contentType) {
-    myContentSize+=length;
+    myContentSize += length;
     addToken(length, null, contentType);
   }
 
@@ -545,8 +545,8 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
       }, null, DocCommandGroupId.noneGroupId(document));
     }
 
-    
-    final String text;
+
+    final StringBuilder[] text;
     final Collection<ConsoleViewContentType> contentTypes;
     int deferredTokensSize;
     synchronized (LOCK) {
@@ -555,7 +555,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
       if (myEditor == null) return;
 
       text = myBuffer.getText();
-      
+
       contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
       List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
       for (TokenInfo deferredToken : deferredTokens) {
@@ -567,7 +567,14 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
     final Document document = myEditor.getDocument();
     final int oldLineCount = document.getLineCount();
     final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength();
-    boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + text.length() > myBuffer.getCyclicBufferSize();
+    int textLength = 0;
+
+    final List<String> textLines = splitToLines(text);
+    for (String line : textLines) {
+      textLength += line.length();
+    }
+
+    boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + textLength > myBuffer.getCyclicBufferSize();
     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
       public void run() {
         int offset = myEditor.getCaretModel().getOffset();
@@ -576,15 +583,15 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
           myEditor.getScrollingModel().accumulateViewportChanges();
         }
         try {
-          String[] strings = text.split("\\r");
-          for (int i = 0; i < strings.length - 1; i++) {
-            document.insertString(document.getTextLength(), strings[i]);
+
+          for (int i = 0; i < textLines.size() - 1; i++) {
+            document.insertString(document.getTextLength(), textLines.get(i));
             int lastLine = document.getLineCount() - 1;
             if (lastLine >= 0) {
               document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
             }
           }
-          document.insertString(document.getTextLength(), strings[strings.length - 1]);
+          document.insertString(document.getTextLength(), textLines.get(textLines.size() - 1));
         }
         finally {
           if (preserveCurrentVisualArea) {
@@ -599,7 +606,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
       }
     }, null, DocCommandGroupId.noneGroupId(document));
     synchronized (LOCK) {
-      for (int i = myTokens.size() - 1; i >=0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
+      for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
         TokenInfo token = myTokens.get(i);
         final HyperlinkInfo info = token.getHyperlinkInfo();
         if (info != null) {
@@ -610,7 +617,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
     myPsiDisposedCheck.performCheck();
     final int newLineCount = document.getLineCount();
     if (cycleUsed) {
-      final int lineCount = LineTokenizer.calcLineCount(text, true);
+      final int lineCount = textLines.size();
       for (Iterator<RangeHighlighter> it = myHyperlinks.getRanges().keySet().iterator(); it.hasNext();) {
         if (!it.next().isValid()) {
           it.remove();
@@ -627,6 +634,30 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
     }
   }
 
+  private List<String> splitToLines(StringBuilder[] text) {
+    final List<String> textLines = Lists.newArrayList();
+    StringBuilder line = new StringBuilder();
+    for (StringBuilder textItem : text) {
+      for (int j = 0; j < textItem.length(); j++) {
+        if (textItem.charAt(j) == '\r') {
+          textLines.add(new String(line));
+          line.setLength(0);
+        }
+        else {
+          line.append(textItem.charAt(j));
+        }
+      }
+    }
+    if (line.length() > 0) {
+      textLines.add(line.toString());
+    }
+
+    if (textLines.size() == 0) {
+      textLines.add("");
+    }
+    return textLines;
+  }
+
   private void flushDeferredUserInput() {
     final String textToSend = myBuffer.cutFirstUserInputLine();
     if (textToSend == null) {
@@ -773,7 +804,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
       @Override
       public Color getDefaultBackground() {
         final Color color = getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY);
-        return color == null? super.getDefaultBackground() : color;
+        return color == null ? super.getDefaultBackground() : color;
       }
     };
     editor.setColorsScheme(scheme);
@@ -1510,7 +1541,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
       scrollTo(next.getStartOffset());
     }
     final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next);
-    return  hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() {
+    return hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() {
       public void navigate(final boolean requestFocus) {
         hyperlinkInfo.navigate(myProject);
         linkFollowed(hyperlinkInfo);
@@ -1551,7 +1582,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
        * used soft wraps mode and perform update if we see that the current value differs from the stored.
        */
       private boolean myLastIsSelected;
-      
+
       @Override
       protected Editor getEditor(AnActionEvent e) {
         return myEditor;