IDEA-63980 Make idea.cycle.buffer.size configurable in IDEA GUI / IDEA settings
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleBuffer.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.impl;
17
18 import com.intellij.execution.filters.HyperlinkInfo;
19 import com.intellij.execution.ui.ConsoleViewContentType;
20 import com.intellij.ide.ui.UISettings;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.util.text.StringUtil;
25 import gnu.trove.TIntArrayList;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28
29 import java.util.*;
30
31 import static com.intellij.execution.impl.ConsoleViewImpl.HyperlinkTokenInfo;
32 import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo;
33
34 /**
35  * IJ user may want the console to use cyclic buffer, i.e. don't keep more than particular amount of symbols. So, we need
36  * to have a data structure that allow to achieve that. This class serves for that purpose.
37  * <p/>
38  * Not thread-safe.
39  * <p/>
40  * <b>Note:</b> basically this class consists of functionality that is cut from {@link ConsoleViewImpl} in order to make it possible
41  * to cover it by tests.
42  *
43  * @author Denis Zhdanov
44  * @since 4/5/11 5:26 PM
45  */
46 public class ConsoleBuffer {
47
48   private static final int DEFAULT_CYCLIC_BUFFER_UNIT_SIZE = 256;
49
50   private static final boolean DEBUG_PROCESSING = false;
51
52   /**
53    * Buffer for deferred stdout, stderr and stdin output.
54    * <p/>
55    * Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract.
56    */
57   private final Deque<StringBuilder> myDeferredOutput = new ArrayDeque<StringBuilder>();
58   private final Set<ConsoleViewContentType> myContentTypesToNotStripOnCycling = new HashSet<ConsoleViewContentType>();
59
60   /**
61    * Main console usage scenario assumes the following:
62    * <pre>
63    * <ul>
64    *   <li>
65    *      console may be {@link ConsoleViewImpl#print(String, ConsoleViewContentType) provided} with the new text from any thread
66    *      (e.g. separate thread is charged for reading output of java application launched under IJ. That output is provided
67    *      to the console);
68    *   </li>
69    *   <li>current class flushes provided text to {@link Editor editor} used for representing it to end-user from EDT;</li>
70    *   <li>
71    *      dedicated buffer is kept to hold console text between the moment when it's provided to the current class
72    *      and flush to the editor;</li>
73    * </ul>
74    * </pre>
75    * <p/>
76    * It's also possible to configure console to use cyclic buffer in order to avoid unnecessary memory consumption.
77    * However, that implies possibility of the following situation - console user provides it with the great number
78    * of small chunks of text (that is the case for junit processing). It's inappropriate to use single {@link StringBuilder} as
79    * a buffer then because every time we see that cyclic buffer size is exceeded and we need to cut exceeding text from buffer
80    * start, trailing part is moved to the zero offset. That produces extensive CPU usage in case of great number of small messages
81    * where every such message exceeds cyclic buffer size.
82    * <p/>
83    * That is the reason why we use data structure similar to STL deque here - we hold number of string buffers of small size instead
84    * of the single big buffer. That means that every 'cut at the start' operation requires much less number of trailing symbols
85    * to be moved. Current constant defines default size of that small buffers.
86    */
87
88   private final int myCyclicBufferSize;
89   private final int myCyclicBufferUnitSize;
90   private final boolean myUseCyclicBuffer;
91
92   /**
93    * Holds information about number of symbols stored at {@link #myDeferredOutput} collection.
94    */
95   private int myDeferredOutputLength;
96
97   /**
98    * Buffer for deferred stdin output.
99    * <p/>
100    * Is assumed to store user input data until it's delivered to the target process. That activity is driven from outside this class.
101    */
102   private StringBuffer myDeferredUserInput = new StringBuffer();
103
104   /**
105    * Holds information about lexical division by offsets of the text that is not yet pushed to document.
106    * <p/>
107    * Target offsets are anchored to the {@link #myDeferredOutput deferred buffer}.
108    */
109   private final List<TokenInfo> myDeferredTokens = new ArrayList<TokenInfo>();
110   private final Set<ConsoleViewContentType> myDeferredTypes = new HashSet<ConsoleViewContentType>();
111
112   public ConsoleBuffer() {
113     this(useCycleBuffer(), getCycleBufferSize(), DEFAULT_CYCLIC_BUFFER_UNIT_SIZE);
114   }
115
116   public ConsoleBuffer(boolean useCyclicBuffer, int cyclicBufferSize, int cyclicBufferUnitSize) {
117     myUseCyclicBuffer = useCyclicBuffer;
118     myCyclicBufferSize = Math.max(cyclicBufferSize, 0);
119     myCyclicBufferUnitSize = cyclicBufferUnitSize;
120     myContentTypesToNotStripOnCycling.add(ConsoleViewContentType.USER_INPUT);
121   }
122
123   public static boolean useCycleBuffer() {
124     return !"disabled".equalsIgnoreCase(System.getProperty("idea.cycle.buffer.size"));
125   }
126
127   public static int getCycleBufferSize() {
128     if (UISettings.getInstance().OVERRIDE_CONSOLE_CYCLE_BUFFER_SIZE) {
129       return UISettings.getInstance().CONSOLE_CYCLE_BUFFER_SIZE_KB * 1024;
130     }
131     return getLegacyCycleBufferSize();
132   }
133
134   public static int getLegacyCycleBufferSize() {
135     String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
136     if (cycleBufferSizeProperty == null) return 1024 * 1024;
137     try {
138       return Integer.parseInt(cycleBufferSizeProperty) * 1024;
139     }
140     catch (NumberFormatException e) {
141       return 1024 * 1024;
142     }
143   }
144
145   public boolean isUseCyclicBuffer() {
146     return myUseCyclicBuffer;
147   }
148
149   public int getCyclicBufferSize() {
150     return myCyclicBufferSize;
151   }
152
153   public boolean isEmpty() {
154     return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0);
155   }
156
157   public int getLength() {
158     return myDeferredOutputLength;
159   }
160
161   public int getUserInputLength() {
162     return myDeferredUserInput.length();
163   }
164
165   public String getUserInput() {
166     return myDeferredUserInput.toString();
167   }
168
169   public List<TokenInfo> getDeferredTokens() {
170     return myDeferredTokens;
171   }
172
173   public Set<ConsoleViewContentType> getDeferredTokenTypes() {
174     return myDeferredTypes;
175   }
176
177   public Deque<StringBuilder> getDeferredOutput() {
178     return myDeferredOutput;
179   }
180
181   public String getText() {
182     if (myDeferredOutput.size() > 1) {
183       final StringBuilder buffer = new StringBuilder();
184       for (StringBuilder builder : myDeferredOutput) {
185         buffer.append(builder);
186       }
187       return buffer.toString();
188     }
189     else if (myDeferredOutput.size() == 1) {
190       return myDeferredOutput.getFirst().substring(0);
191     }
192     else {
193       return "";
194     }
195   }
196
197   /**
198    * This buffer automatically strips text that exceeds {@link #getCycleBufferSize() cyclic buffer size}. However, we may want
199    * to avoid 'significant text' stripping, i.e. don't strip the text of particular type.
200    * <p/>
201    * {@link ConsoleViewContentType#USER_INPUT} is considered to be such a type by default, however, it's possible to overwrite that
202    * via the current method.
203    *
204    * @param types content types that should not be stripped during the buffer's cycling
205    */
206   public void setContentTypesToNotStripOnCycling(@NotNull Collection<ConsoleViewContentType> types) {
207     myContentTypesToNotStripOnCycling.clear();
208     myContentTypesToNotStripOnCycling.addAll(types);
209   }
210
211   public void clear() {
212     clear(true);
213   }
214   
215   public void clear(boolean clearUserInputAsWell) {
216     if (myUseCyclicBuffer) {
217       myDeferredOutput.clear();
218       myDeferredOutput.add(new StringBuilder(myCyclicBufferUnitSize));
219     }
220     else {
221       for (StringBuilder builder : myDeferredOutput) {
222         builder.setLength(0);
223       }
224     }
225     myDeferredOutputLength = 0;
226     myDeferredTypes.clear();
227     myDeferredTokens.clear();
228     if (clearUserInputAsWell) {
229       myDeferredUserInput = new StringBuffer();
230     }
231   }
232
233   @Nullable
234   public String cutFirstUserInputLine() {
235     final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length());
236     final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r'));
237     if (index < 0) {
238       return null;
239     }
240     final String result = text.substring(0, index + 1);
241     myDeferredUserInput.setLength(0);
242     myDeferredUserInput.append(text.substring(index + 1));
243     return result;
244   }
245
246   public void addUserText(int offset, String text) {
247     myDeferredUserInput.insert(offset, text);
248   }
249
250   public void removeUserText(int startOffset, int endOffset) {
251     if (startOffset >= myDeferredUserInput.length()) {
252       return;
253     }
254     int startToUse = Math.max(0, startOffset);
255     int endToUse = Math.min(myDeferredUserInput.length(), endOffset);
256     myDeferredUserInput.delete(startToUse, endToUse);
257   }
258
259   public void replaceUserText(int startOffset, int endOffset, String text) {
260     myDeferredUserInput.replace(startOffset, endOffset, text);
261   }
262
263   /**
264    * Asks current buffer to store given text of the given type.
265    *
266    * @param s           text to store
267    * @param contentType type of the given text
268    * @param info        hyperlink info for the given text (if any)
269    * @return text that is actually stored (there is a possible case that the buffer is full and given text's type
270    *         is considered to have lower priority than the stored one, hence, it's better to drop given text completely
271    *         or partially) and number of existed symbols removed during storing the given data
272    */
273   @NotNull
274   public Pair<String, Integer> print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
275     int numberOfSymbolsToProceed = s.length();
276     int trimmedSymbolsNumber = myDeferredOutputLength;
277     if (contentType != ConsoleViewContentType.USER_INPUT) {
278       numberOfSymbolsToProceed = trimDeferredOutputIfNecessary(s.length());
279       trimmedSymbolsNumber -= myDeferredOutputLength;
280     }
281     else {
282       trimmedSymbolsNumber = 0;
283     }
284
285     if (numberOfSymbolsToProceed <= 0) {
286       return new Pair<String, Integer>("", 0);
287     }
288
289     if (numberOfSymbolsToProceed < s.length()) {
290       s = s.substring(s.length() - numberOfSymbolsToProceed);
291     }
292
293     myDeferredTypes.add(contentType);
294
295     s = StringUtil.convertLineSeparators(s, true);
296
297     myDeferredOutputLength += s.length();
298     StringBuilder bufferToUse;
299     if (myDeferredOutput.isEmpty()) {
300       myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize));
301     }
302     else {
303       bufferToUse = myDeferredOutput.getLast();
304     }
305     int offset = 0;
306     while (offset < s.length()) {
307       if (bufferToUse.length() >= myCyclicBufferUnitSize) {
308         myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize));
309       }
310
311       if (bufferToUse.length() < myCyclicBufferUnitSize) {
312         int numberOfSymbolsToAdd = Math.min(myCyclicBufferUnitSize - bufferToUse.length(), s.length() - offset);
313         bufferToUse.append(s.substring(offset, offset + numberOfSymbolsToAdd));
314         offset += numberOfSymbolsToAdd;
315       }
316     }
317
318     if (contentType == ConsoleViewContentType.USER_INPUT) {
319       myDeferredUserInput.append(s);
320     }
321
322     ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens);
323     return new Pair<String, Integer>(s, trimmedSymbolsNumber);
324   }
325
326   //private void checkState() {
327   //  int bufferOffset = 0;
328   //  Iterator<StringBuilder> iterator = myDeferredOutput.iterator();
329   //  StringBuilder currentBuffer = null;
330   //  int prevTokenEnd = 0;
331   //  for (TokenInfo token : myDeferredTokens) {
332   //    if (prevTokenEnd != token.startOffset) {
333   //      try {
334   //        System.out.println("Problem detected!");
335   //        System.in.read();
336   //      }
337   //      catch (IOException e) {
338   //        e.printStackTrace();
339   //      }
340   //    }
341   //    prevTokenEnd = token.endOffset;
342   //    char c = token.contentType == ConsoleViewContentType.ERROR_OUTPUT ? '2' : '1';
343   //    int length = token.getLength();
344   //    if (currentBuffer == null) {
345   //      currentBuffer = iterator.next();
346   //    }
347   //    
348   //    while (length > 0) {
349   //      if (bufferOffset == currentBuffer.length()) {
350   //        if (!iterator.hasNext()) {
351   //          try {
352   //            System.out.println("Problem detected!");
353   //            System.in.read();
354   //          }
355   //          catch (IOException e) {
356   //            e.printStackTrace();
357   //          }
358   //        }
359   //        currentBuffer = iterator.next();
360   //        bufferOffset = 0;
361   //      }
362   //      else {
363   //        int endOffset = Math.min(bufferOffset + length, currentBuffer.length());
364   //        if (token.contentType == ConsoleViewContentType.NORMAL_OUTPUT || token.contentType == ConsoleViewContentType.ERROR_OUTPUT) {
365   //          for (int i = bufferOffset; i < endOffset; i++) {
366   //            char c1 = currentBuffer.charAt(i);
367   //            if (c1 != c && c1 != '\n') {
368   //              try {
369   //                System.out.println("Problem detected!");
370   //                System.in.read();
371   //              }
372   //              catch (IOException e) {
373   //                e.printStackTrace();
374   //              }
375   //            }
376   //          }
377   //        }
378   //        length -= endOffset - bufferOffset;
379   //        bufferOffset = endOffset;
380   //      }
381   //    }
382   //  }
383   //}
384
385   /**
386    * IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and
387    * pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process
388    * output is received in a control flow of the thread over than EDT but push to the document is performed from EDT. Hence, we
389    * have a potential situation when particular process outputs a lot and EDT is busy or push to the document is performed slowly.
390    * <p/>
391    * We don't want to keep too many information from the underlying process then and want to trim text buffer that holds text
392    * to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of
393    * text is received from the underlying process and trims existing text buffer if necessary.
394    *
395    * @param numberOfNewSymbols number of symbols read from the managed process output
396    * @return number of newly read symbols that should be accepted
397    */
398   @SuppressWarnings({"ForLoopReplaceableByForEach"})
399   private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) {
400     if (!myUseCyclicBuffer || myDeferredOutputLength + numberOfNewSymbols <= myCyclicBufferSize) {
401       return numberOfNewSymbols;
402     }
403
404     final int numberOfSymbolsToRemove = Math.min(myDeferredOutputLength, myDeferredOutputLength + numberOfNewSymbols - myCyclicBufferSize);
405     myDeferredTypes.clear();
406
407     if (DEBUG_PROCESSING) {
408       log("Starting console trimming. Need to delete %d symbols (deferred output length: %d, number of new symbols: %d, "
409           + "cyclic buffer size: %d). Current state:",
410           numberOfSymbolsToRemove, myDeferredOutputLength, numberOfNewSymbols, myCyclicBufferSize
411       );
412       dumpDeferredOutput();
413     }
414
415     Context context = new Context(numberOfSymbolsToRemove);
416
417     TIntArrayList indicesOfTokensToRemove = new TIntArrayList();
418     for (int i = 0; i < myDeferredTokens.size(); i++) {
419       TokenInfo tokenInfo = myDeferredTokens.get(i);
420       tokenInfo.startOffset -= context.removedSymbolsNumber;
421       tokenInfo.endOffset -= context.removedSymbolsNumber;
422
423       if (!context.canContinueProcessing()) {
424         // Just update token offsets.
425         myDeferredTypes.add(tokenInfo.contentType);
426         if (context.removedSymbolsNumber == 0) {
427           break;
428         }
429         continue;
430       }
431
432       int tokenLength = tokenInfo.getLength();
433
434       // Don't remove input text.
435       if (myContentTypesToNotStripOnCycling.contains(tokenInfo.contentType)) {
436         skip(context, tokenLength);
437         myDeferredTypes.add(tokenInfo.contentType);
438         continue;
439       }
440
441       int removedTokenSymbolsNumber = remove(context, tokenLength);
442       if (removedTokenSymbolsNumber == tokenLength) {
443         indicesOfTokensToRemove.add(i);
444       }
445       else {
446         tokenInfo.endOffset -= removedTokenSymbolsNumber;
447         myDeferredTypes.add(tokenInfo.contentType);
448       }
449     }
450
451     for (int i = indicesOfTokensToRemove.size() - 1; i >= 0; i--) {
452       myDeferredTokens.remove(indicesOfTokensToRemove.get(i));
453     }
454
455     if (!myDeferredTokens.isEmpty()) {
456       TokenInfo tokenInfo = myDeferredTokens.get(0);
457       if (tokenInfo.startOffset > 0) {
458         final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo();
459         myDeferredTokens
460           .add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo)
461                                         : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset));
462         myDeferredTypes.add(ConsoleViewContentType.USER_INPUT);
463       }
464     }
465
466     if (numberOfNewSymbols + myDeferredOutputLength > myCyclicBufferSize) {
467       int result = myCyclicBufferSize - myDeferredOutputLength;
468       if (result < 0) {
469         return 0;
470       }
471       return result;
472     }
473     return numberOfNewSymbols;
474   }
475
476   private static void skip(@NotNull Context context, int symbolsToSkipNumber) {
477     int remainingNumberOfBufferSymbols = context.currentBuffer.length() - context.bufferOffset;
478     if (remainingNumberOfBufferSymbols < symbolsToSkipNumber) {
479       symbolsToSkipNumber -= remainingNumberOfBufferSymbols;
480       while (context.iterator.hasNext()) {
481         context.currentBuffer = context.iterator.next();
482         context.bufferOffset = 0;
483         if (DEBUG_PROCESSING) {
484           log("Switching to the next buffer. Number of token symbols to skip: %d", symbolsToSkipNumber);
485         }
486         if (symbolsToSkipNumber <= 0) {
487           break;
488         }
489         if (context.currentBuffer.length() > symbolsToSkipNumber) {
490           context.bufferOffset = symbolsToSkipNumber;
491           symbolsToSkipNumber = 0;
492           break;
493         }
494         else {
495           symbolsToSkipNumber -= context.currentBuffer.length();
496         }
497       }
498       assert symbolsToSkipNumber <= 0;
499     }
500     else {
501       context.bufferOffset += symbolsToSkipNumber;
502       if (DEBUG_PROCESSING) {
503         log("All symbols to skip are processed. Current buffer offset is %d, text: '%s'", context.bufferOffset, context.currentBuffer);
504       }
505     }
506   }
507
508   private int remove(@NotNull Context context, int tokenLength) {
509     int removedSymbolsNumber = 0;
510     int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber;
511     int numberOfTokenSymbolsToRemove = Math.min(remainingTotalNumberOfSymbolsToRemove, tokenLength);
512     while (numberOfTokenSymbolsToRemove > 0 && context.currentBuffer != null) {
513       int diff = numberOfTokenSymbolsToRemove - (context.currentBuffer.length() - context.bufferOffset);
514       int endDeleteBufferOffset = Math.min(context.bufferOffset + numberOfTokenSymbolsToRemove, context.currentBuffer.length());
515       int numberOfSymbolsRemovedFromCurrentBuffer = endDeleteBufferOffset - context.bufferOffset;
516       if (DEBUG_PROCESSING) {
517         log("About to delete %d symbols from the current buffer (offset is %d). Removed symbols number: %d. Current buffer: %d: '%s'",
518             numberOfSymbolsRemovedFromCurrentBuffer, context.bufferOffset, context.removedSymbolsNumber, context.currentBuffer.length(),
519             StringUtil.convertLineSeparators(context.currentBuffer.toString()));
520       }
521       numberOfTokenSymbolsToRemove -= numberOfSymbolsRemovedFromCurrentBuffer;
522       removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer;
523       context.removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer;
524       myDeferredOutputLength -= numberOfSymbolsRemovedFromCurrentBuffer;
525
526       if (context.bufferOffset == 0 && (diff >= 0 || endDeleteBufferOffset == context.currentBuffer.length())) {
527         context.iterator.remove();
528         context.nextBuffer();
529       }
530       else {
531         context.currentBuffer.delete(context.bufferOffset, endDeleteBufferOffset);
532         if (DEBUG_PROCESSING) {
533           log("Removed symbols at range [%d; %d). Buffer offset: %d, buffer length: %d, text: '%s'",
534               context.bufferOffset, endDeleteBufferOffset, context.bufferOffset, context.currentBuffer.length(), context.currentBuffer);
535         }
536         if (context.bufferOffset == context.currentBuffer.length()) {
537           context.nextBuffer();
538         }
539       }
540     }
541     return removedSymbolsNumber;
542   }
543
544   private final class Context {
545
546     public final int numberOfSymbolsToRemove;
547     public StringBuilder currentBuffer;
548     public Iterator<StringBuilder> iterator;
549     public int bufferOffset;
550     public int removedSymbolsNumber;
551
552     Context(int numberOfSymbolsToRemove) {
553       this.numberOfSymbolsToRemove = numberOfSymbolsToRemove;
554       iterator = myDeferredOutput.iterator();
555       if (iterator.hasNext()) {
556         currentBuffer = iterator.next();
557       }
558       else {
559         currentBuffer = null;
560       }
561     }
562
563     public boolean canContinueProcessing() {
564       return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null;
565     }
566
567     public boolean nextBuffer() {
568       if (iterator.hasNext()) {
569         currentBuffer = iterator.next();
570         bufferOffset = 0;
571         return true;
572       }
573       return false;
574     }
575   }
576
577   @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
578   private void dumpDeferredOutput() {
579     if (!DEBUG_PROCESSING) {
580       return;
581     }
582     log("Tokens:");
583     for (TokenInfo token : myDeferredTokens) {
584       log("\t" + token);
585     }
586     log("Data:");
587     for (StringBuilder buffer : myDeferredOutput) {
588       log("\t%d: '%s'", buffer.length(), StringUtil.convertLineSeparators(buffer.toString()));
589     }
590     log("-----------------------------------------------------------------------------------------------------");
591   }
592
593   @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"})
594   private static void log(Object o) {
595     //try {
596     //  doLog(o);
597     //}
598     //catch (Exception e) {
599     //  e.printStackTrace();
600     //}
601   }
602
603   @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"})
604   private static void log(String message, Object... formatData) {
605     //try {
606     //  doLog(String.format(message, formatData));
607     //}
608     //catch (Exception e) {
609     //  e.printStackTrace();
610     //}
611   }
612
613   //private static BufferedWriter myWriter;
614   //private static void doLog(Object o) throws Exception {
615   //  if (!DEBUG_PROCESSING) {
616   //    return;
617   //  }
618   //  File file = new File("/home/denis/log/console.log");
619   //  if (myWriter == null || !file.exists()) {
620   //    myWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
621   //  }
622   //  myWriter.write(o.toString());
623   //  myWriter.newLine();
624   //  myWriter.flush();
625   //}
626 }