(str);
+ }
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ String expected = myDebugArray.toString();
+ checkStrings("toString()", expected, str);
+ }
+ return str;
+ }
+
+ @Override
+ public final int length() {
+ final int result = myCount + myDeferredShift;
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ int expected = myDebugArray.length();
+ if (expected != result) {
+ dumpDebugInfo(String.format("Incorrect length() processing. Expected: '%s', actual: '%s'", expected, result));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public final char charAt(int i) {
+ if (i < 0 || i >= length()) {
+ throw new IndexOutOfBoundsException("Wrong offset: " + i + "; count:" + length());
+ }
+ i += myStart;
+ if (myOriginalSequence != null) return myOriginalSequence.charAt(i);
+ final char result;
+ if (hasDeferredChanges()) {
+ TextChangesStorage storage = myDeferredChangesStorage.get();
+ storage.getLock().lock();
+ try {
+ result = storage.charAt(myArray, i);
+ } finally {
+ storage.getLock().unlock();
+ }
+ } else {
+ result = myArray[i];
+ }
+
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ char expected = myDebugArray.charAt(i);
+ if (expected != result) {
+ dumpDebugInfo(
+ String.format("Incorrect charAt() processing for index %d. Expected: '%c', actual: '%c'", i, expected, result)
+ );
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public CharSequence subSequence(final int start, final int end) {
+ if (start == 0 && end == length()) return this;
+ if (myOriginalSequence != null) {
+ return myOriginalSequence.subSequence(start, end);
+ }
+ if (hasDeferredChanges()) {
+ return new CharArray(myBufferSize, myDeferredChangesStorage.get(), myArray, myStart + start, myStart + end) {
+ @NotNull
+ @Override
+ protected DocumentEvent beforeChangedUpdate(DocumentImpl subj,
+ int offset,
+ CharSequence oldString,
+ CharSequence newString,
+ boolean wholeTextReplaced) {
+ return new DocumentEventImpl(subj, offset, oldString, newString, LocalTimeCounter.currentTime(), wholeTextReplaced);
+ }
+
+ @Override
+ protected void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp) {
+ }
+
+ @Override
+ public char[] getChars() {
+ char[] chars = CharArray.this.getChars();
+ char[] result = new char[end - start];
+ System.arraycopy(chars, start, result, 0, result.length);
+ return result;
+ }
+ };
+ } else {
+ // We don't use the same approach as with 'defer changes' mode because the former is the new experimental one and this one
+ // is rather mature, hence, we just minimizes the risks that something is wrong within the new approach.
+ return new CharArrayCharSequence(myArray, start, end);
+ }
+ }
+
+ private boolean isSubSequence() {
+ return myEnd >= 0;
+ }
+
+ @Override
+ public char[] getChars() {
+ if (myOriginalSequence != null) {
+ if (myArray == null) {
+ myArray = CharArrayUtil.fromSequence(myOriginalSequence);
+ }
+ }
+ flushDeferredChanged(myDeferredChangesStorage.get());
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ char[] expected = myDebugArray.getChars();
+ for (int i = 0, max = length(); i < max; i++) {
+ if (myArray[i] != expected[i]) {
+ dumpDebugInfo(String.format("getChars(). Index: %d, expected: %c, actual: %c", i, expected[i], myArray[i]));
+ break;
+ }
+ }
+ }
+ return myArray;
+ }
+
+ @Override
+ public void getChars(final char[] dst, final int dstOffset) {
+ flushDeferredChanged(myDeferredChangesStorage.get());
+ if (myOriginalSequence != null) {
+ CharArrayUtil.getChars(myOriginalSequence, dst, dstOffset);
+ } else {
+ System.arraycopy(myArray, myStart, dst, dstOffset, length());
+ }
+
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ char[] expected = new char[dst.length];
+ myDebugArray.getChars(expected, dstOffset);
+ for (int i = dstOffset, j = myStart; i < dst.length && j < myArray.length; i++, j++) {
+ if (expected[i] != myArray[j]) {
+ dumpDebugInfo(String.format("getChars(char[], int). Given array of length %d, offset %d. Found char '%c' at index %d, " +
+ "expected to find '%c'", dst.length, dstOffset, myArray[j], i, expected[i]));
+ break;
+ }
+ }
+ }
+ }
+
+ public CharSequence substring(final int start, final int end) {
+ if (start == end) return "";
+ final CharSequence result;
+ if (myOriginalSequence == null) {
+ TextChangesStorage storage = myDeferredChangesStorage.get();
+ storage.getLock().lock();
+ try {
+ result = storage.substring(myArray, start + myStart, end + myStart);
+ } finally {
+ storage.getLock().unlock();
+ }
+ } else {
+ result = myOriginalSequence.subSequence(start, end);
+ }
+
+ if (myDebugDeferredProcessing && isDeferredChangeMode()) {
+ String expected = myDebugArray.substring(start, end).toString();
+ checkStrings(String.format("substring(%d, %d)", start, end), expected, result.toString());
+ }
+ return result;
+ }
+
+ private static char[] relocateArray(char[] array, int index) {
+ if (index < array.length) {
+ return array;
+ }
+
+ int newArraySize = array.length;
+ if (newArraySize == 0) {
+ newArraySize = 16;
+ }
+ while (newArraySize <= index) {
+ newArraySize = newArraySize * 12 / 10 + 1;
+ }
+ char[] newArray = new char[newArraySize];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ return newArray;
+ }
+
+ private void trimToSize(DocumentImpl subj) {
+ if (myBufferSize != 0 && length() > myBufferSize) {
+ flushDeferredChanged(myDeferredChangesStorage.get());
+ // make a copy
+ remove(subj, 0, myCount - myBufferSize, getCharArray().subSequence(0, myCount - myBufferSize).toString());
+ }
+ }
+
+ /**
+ * @return true if this object is at {@link #setDeferredChangeMode(boolean) defer changes} mode;
+ * false otherwise
+ */
+ public boolean isDeferredChangeMode() {
+ return !DISABLE_DEFERRED_PROCESSING && myDeferredChangeMode;
+ }
+
+ public boolean hasDeferredChanges() {
+ return !myDeferredChangesStorage.get().isEmpty();
+ }
+
+ /**
+ * There is a possible case that client of this class wants to perform great number of modifications in a short amount of time
+ * (e.g. end-user performs formatting of the document backed by the object of the current class). It may result in significant
+ * performance degradation is the changes are performed one by one (every time the change is applied tail content is shifted to
+ * the left or right). So, we may want to optimize that by avoiding actual array modification until information about
+ * all target changes is provided and perform array data moves only after that.
+ *
+ * This method allows to define that 'defer changes' mode usages, i.e. expected usage pattern is as follows:
+ *
+ *
+ * -
+ * Client of this class enters
'defer changes' mode (calls this method with 'true' argument).
+ * That means that all subsequent changes will not actually modify backed array data and will be stored separately;
+ *
+ * -
+ * Number of target changes are applied to the current object via standard API
+ * ({@link #insert(com.intellij.openapi.editor.impl.DocumentImpl, CharSequence, int) insert},
+ * {@link #remove(com.intellij.openapi.editor.impl.DocumentImpl, int, int, CharSequence) remove} and
+ * {@link #replace(com.intellij.openapi.editor.impl.DocumentImpl, int, int, CharSequence, CharSequence, long, boolean) replace});
+ *
+ * -
+ * Client of this class indicates that
'massive change time' is over by calling this method with 'false'
+ * argument. That flushes all deferred changes (if any) to the backed data array and makes every subsequent change to
+ * be immediate flushed to the backed array;
+ *
+ *
+ *
+ *
+ * Note: we can't exclude possibility that 'defer changes' mode is started but inadvertently not ended
+ * (due to programming error, unexpected exception etc). Hence, this class is free to automatically end
+ * 'defer changes' mode when necessary in order to avoid memory leak with infinite deferred changes storing.
+ *
+ * @param deferredChangeMode flag that defines if 'defer changes' mode should be used by the current object
+ */
+ public void setDeferredChangeMode(boolean deferredChangeMode) {
+ if (deferredChangeMode && myDebugDeferredProcessing) {
+ myDebugArray.setText(null, myDebugTextOnBatchUpdateStart = toString());
+ myDebugDeferredChanges.clear();
+ }
+ myDeferredChangeMode = deferredChangeMode;
+ if (!deferredChangeMode) {
+ flushDeferredChanged(myDeferredChangesStorage.get());
+ }
+ }
+
+ private void flushDeferredChanged(@NotNull TextChangesStorage storage) {
+ storage.getLock().lock();
+ try {
+ doFlushDeferredChanged();
+ } finally {
+ storage.getLock().unlock();
+ }
+ }
+
+ private void doFlushDeferredChanged() {
+ TextChangesStorage storage = myDeferredChangesStorage.get();
+ List changes = storage.getChanges();
+ if (changes.isEmpty()) {
+ return;
+ }
+
+ char[] beforeMerge = null;
+ final boolean inPlace;
+ if (myDebugDeferredProcessing) {
+ beforeMerge = new char[myArray.length];
+ System.arraycopy(myArray, 0, beforeMerge, 0, myArray.length);
+ }
+
+ BulkChangesMerger changesMerger = BulkChangesMerger.INSTANCE;
+ if (myArray.length < length()) {
+ myArray = changesMerger.mergeToCharArray(myArray, myCount, changes);
+ inPlace = false;
+ } else {
+ changesMerger.mergeInPlace(myArray, myCount, changes);
+ inPlace = true;
+ }
+
+ if (myDebugDeferredProcessing) {
+ for (int i = 0, max = length(); i < max; i++) {
+ if (myArray[i] != myDebugArray.myArray[i]) {
+ dumpDebugInfo(String.format(
+ "flushDeferredChanged(). Index %d, expected: '%c', actual '%c'. Text before merge: '%s', merge inplace: %b",
+ i, myDebugArray.myArray[i], myArray[i], Arrays.toString(beforeMerge), inPlace));
+ break;
+ }
+ }
+ }
+
+ myCount += myDeferredShift;
+ myDeferredShift = 0;
+ storage.clear();
+ myDeferredChangeMode = false;
+ }
+
+ @NotNull
+ public String dumpState() {
+ return String.format(
+ "deferred changes mode: %b, length: %d (data array length: %d, deferred shift: %d); view offsets: [%d; %d]; deferred changes: %s",
+ isDeferredChangeMode(), length(), myCount, myDeferredShift, myStart, myEnd, myDeferredChangesStorage
+ );
+ }
+
+ private void checkStrings(@NotNull String operation, @NotNull String expected, @NotNull String actual) {
+ if (expected.equals(actual)) {
+ return;
+ }
+ for (int i = 0, max = Math.min(expected.length(), actual.length()); i < max; i++) {
+ if (actual.charAt(i) != expected.charAt(i)) {
+ dumpDebugInfo(String.format(
+ "Incorrect %s processing. Expected length: %d, actual length: %d. Unmatched symbol at %d - expected: '%c', " +
+ "actual: '%c', expected document: '%s', actual document: '%s'",
+ operation, expected.length(), actual.length(), i, expected.charAt(i), actual.charAt(i), expected, actual
+ ));
+ return;
+ }
+ }
+ dumpDebugInfo(String.format(
+ "Incorrect %s processing. Expected length: %d, actual length: %d, expected: '%s', actual: '%s'",
+ operation, expected.length(), actual.length(), expected, actual
+ ));
+ }
+
+ private void dumpDebugInfo(@NotNull String problem) {
+ //LOG.error(String.format(
+ // "/***********************************************************\n" +
+ // " * Please email idea.log to Denis.Zhdanov@jetbrains.com\n" +
+ // " ***********************************************************/\n" +
+ // "Incorrect CharArray processing detected: '%s'. Start: %d, end: %d, text on batch update start: '%s', deferred changes history: %s, "
+ // + "current deferred changes: %s",
+ // problem, myStart, myEnd, myDebugTextOnBatchUpdateStart, myDebugDeferredChanges, myDeferredChangesStorage
+ //));
+ LOG.error(String.format(
+ "Incorrect CharArray processing detected: '%s'. Start: %d, end: %d, text on batch update start: '%s', deferred changes history: %s, "
+ + "current deferred changes: %s",
+ problem, myStart, myEnd, myDebugTextOnBatchUpdateStart, myDebugDeferredChanges, myDeferredChangesStorage
+ ));
+ }
+}
diff --git a/idea_fix/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java b/idea_fix/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java
new file mode 100644
index 00000000000..35114b8b85e
--- /dev/null
+++ b/idea_fix/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2000-2009 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.openapi.fileEditor.impl;
+
+import com.intellij.lang.properties.charset.Native2AsciiCharset;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.BinaryFileDecompiler;
+import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Trinity;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.CharsetToolkit;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.encoding.EncodingRegistry;
+import com.intellij.testFramework.LightVirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.text.CharArrayUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+
+public final class LoadTextUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.LoadTextUtil");
+ private static final Key DETECTED_LINE_SEPARATOR_KEY = Key.create("DETECTED_LINE_SEPARATOR_KEY");
+
+ private LoadTextUtil() {
+ }
+
+ @NotNull
+ private static Pair convertLineSeparators(@NotNull CharBuffer buffer) {
+ int dst = 0;
+ char prev = ' ';
+ int crCount = 0;
+ int lfCount = 0;
+ int crlfCount = 0;
+
+ final int length = buffer.length();
+ final char[] bufferArray = CharArrayUtil.fromSequenceWithoutCopying(buffer);
+
+ for (int src = 0; src < length; src++) {
+ char c = bufferArray != null ? bufferArray[src] : buffer.charAt(src);
+ switch (c) {
+ case '\r':
+ buffer.put(dst++, '\n');
+ crCount++;
+ break;
+ case '\n':
+ if (prev == '\r') {
+ crCount--;
+ crlfCount++;
+ } else {
+ buffer.put(dst++, '\n');
+ lfCount++;
+ }
+ break;
+ default:
+ buffer.put(dst++, c);
+ break;
+ }
+ prev = c;
+ }
+
+ String detectedLineSeparator = null;
+ if (crlfCount > crCount && crlfCount > lfCount) {
+ detectedLineSeparator = "\r\n";
+ } else if (crCount > lfCount) {
+ detectedLineSeparator = "\r";
+ } else if (lfCount > 0) {
+ detectedLineSeparator = "\n";
+ }
+
+ CharSequence result = buffer.length() == dst ? buffer : buffer.subSequence(0, dst);
+ return Pair.create(result, detectedLineSeparator);
+ }
+
+ private static Charset detectCharset(@NotNull VirtualFile virtualFile, @NotNull byte[] content) {
+ if (virtualFile.isCharsetSet()) return virtualFile.getCharset();
+
+ Charset charset = doDetectCharset(virtualFile, content);
+ charset = charset == null ? EncodingRegistry.getInstance().getDefaultCharset() : charset;
+ if (EncodingRegistry.getInstance().isNative2Ascii(virtualFile)) {
+ charset = Native2AsciiCharset.wrap(charset);
+ }
+ virtualFile.setCharset(charset);
+ return charset;
+ }
+
+ @NotNull
+ public static Charset detectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content) {
+ return doDetectCharsetAndSetBOM(virtualFile, content).getFirst();
+ }
+
+ @NotNull
+ private static Pair doDetectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content) {
+ Charset charset = detectCharset(virtualFile, content);
+ Pair bomAndCharset = getBOMAndCharset(content, charset);
+ final byte[] bom = bomAndCharset.second;
+ if (bom.length != 0) {
+ virtualFile.setBOM(bom);
+ }
+ return bomAndCharset;
+ }
+
+ private static Charset doDetectCharset(@NotNull VirtualFile virtualFile, @NotNull byte[] content) {
+ Trinity guessed = guessFromContent(virtualFile, content, content.length);
+ if (guessed != null && guessed.first != null) return guessed.first;
+
+ FileType fileType = virtualFile.getFileType();
+ String charsetName = fileType.getCharset(virtualFile, content);
+
+ if (charsetName == null) {
+ Charset saved = EncodingRegistry.getInstance().getEncoding(virtualFile, true);
+ if (saved != null) return saved;
+ }
+ return CharsetToolkit.forName(charsetName);
+ }
+
+ @Nullable("null means no luck, otherwise it's tuple(guessed encoding, hint about content if was unable to guess, BOM)")
+ public static Trinity guessFromContent(VirtualFile virtualFile, byte[] content, int length) {
+ EncodingRegistry settings = EncodingRegistry.getInstance();
+ boolean shouldGuess = settings != null && settings.isUseUTFGuessing(virtualFile);
+ CharsetToolkit toolkit = shouldGuess ? new CharsetToolkit(content, EncodingRegistry.getInstance().getDefaultCharset()) : null;
+ setCharsetWasDetectedFromBytes(virtualFile, false);
+ if (shouldGuess) {
+ toolkit.setEnforce8Bit(true);
+ Charset charset = toolkit.guessFromBOM();
+ if (charset != null) {
+ setCharsetWasDetectedFromBytes(virtualFile, true);
+ byte[] bom = CharsetToolkit.getBom(charset);
+ if (bom == null) bom = CharsetToolkit.UTF8_BOM;
+ return Trinity.create(charset, null, bom);
+ }
+ CharsetToolkit.GuessedEncoding guessed = toolkit.guessFromContent(length);
+ if (guessed == CharsetToolkit.GuessedEncoding.VALID_UTF8) {
+ setCharsetWasDetectedFromBytes(virtualFile, true);
+ return Trinity.create(CharsetToolkit.UTF8_CHARSET, null, null); //UTF detected, ignore all directives
+ }
+ return Trinity.create(null, guessed, null);
+ }
+ return null;
+ }
+
+ @NotNull
+ private static Pair getBOMAndCharset(@NotNull byte[] content, final Charset charset) {
+ if (charset != null && charset.name().contains(CharsetToolkit.UTF8) && CharsetToolkit.hasUTF8Bom(content)) {
+ return Pair.create(charset, CharsetToolkit.UTF8_BOM);
+ }
+ try {
+ if (CharsetToolkit.hasUTF16LEBom(content)) {
+ return Pair.create(CharsetToolkit.UTF_16LE_CHARSET, CharsetToolkit.UTF16LE_BOM);
+ }
+ if (CharsetToolkit.hasUTF16BEBom(content)) {
+ return Pair.create(CharsetToolkit.UTF_16BE_CHARSET, CharsetToolkit.UTF16BE_BOM);
+ }
+ } catch (UnsupportedCharsetException ignore) {
+ }
+ if (charset == null) {
+ return Pair.create(Charset.defaultCharset(), CharsetToolkit.UTF8_BOM);
+ }
+ return Pair.create(charset, ArrayUtil.EMPTY_BYTE_ARRAY);
+ }
+
+ /**
+ * Gets the Writer for this file and sets modification stamp and time stamp to the specified values
+ * after closing the Writer.
+ *
+ * Normally you should not use this method.
+ *
+ * @param project
+ * @param virtualFile
+ * @param requestor any object to control who called this method. Note that
+ * it is considered to be an external change if requestor is null.
+ * See {@link com.intellij.openapi.vfs.VirtualFileEvent#getRequestor}
+ * @param text
+ * @param newModificationStamp new modification stamp or -1 if no special value should be set @return Writer
+ * @throws java.io.IOException if an I/O error occurs
+ * @see com.intellij.openapi.vfs.VirtualFile#getModificationStamp()
+ */
+ @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
+ public static Writer getWriter(@Nullable Project project, @NotNull VirtualFile virtualFile, Object requestor, @NotNull String text, final long newModificationStamp)
+ throws IOException {
+ Charset existing = virtualFile.getCharset();
+ Charset specified = extractCharsetFromFileContent(project, virtualFile, text);
+ Charset charset = chooseMostlyHarmlessCharset(existing, specified, text);
+ if (charset != null) {
+ if (!charset.equals(existing)) {
+ virtualFile.setCharset(charset);
+ }
+ setDetectedFromBytesFlagBack(virtualFile, charset, text);
+ }
+
+ // in c ase of "UTF-16", OutputStreamWriter sometimes adds BOM on it's own.
+ // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6800103
+ byte[] bom = virtualFile.getBOM();
+ Charset fromBom = bom == null ? null : CharsetToolkit.guessFromBOM(bom);
+ if (fromBom != null) charset = fromBom;
+
+ OutputStream outputStream = virtualFile.getOutputStream(requestor, newModificationStamp, -1);
+ OutputStreamWriter writer = charset == null ? new OutputStreamWriter(outputStream) : new OutputStreamWriter(outputStream, charset);
+ // no need to buffer ByteArrayOutputStream
+ return outputStream instanceof ByteArrayOutputStream ? writer : new BufferedWriter(writer);
+ }
+
+ private static void setDetectedFromBytesFlagBack(@NotNull VirtualFile virtualFile, @NotNull Charset charset, @NotNull String text) {
+ if (virtualFile.getBOM() != null) {
+ // prevent file to be reloaded in other encoding after save with BOM
+ setCharsetWasDetectedFromBytes(virtualFile, true);
+ return;
+ }
+
+ byte[] content = text.getBytes(charset);
+ CharsetToolkit.GuessedEncoding guessedEncoding = new CharsetToolkit(content).guessFromContent(content.length);
+ if (guessedEncoding == CharsetToolkit.GuessedEncoding.VALID_UTF8) {
+ setCharsetWasDetectedFromBytes(virtualFile, true);
+ }
+ }
+
+ private static Charset chooseMostlyHarmlessCharset(Charset existing, Charset specified, String text) {
+ if (existing == null) return specified;
+ if (specified == null) return existing;
+ if (specified.equals(existing)) return specified;
+ if (isSupported(specified, text)) return specified; //if explicitly specified encoding is safe, return it
+ if (isSupported(existing, text)) return existing; //otherwise stick to the old encoding if it's ok
+ return specified; //if both are bad there is no difference
+ }
+
+ private static boolean isSupported(@NotNull Charset charset, @NotNull String str) {
+ if (!charset.canEncode()) return false;
+ ByteBuffer out = charset.encode(str);
+ CharBuffer buffer = charset.decode(out);
+ return str.equals(buffer.toString());
+ }
+
+ public static Charset extractCharsetFromFileContent(@Nullable Project project, @NotNull VirtualFile virtualFile, @NotNull String text) {
+ Charset charset = charsetFromContentOrNull(project, virtualFile, text);
+ if (charset == null) charset = virtualFile.getCharset();
+ return charset;
+ }
+
+ @Nullable("returns null if cannot determine from content")
+ public static Charset charsetFromContentOrNull(@Nullable Project project, @NotNull VirtualFile virtualFile, @NotNull String text) {
+ FileType fileType = virtualFile.getFileType();
+ if (fileType instanceof LanguageFileType) {
+ return ((LanguageFileType) fileType).extractCharsetFromFileContent(project, virtualFile, text);
+ }
+ return null;
+ }
+
+ public static CharSequence loadText(@NotNull VirtualFile file) {
+ if (file instanceof LightVirtualFile) {
+ CharSequence content = ((LightVirtualFile) file).getContent();
+ if (StringUtil.indexOf(content, '\r') == -1) return content;
+
+ CharBuffer buffer = CharBuffer.allocate(content.length());
+ buffer.append(content);
+ buffer.rewind();
+ return convertLineSeparators(buffer).first;
+ }
+
+ assert !file.isDirectory() : "'" + file.getPresentableUrl() + "' is directory";
+ final FileType fileType = file.getFileType();
+
+ if (fileType.isBinary()) {
+ final BinaryFileDecompiler decompiler = BinaryFileTypeDecompilers.INSTANCE.forFileType(fileType);
+ if (decompiler != null) {
+ CharSequence text = decompiler.decompile(file);
+ StringUtil.assertValidSeparators(text);
+ return text;
+ }
+
+ throw new IllegalArgumentException("Attempt to load text for binary file, that doesn't have decompiler plugged in: " + file.getPresentableUrl());
+ }
+
+ try {
+ byte[] bytes = file.contentsToByteArray();
+ return getTextByBinaryPresentation(bytes, file);
+ } catch (IOException e) {
+ return ArrayUtil.EMPTY_CHAR_SEQUENCE;
+ }
+ }
+
+ @NotNull
+ public static CharSequence getTextByBinaryPresentation(@NotNull final byte[] bytes, @NotNull VirtualFile virtualFile) {
+ return getTextByBinaryPresentation(bytes, virtualFile, true);
+ }
+
+ @NotNull
+ public static CharSequence getTextByBinaryPresentation(@NotNull byte[] bytes, @NotNull VirtualFile virtualFile, final boolean rememberDetectedSeparators) {
+ Pair pair = doDetectCharsetAndSetBOM(virtualFile, bytes);
+ final Charset charset = pair.getFirst();
+ byte[] bom = pair.getSecond();
+ int offset = bom == null ? 0 : bom.length;
+
+ final Pair result = convertBytes(bytes, charset, offset);
+ if (rememberDetectedSeparators) {
+ virtualFile.putUserData(DETECTED_LINE_SEPARATOR_KEY, result.getSecond());
+ }
+ return result.getFirst();
+ }
+
+ /**
+ * Get detected line separator, if the file never been loaded, is loaded if checkFile parameter is specified.
+ *
+ * @param file the file to check
+ * @param checkFile if the line separator was not detected before, try to detect it
+ * @return the detected line separator or null
+ */
+ @Nullable
+ public static String detectLineSeparator(@NotNull VirtualFile file, boolean checkFile) {
+ String lineSeparator = getDetectedLineSeparator(file);
+ if (lineSeparator == null && checkFile) {
+ try {
+ getTextByBinaryPresentation(file.contentsToByteArray(), file);
+ lineSeparator = getDetectedLineSeparator(file);
+ } catch (IOException e) {
+ // null will be returned
+ }
+ }
+ return lineSeparator;
+ }
+
+ static String getDetectedLineSeparator(@NotNull VirtualFile file) {
+ return file.getUserData(DETECTED_LINE_SEPARATOR_KEY);
+ }
+
+ /**
+ * Change line separator for the file to the specified value (assumes that the documents were saved)
+ *
+ * @param project the project instance
+ * @param requestor the requestor for the operation
+ * @param file the file to convert
+ * @param newLineSeparator the new line separator for the file
+ * @throws java.io.IOException in the case of IO problem
+ */
+ public static void changeLineSeparator(@Nullable Project project,
+ @Nullable Object requestor,
+ @NotNull VirtualFile file,
+ @NotNull String newLineSeparator) throws IOException {
+ String lineSeparator = getDetectedLineSeparator(file);
+ if (lineSeparator != null && lineSeparator.equals(newLineSeparator)) {
+ return;
+ }
+ CharSequence cs = getTextByBinaryPresentation(file.contentsToByteArray(), file);
+ lineSeparator = getDetectedLineSeparator(file);
+ if (lineSeparator == null || lineSeparator.equals(newLineSeparator)) {
+ return;
+ }
+ if (!newLineSeparator.equals("\n")) {
+ cs = StringUtil.convertLineSeparators(cs.toString(), newLineSeparator);
+ }
+ String text = cs.toString();
+ file.putUserData(DETECTED_LINE_SEPARATOR_KEY, newLineSeparator);
+ Writer w = getWriter(project, file, requestor, text, System.currentTimeMillis());
+ try {
+ w.write(text);
+ } finally {
+ w.close();
+ }
+ }
+
+ @NotNull
+ public static CharSequence getTextByBinaryPresentation(@NotNull byte[] bytes, Charset charset) {
+ Pair pair = getBOMAndCharset(bytes, charset);
+ byte[] bom = pair.getSecond();
+ int offset = bom == null ? 0 : bom.length;
+
+ final Pair result = convertBytes(bytes, charset, offset);
+ return result.getFirst();
+ }
+
+ // do not need to think about BOM here. it is processed outside
+ @NotNull
+ private static Pair convertBytes(@NotNull byte[] bytes, Charset charset, final int startOffset) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, startOffset, bytes.length - startOffset);
+
+ if (charset == null) {
+ charset = CharsetToolkit.getDefaultSystemCharset();
+ }
+ if (charset == null) {
+ //noinspection HardCodedStringLiteral
+ charset = Charset.forName("ISO-8859-1");
+ }
+ CharBuffer charBuffer = charset.decode(byteBuffer);
+ return convertLineSeparators(charBuffer);
+ }
+
+ private static final Key CHARSET_WAS_DETECTED_FROM_BYTES = new Key("CHARSET_WAS_DETECTED_FROM_BYTES");
+
+ public static boolean wasCharsetDetectedFromBytes(@NotNull VirtualFile virtualFile) {
+ return virtualFile.getUserData(CHARSET_WAS_DETECTED_FROM_BYTES) != null;
+ }
+
+ public static void setCharsetWasDetectedFromBytes(@NotNull VirtualFile virtualFile, boolean flag) {
+ virtualFile.putUserData(CHARSET_WAS_DETECTED_FROM_BYTES, flag ? Boolean.TRUE : null);
+ }
+}
diff --git a/idea_fix/src/com/intellij/openapi/util/IconLoader.java b/idea_fix/src/com/intellij/openapi/util/IconLoader.java
new file mode 100644
index 00000000000..70c739fed80
--- /dev/null
+++ b/idea_fix/src/com/intellij/openapi/util/IconLoader.java
@@ -0,0 +1,351 @@
+/*
+ * 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.openapi.util;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.reference.SoftReference;
+import com.intellij.util.ImageLoader;
+import com.intellij.util.containers.ConcurrentHashMap;
+import com.intellij.util.containers.WeakHashMap;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.lang.ref.Reference;
+import java.net.URL;
+import java.util.Map;
+
+//import sun.reflect.Reflection;
+
+public final class IconLoader {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ private static final ConcurrentHashMap ourIconsCache = new ConcurrentHashMap(100, 0.9f, 2);
+
+ /**
+ * This cache contains mapping between icons and disabled icons.
+ */
+ private static final Map ourIcon2DisabledIcon = new WeakHashMap(200);
+
+ private static final ImageIcon EMPTY_ICON = new ImageIcon(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)) {
+ @NonNls
+ public String toString() {
+ return "Empty icon " + super.toString();
+ }
+ };
+
+ private static boolean ourIsActivated = false;
+
+ private IconLoader() {
+ }
+
+ @Deprecated
+ public static Icon getIcon(@NotNull final Image image) {
+ return new MyImageIcon(image);
+ }
+
+ @NotNull
+ public static Icon getIcon(@NonNls final String path) {
+ int stackFrameCount = 2;
+ return IconLoader.EMPTY_ICON;
+ /*Class callerClass = Reflection.getCallerClass(stackFrameCount);
+ while (callerClass != null && callerClass.getClassLoader() == null) { // looks like a system class
+ callerClass = Reflection.getCallerClass(++stackFrameCount);
+ }
+ if (callerClass == null) {
+ callerClass = Reflection.getCallerClass(1);
+ }
+ return getIcon(path, callerClass);*/
+ }
+
+ @Nullable
+ /**
+ * Might return null if icon was not found.
+ * Use only if you expected null return value, otherwise see {@link com.intellij.openapi.util.IconLoader#getIcon(String)}
+ */
+ public static Icon findIcon(@NonNls final String path) {
+ int stackFrameCount = 2;
+ return IconLoader.EMPTY_ICON;
+ /*Class callerClass = Reflection.getCallerClass(stackFrameCount);
+ while (callerClass != null && callerClass.getClassLoader() == null) { // looks like a system class
+ callerClass = Reflection.getCallerClass(++stackFrameCount);
+ }
+ if (callerClass == null) {
+ callerClass = Reflection.getCallerClass(1);
+ }
+ return findIcon(path, callerClass);*/
+ }
+
+ @NotNull
+ public static Icon getIcon(@NotNull String path, @NotNull final Class aClass) {
+ final Icon icon = findIcon(path, aClass);
+ if (icon == null) {
+ LOG.error("Icon cannot be found in '" + path + "', aClass='" + aClass + "'");
+ }
+ return icon;
+ }
+
+ public static void activate() {
+ ourIsActivated = true;
+ }
+
+ private static boolean isLoaderDisabled() {
+ return !ourIsActivated;
+ }
+
+ /**
+ * Might return null if icon was not found.
+ * Use only if you expected null return value, otherwise see {@link com.intellij.openapi.util.IconLoader#getIcon(String, Class)}
+ */
+ @Nullable
+ public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass) {
+ return findIcon(path, aClass, false);
+ }
+
+ public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass, boolean computeNow) {
+ /*final ByClass icon = new ByClass(aClass, path);
+
+ if (computeNow || !Registry.is("ide.lazyIconLoading", true)) {
+ return icon.getOrComputeIcon();
+ }
+
+ return icon;*/
+ return IconLoader.EMPTY_ICON;
+ }
+
+ @Nullable
+ private static Icon findIcon(URL url) {
+ if (url == null) return null;
+ Icon icon = ourIconsCache.get(url);
+ if (icon == null) {
+ icon = new CachedImageIcon(url);
+ icon = ourIconsCache.cacheOrGet(url, icon);
+ }
+ return icon;
+ }
+
+ @Nullable
+ public static Icon findIcon(final String path, final ClassLoader aClassLoader) {
+ if (!path.startsWith("/")) return null;
+
+ final URL url = aClassLoader.getResource(path.substring(1));
+ return findIcon(url);
+ }
+
+ @Nullable
+ private static Icon checkIcon(final Image image, URL url) {
+ if (image == null || image.getHeight(LabelHolder.ourFakeComponent) < 1) { // image wasn't loaded or broken
+ return null;
+ }
+
+ final Icon icon = getIcon(image);
+ if (icon != null && !isGoodSize(icon)) {
+ LOG.error("Invalid icon: " + url); // # 22481
+ return EMPTY_ICON;
+ }
+ return icon;
+ }
+
+ public static boolean isGoodSize(@NotNull final Icon icon) {
+ return icon.getIconWidth() > 0 && icon.getIconHeight() > 0;
+ }
+
+ /**
+ * Gets (creates if necessary) disabled icon based on the passed one.
+ *
+ * @param icon
+ * @return ImageIcon constructed from disabled image of passed icon.
+ */
+ @Nullable
+ public static Icon getDisabledIcon(final Icon icon) {
+ if (icon == null) {
+ return null;
+ }
+ Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
+ if (disabledIcon == null) {
+ if (!isGoodSize(icon)) {
+ LOG.error(icon); // # 22481
+ return EMPTY_ICON;
+ }
+ final BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+ final Graphics2D graphics = image.createGraphics();
+
+ graphics.setColor(UIUtil.TRANSPARENT_COLOR);
+ graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
+ icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0);
+
+ graphics.dispose();
+
+ disabledIcon = new MyImageIcon(GrayFilter.createDisabledImage(image));
+ ourIcon2DisabledIcon.put(icon, disabledIcon);
+ }
+ return disabledIcon;
+ }
+
+ public static Icon getTransparentIcon(final Icon icon) {
+ return getTransparentIcon(icon, 0.5f);
+ }
+
+ public static Icon getTransparentIcon(final Icon icon, final float alpha) {
+ return new Icon() {
+ public int getIconHeight() {
+ return icon.getIconHeight();
+ }
+
+ public int getIconWidth() {
+ return icon.getIconWidth();
+ }
+
+ public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
+ final Graphics2D g2 = (Graphics2D) g;
+ final Composite saveComposite = g2.getComposite();
+ g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+ icon.paintIcon(c, g2, x, y);
+ g2.setComposite(saveComposite);
+ }
+ };
+ }
+
+ private static final class CachedImageIcon implements Icon {
+ private Object myRealIcon;
+ private final URL myUrl;
+
+ public CachedImageIcon(URL url) {
+ myUrl = url;
+ }
+
+ private synchronized Icon getRealIcon() {
+ if (isLoaderDisabled()) return EMPTY_ICON;
+
+ if (myRealIcon instanceof Icon) return (Icon) myRealIcon;
+
+ Icon icon;
+ if (myRealIcon instanceof Reference) {
+ icon = ((Reference) myRealIcon).get();
+ if (icon != null) return icon;
+ }
+
+ Image image = ImageLoader.loadFromUrl(myUrl);
+ icon = checkIcon(image, myUrl);
+
+ if (icon != null) {
+ if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) {
+ myRealIcon = icon;
+ } else {
+ myRealIcon = new SoftReference(icon);
+ }
+ }
+
+ return icon != null ? icon : EMPTY_ICON;
+ }
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ getRealIcon().paintIcon(c, g, x, y);
+ }
+
+ public int getIconWidth() {
+ return getRealIcon().getIconWidth();
+ }
+
+ public int getIconHeight() {
+ return getRealIcon().getIconHeight();
+ }
+ }
+
+ private static final class MyImageIcon extends ImageIcon {
+ public MyImageIcon(final Image image) {
+ super(image);
+ }
+
+ public final synchronized void paintIcon(final Component c, final Graphics g, final int x, final int y) {
+ super.paintIcon(null, g, x, y);
+ }
+ }
+
+ public abstract static class LazyIcon implements Icon {
+ private boolean myWasComputed;
+ private Icon myIcon;
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ final Icon icon = getOrComputeIcon();
+ if (icon != null) {
+ icon.paintIcon(c, g, x, y);
+ }
+ }
+
+ @Override
+ public int getIconWidth() {
+ final Icon icon = getOrComputeIcon();
+ return icon != null ? icon.getIconWidth() : 0;
+ }
+
+ @Override
+ public int getIconHeight() {
+ final Icon icon = getOrComputeIcon();
+ return icon != null ? icon.getIconHeight() : 0;
+ }
+
+ protected synchronized final Icon getOrComputeIcon() {
+ if (!myWasComputed) {
+ myWasComputed = true;
+ myIcon = compute();
+ }
+
+ return myIcon;
+ }
+
+ public final void load() {
+ getIconWidth();
+ }
+
+ protected abstract Icon compute();
+ }
+
+ private static class ByClass extends LazyIcon {
+ private final Class myCallerClass;
+ private final String myPath;
+
+ public ByClass(Class aClass, String path) {
+ myCallerClass = aClass;
+ myPath = path;
+ }
+
+ @Override
+ protected Icon compute() {
+ URL url = myCallerClass.getResource(myPath);
+ return findIcon(url);
+ }
+
+ @Override
+ public String toString() {
+ return "icon path=" + myPath + " class=" + myCallerClass;
+ }
+ }
+
+ private static class LabelHolder {
+ /**
+ * To get disabled icon with paint it into the image. Some icons require
+ * not null component to paint.
+ */
+ private static final JComponent ourFakeComponent = new JLabel();
+ }
+}
diff --git a/idea_fix/src/com/intellij/openapi/util/UserDataHolderBase.java b/idea_fix/src/com/intellij/openapi/util/UserDataHolderBase.java
new file mode 100644
index 00000000000..7c7b4fa3c56
--- /dev/null
+++ b/idea_fix/src/com/intellij/openapi/util/UserDataHolderBase.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2000-2009 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.openapi.util;
+
+
+//import com.intellij.util.concurrency.AtomicFieldUpdater;
+
+import com.intellij.util.containers.StripedLockConcurrentHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.util.ConcurrentModificationException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
+ private static final Key