| /* |
| * Copyright 2000-2011 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.execution.impl; |
| |
| import com.intellij.execution.filters.HyperlinkInfo; |
| import com.intellij.execution.ui.ConsoleViewContentType; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import gnu.trove.TIntArrayList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| import static com.intellij.execution.impl.ConsoleViewImpl.HyperlinkTokenInfo; |
| import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; |
| |
| /** |
| * IJ user may want the console to use cyclic buffer, i.e. don't keep more than particular amount of symbols. So, we need |
| * to have a data structure that allow to achieve that. This class serves for that purpose. |
| * <p/> |
| * Not thread-safe. |
| * <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 |
| */ |
| public class ConsoleBuffer { |
| |
| private static final int DEFAULT_CYCLIC_BUFFER_UNIT_SIZE = 256; |
| |
| private static final boolean DEBUG_PROCESSING = false; |
| |
| /** |
| * Buffer for deferred stdout, stderr and stdin output. |
| * <p/> |
| * Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract. |
| */ |
| private final Deque<StringBuilder> myDeferredOutput = new ArrayDeque<StringBuilder>(); |
| private final Set<ConsoleViewContentType> myContentTypesToNotStripOnCycling = new HashSet<ConsoleViewContentType>(); |
| |
| /** |
| * Main console usage scenario assumes the following: |
| * <pre> |
| * <ul> |
| * <li> |
| * console may be {@link ConsoleViewImpl#print(String, ConsoleViewContentType) provided} with the new text from any thread |
| * (e.g. separate thread is charged for reading output of java application launched under IJ. That output is provided |
| * to the console); |
| * </li> |
| * <li>current class flushes provided text to {@link Editor editor} used for representing it to end-user from EDT;</li> |
| * <li> |
| * dedicated buffer is kept to hold console text between the moment when it's provided to the current class |
| * and flush to the editor;</li> |
| * </ul> |
| * </pre> |
| * <p/> |
| * It's also possible to configure console to use cyclic buffer in order to avoid unnecessary memory consumption. |
| * However, that implies possibility of the following situation - console user provides it with the great number |
| * of small chunks of text (that is the case for junit processing). It's inappropriate to use single {@link StringBuilder} as |
| * a buffer then because every time we see that cyclic buffer size is exceeded and we need to cut exceeding text from buffer |
| * start, trailing part is moved to the zero offset. That produces extensive CPU usage in case of great number of small messages |
| * where every such message exceeds cyclic buffer size. |
| * <p/> |
| * That is the reason why we use data structure similar to STL deque here - we hold number of string buffers of small size instead |
| * of the single big buffer. That means that every 'cut at the start' operation requires much less number of trailing symbols |
| * to be moved. Current constant defines default size of that small buffers. |
| */ |
| |
| private final int myCyclicBufferSize; |
| private final int myCyclicBufferUnitSize; |
| private final boolean myUseCyclicBuffer; |
| |
| /** |
| * Holds information about number of symbols stored at {@link #myDeferredOutput} collection. |
| */ |
| private int myDeferredOutputLength; |
| |
| /** |
| * Buffer for deferred stdin output. |
| * <p/> |
| * Is assumed to store user input data until it's delivered to the target process. That activity is driven from outside this class. |
| */ |
| private StringBuffer myDeferredUserInput = new StringBuffer(); |
| |
| /** |
| * Holds information about lexical division by offsets of the text that is not yet pushed to document. |
| * <p/> |
| * Target offsets are anchored to the {@link #myDeferredOutput deferred buffer}. |
| */ |
| private final List<TokenInfo> myDeferredTokens = new ArrayList<TokenInfo>(); |
| private final Set<ConsoleViewContentType> myDeferredTypes = new HashSet<ConsoleViewContentType>(); |
| |
| public ConsoleBuffer() { |
| this(useCycleBuffer(), getCycleBufferSize(), DEFAULT_CYCLIC_BUFFER_UNIT_SIZE); |
| } |
| |
| public ConsoleBuffer(boolean useCyclicBuffer, int cyclicBufferSize, int cyclicBufferUnitSize) { |
| myUseCyclicBuffer = useCyclicBuffer; |
| myCyclicBufferSize = Math.max(cyclicBufferSize, 0); |
| myCyclicBufferUnitSize = cyclicBufferUnitSize; |
| myContentTypesToNotStripOnCycling.add(ConsoleViewContentType.USER_INPUT); |
| } |
| |
| public static boolean useCycleBuffer() { |
| final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size"); |
| return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty); |
| } |
| |
| public static int getCycleBufferSize() { |
| final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size"); |
| if (cycleBufferSizeProperty == null) return 1024 * 1024; |
| try { |
| return Integer.parseInt(cycleBufferSizeProperty) * 1024; |
| } |
| catch (NumberFormatException e) { |
| return 1024 * 1024; |
| } |
| } |
| |
| public boolean isUseCyclicBuffer() { |
| return myUseCyclicBuffer; |
| } |
| |
| public int getCyclicBufferSize() { |
| return myCyclicBufferSize; |
| } |
| |
| public boolean isEmpty() { |
| return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0); |
| } |
| |
| public int getLength() { |
| return myDeferredOutputLength; |
| } |
| |
| public int getUserInputLength() { |
| return myDeferredUserInput.length(); |
| } |
| |
| public String getUserInput() { |
| return myDeferredUserInput.toString(); |
| } |
| |
| public List<TokenInfo> getDeferredTokens() { |
| return myDeferredTokens; |
| } |
| |
| public Set<ConsoleViewContentType> getDeferredTokenTypes() { |
| return myDeferredTypes; |
| } |
| |
| public Deque<StringBuilder> getDeferredOutput() { |
| return myDeferredOutput; |
| } |
| |
| public String getText() { |
| if (myDeferredOutput.size() > 1) { |
| 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); |
| } |
| else { |
| return ""; |
| } |
| } |
| |
| /** |
| * This buffer automatically strips text that exceeds {@link #getCycleBufferSize() cyclic buffer size}. However, we may want |
| * to avoid 'significant text' stripping, i.e. don't strip the text of particular type. |
| * <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 |
| */ |
| public void setContentTypesToNotStripOnCycling(@NotNull Collection<ConsoleViewContentType> types) { |
| myContentTypesToNotStripOnCycling.clear(); |
| myContentTypesToNotStripOnCycling.addAll(types); |
| } |
| |
| public void clear() { |
| clear(true); |
| } |
| |
| public void clear(boolean clearUserInputAsWell) { |
| if (myUseCyclicBuffer) { |
| myDeferredOutput.clear(); |
| myDeferredOutput.add(new StringBuilder(myCyclicBufferUnitSize)); |
| } |
| else { |
| for (StringBuilder builder : myDeferredOutput) { |
| builder.setLength(0); |
| } |
| } |
| myDeferredOutputLength = 0; |
| myDeferredTypes.clear(); |
| myDeferredTokens.clear(); |
| if (clearUserInputAsWell) { |
| myDeferredUserInput = new StringBuffer(); |
| } |
| } |
| |
| @Nullable |
| public String cutFirstUserInputLine() { |
| final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length()); |
| final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r')); |
| if (index < 0) { |
| return null; |
| } |
| final String result = text.substring(0, index + 1); |
| myDeferredUserInput.setLength(0); |
| myDeferredUserInput.append(text.substring(index + 1)); |
| return result; |
| } |
| |
| public void addUserText(int offset, String text) { |
| myDeferredUserInput.insert(offset, text); |
| } |
| |
| public void removeUserText(int startOffset, int endOffset) { |
| if (startOffset >= myDeferredUserInput.length()) { |
| return; |
| } |
| int startToUse = Math.max(0, startOffset); |
| int endToUse = Math.min(myDeferredUserInput.length(), endOffset); |
| myDeferredUserInput.delete(startToUse, endToUse); |
| } |
| |
| 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 |
| */ |
| @NotNull |
| public Pair<String, Integer> print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) { |
| int numberOfSymbolsToProceed = s.length(); |
| int trimmedSymbolsNumber = myDeferredOutputLength; |
| if (contentType != ConsoleViewContentType.USER_INPUT) { |
| numberOfSymbolsToProceed = trimDeferredOutputIfNecessary(s.length()); |
| trimmedSymbolsNumber -= myDeferredOutputLength; |
| } |
| else { |
| trimmedSymbolsNumber = 0; |
| } |
| |
| if (numberOfSymbolsToProceed <= 0) { |
| return new Pair<String, Integer>("", 0); |
| } |
| |
| if (numberOfSymbolsToProceed < s.length()) { |
| s = s.substring(s.length() - numberOfSymbolsToProceed); |
| } |
| |
| myDeferredTypes.add(contentType); |
| |
| s = StringUtil.convertLineSeparators(s, true); |
| |
| myDeferredOutputLength += s.length(); |
| StringBuilder bufferToUse; |
| if (myDeferredOutput.isEmpty()) { |
| myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize)); |
| } |
| else { |
| bufferToUse = myDeferredOutput.getLast(); |
| } |
| int offset = 0; |
| while (offset < s.length()) { |
| 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)); |
| offset += numberOfSymbolsToAdd; |
| } |
| } |
| |
| if (contentType == ConsoleViewContentType.USER_INPUT) { |
| myDeferredUserInput.append(s); |
| } |
| |
| ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens); |
| return new Pair<String, Integer>(s, trimmedSymbolsNumber); |
| } |
| |
| //private void checkState() { |
| // int bufferOffset = 0; |
| // Iterator<StringBuilder> iterator = myDeferredOutput.iterator(); |
| // StringBuilder currentBuffer = null; |
| // int prevTokenEnd = 0; |
| // for (TokenInfo token : myDeferredTokens) { |
| // if (prevTokenEnd != token.startOffset) { |
| // try { |
| // System.out.println("Problem detected!"); |
| // System.in.read(); |
| // } |
| // catch (IOException e) { |
| // e.printStackTrace(); |
| // } |
| // } |
| // prevTokenEnd = token.endOffset; |
| // char c = token.contentType == ConsoleViewContentType.ERROR_OUTPUT ? '2' : '1'; |
| // int length = token.getLength(); |
| // if (currentBuffer == null) { |
| // currentBuffer = iterator.next(); |
| // } |
| // |
| // while (length > 0) { |
| // if (bufferOffset == currentBuffer.length()) { |
| // if (!iterator.hasNext()) { |
| // try { |
| // System.out.println("Problem detected!"); |
| // System.in.read(); |
| // } |
| // catch (IOException e) { |
| // e.printStackTrace(); |
| // } |
| // } |
| // currentBuffer = iterator.next(); |
| // bufferOffset = 0; |
| // } |
| // else { |
| // int endOffset = Math.min(bufferOffset + length, currentBuffer.length()); |
| // if (token.contentType == ConsoleViewContentType.NORMAL_OUTPUT || token.contentType == ConsoleViewContentType.ERROR_OUTPUT) { |
| // for (int i = bufferOffset; i < endOffset; i++) { |
| // char c1 = currentBuffer.charAt(i); |
| // if (c1 != c && c1 != '\n') { |
| // try { |
| // System.out.println("Problem detected!"); |
| // System.in.read(); |
| // } |
| // catch (IOException e) { |
| // e.printStackTrace(); |
| // } |
| // } |
| // } |
| // } |
| // length -= endOffset - bufferOffset; |
| // bufferOffset = endOffset; |
| // } |
| // } |
| // } |
| //} |
| |
| /** |
| * 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 |
| * output is received in a control flow of the thread over than EDT but push to the document is performed from EDT. Hence, we |
| * have a potential situation when particular process outputs a lot and EDT is busy or push to the document is performed slowly. |
| * <p/> |
| * We don't want to keep too many information from the underlying process then and want to trim text buffer that holds text |
| * 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 |
| */ |
| @SuppressWarnings({"ForLoopReplaceableByForEach"}) |
| private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) { |
| if (!myUseCyclicBuffer || myDeferredOutputLength + numberOfNewSymbols <= myCyclicBufferSize) { |
| return numberOfNewSymbols; |
| } |
| |
| final int numberOfSymbolsToRemove = Math.min(myDeferredOutputLength, myDeferredOutputLength + numberOfNewSymbols - myCyclicBufferSize); |
| myDeferredTypes.clear(); |
| |
| if (DEBUG_PROCESSING) { |
| log("Starting console trimming. Need to delete %d symbols (deferred output length: %d, number of new symbols: %d, " |
| + "cyclic buffer size: %d). Current state:", |
| numberOfSymbolsToRemove, myDeferredOutputLength, numberOfNewSymbols, myCyclicBufferSize |
| ); |
| dumpDeferredOutput(); |
| } |
| |
| 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); |
| if (context.removedSymbolsNumber == 0) { |
| break; |
| } |
| continue; |
| } |
| |
| int tokenLength = tokenInfo.getLength(); |
| |
| // Don't remove input text. |
| if (myContentTypesToNotStripOnCycling.contains(tokenInfo.contentType)) { |
| skip(context, tokenLength); |
| myDeferredTypes.add(tokenInfo.contentType); |
| continue; |
| } |
| |
| int removedTokenSymbolsNumber = remove(context, tokenLength); |
| if (removedTokenSymbolsNumber == tokenLength) { |
| indicesOfTokensToRemove.add(i); |
| } |
| else { |
| tokenInfo.endOffset -= removedTokenSymbolsNumber; |
| myDeferredTypes.add(tokenInfo.contentType); |
| } |
| } |
| |
| for (int i = indicesOfTokensToRemove.size() - 1; i >= 0; i--) { |
| myDeferredTokens.remove(indicesOfTokensToRemove.get(i)); |
| } |
| |
| if (!myDeferredTokens.isEmpty()) { |
| 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)); |
| myDeferredTypes.add(ConsoleViewContentType.USER_INPUT); |
| } |
| } |
| |
| if (numberOfNewSymbols + myDeferredOutputLength > myCyclicBufferSize) { |
| int result = myCyclicBufferSize - myDeferredOutputLength; |
| if (result < 0) { |
| return 0; |
| } |
| return result; |
| } |
| return numberOfNewSymbols; |
| } |
| |
| private static void skip(@NotNull Context context, int symbolsToSkipNumber) { |
| int remainingNumberOfBufferSymbols = context.currentBuffer.length() - context.bufferOffset; |
| if (remainingNumberOfBufferSymbols < symbolsToSkipNumber) { |
| symbolsToSkipNumber -= remainingNumberOfBufferSymbols; |
| while (context.iterator.hasNext()) { |
| context.currentBuffer = context.iterator.next(); |
| context.bufferOffset = 0; |
| if (DEBUG_PROCESSING) { |
| log("Switching to the next buffer. Number of token symbols to skip: %d", symbolsToSkipNumber); |
| } |
| if (symbolsToSkipNumber <= 0) { |
| break; |
| } |
| if (context.currentBuffer.length() > symbolsToSkipNumber) { |
| context.bufferOffset = symbolsToSkipNumber; |
| symbolsToSkipNumber = 0; |
| break; |
| } |
| else { |
| symbolsToSkipNumber -= context.currentBuffer.length(); |
| } |
| } |
| assert symbolsToSkipNumber <= 0; |
| } |
| else { |
| context.bufferOffset += symbolsToSkipNumber; |
| if (DEBUG_PROCESSING) { |
| log("All symbols to skip are processed. Current buffer offset is %d, text: '%s'", context.bufferOffset, context.currentBuffer); |
| } |
| } |
| } |
| |
| private int remove(@NotNull Context context, int tokenLength) { |
| int removedSymbolsNumber = 0; |
| int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber; |
| int numberOfTokenSymbolsToRemove = Math.min(remainingTotalNumberOfSymbolsToRemove, tokenLength); |
| while (numberOfTokenSymbolsToRemove > 0 && context.currentBuffer != null) { |
| int diff = numberOfTokenSymbolsToRemove - (context.currentBuffer.length() - context.bufferOffset); |
| int endDeleteBufferOffset = Math.min(context.bufferOffset + numberOfTokenSymbolsToRemove, context.currentBuffer.length()); |
| int numberOfSymbolsRemovedFromCurrentBuffer = endDeleteBufferOffset - context.bufferOffset; |
| if (DEBUG_PROCESSING) { |
| log("About to delete %d symbols from the current buffer (offset is %d). Removed symbols number: %d. Current buffer: %d: '%s'", |
| numberOfSymbolsRemovedFromCurrentBuffer, context.bufferOffset, context.removedSymbolsNumber, context.currentBuffer.length(), |
| StringUtil.convertLineSeparators(context.currentBuffer.toString())); |
| } |
| numberOfTokenSymbolsToRemove -= numberOfSymbolsRemovedFromCurrentBuffer; |
| removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer; |
| context.removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer; |
| myDeferredOutputLength -= numberOfSymbolsRemovedFromCurrentBuffer; |
| |
| if (context.bufferOffset == 0 && (diff >= 0 || endDeleteBufferOffset == context.currentBuffer.length())) { |
| context.iterator.remove(); |
| context.nextBuffer(); |
| } |
| else { |
| context.currentBuffer.delete(context.bufferOffset, endDeleteBufferOffset); |
| if (DEBUG_PROCESSING) { |
| log("Removed symbols at range [%d; %d). Buffer offset: %d, buffer length: %d, text: '%s'", |
| context.bufferOffset, endDeleteBufferOffset, context.bufferOffset, context.currentBuffer.length(), context.currentBuffer); |
| } |
| if (context.bufferOffset == context.currentBuffer.length()) { |
| context.nextBuffer(); |
| } |
| } |
| } |
| return removedSymbolsNumber; |
| } |
| |
| private final class Context { |
| |
| public final int numberOfSymbolsToRemove; |
| public StringBuilder currentBuffer; |
| public Iterator<StringBuilder> iterator; |
| public int bufferOffset; |
| public int removedSymbolsNumber; |
| |
| Context(int numberOfSymbolsToRemove) { |
| this.numberOfSymbolsToRemove = numberOfSymbolsToRemove; |
| iterator = myDeferredOutput.iterator(); |
| if (iterator.hasNext()) { |
| currentBuffer = iterator.next(); |
| } |
| else { |
| currentBuffer = null; |
| } |
| } |
| |
| public boolean canContinueProcessing() { |
| return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null; |
| } |
| |
| public boolean nextBuffer() { |
| if (iterator.hasNext()) { |
| currentBuffer = iterator.next(); |
| bufferOffset = 0; |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) |
| private void dumpDeferredOutput() { |
| if (!DEBUG_PROCESSING) { |
| return; |
| } |
| log("Tokens:"); |
| for (TokenInfo token : myDeferredTokens) { |
| log("\t" + token); |
| } |
| log("Data:"); |
| for (StringBuilder buffer : myDeferredOutput) { |
| log("\t%d: '%s'", buffer.length(), StringUtil.convertLineSeparators(buffer.toString())); |
| } |
| log("-----------------------------------------------------------------------------------------------------"); |
| } |
| |
| @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"}) |
| private static void log(Object o) { |
| //try { |
| // doLog(o); |
| //} |
| //catch (Exception e) { |
| // e.printStackTrace(); |
| //} |
| } |
| |
| @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"}) |
| private static void log(String message, Object... formatData) { |
| //try { |
| // doLog(String.format(message, formatData)); |
| //} |
| //catch (Exception e) { |
| // e.printStackTrace(); |
| //} |
| } |
| |
| //private static BufferedWriter myWriter; |
| //private static void doLog(Object o) throws Exception { |
| // if (!DEBUG_PROCESSING) { |
| // return; |
| // } |
| // File file = new File("/home/denis/log/console.log"); |
| // if (myWriter == null || !file.exists()) { |
| // myWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); |
| // } |
| // myWriter.write(o.toString()); |
| // myWriter.newLine(); |
| // myWriter.flush(); |
| //} |
| } |