removed redundant or outdated modules
This commit is contained in:
Generated
+4
-2
@@ -1,5 +1,7 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="">
|
||||
<module2copyright />
|
||||
<settings default="apache">
|
||||
<module2copyright>
|
||||
<element module="All" copyright="apache" />
|
||||
</module2copyright>
|
||||
</settings>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://E:/kotlin/kotlin-0.1.81/lib/intellij-core.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem;
|
||||
import com.intellij.openapi.vfs.local.CoreLocalFileSystem;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.file.PsiPackageImpl;
|
||||
import com.intellij.psi.impl.file.impl.JavaFileManager;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class CoreJavaFileManager implements JavaFileManager {
|
||||
private final CoreLocalFileSystem myLocalFileSystem;
|
||||
private final CoreJarFileSystem myJarFileSystem;
|
||||
private final List<File> myClasspath = new ArrayList<File>();
|
||||
private final List<String> myVirtualClasspath = new ArrayList<String>();
|
||||
private final PsiManager myPsiManager;
|
||||
|
||||
public CoreJavaFileManager(PsiManager psiManager, CoreLocalFileSystem localFileSystem, CoreJarFileSystem jarFileSystem) {
|
||||
myPsiManager = psiManager;
|
||||
myLocalFileSystem = localFileSystem;
|
||||
myJarFileSystem = jarFileSystem;
|
||||
addToClasspath("rt.jar");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiPackage findPackage(@NotNull String packageName) {
|
||||
String dirName = packageName.replace(".", "/");
|
||||
for (File file : myClasspath) {
|
||||
VirtualFile classDir = findUnderClasspathEntry(file, dirName);
|
||||
if (classDir != null) {
|
||||
return new PsiPackageImpl(myPsiManager, packageName);
|
||||
}
|
||||
}
|
||||
|
||||
for (String str : myVirtualClasspath) {
|
||||
VirtualFile file = myJarFileSystem.findFileByPath(str + "!/" + dirName);
|
||||
if (file != null) {
|
||||
return new PsiPackageImpl(myPsiManager, packageName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile findUnderClasspathEntry(File classpathEntry, String relativeName) {
|
||||
if (classpathEntry.isFile()) {
|
||||
return myJarFileSystem.findFileByPath(classpathEntry.getPath() + "!/" + relativeName);
|
||||
} else {
|
||||
return myLocalFileSystem.findFileByPath(new File(classpathEntry, relativeName).getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiClass findInVirtualJar(String classpathEntry, String relativeName) {
|
||||
String fileName = relativeName.replace(".", "/") + ".class";
|
||||
VirtualFile file = myJarFileSystem.findFileByPath(classpathEntry + "!/" + fileName);
|
||||
if (file != null) {
|
||||
PsiFile psiFile = myPsiManager.findFile(file);
|
||||
if (!(psiFile instanceof PsiJavaFile)) {
|
||||
/*ErrorWriter.ERROR_WRITER.writeException(ErrorWriter.getExceptionForLog(
|
||||
"UNKNOWN", new UnsupportedOperationException("no java file for .class"), classpathEntry + "!/" + relativeName
|
||||
));
|
||||
return null;*/
|
||||
throw new UnsupportedOperationException("no java file for .class " + classpathEntry + "!/" + relativeName);
|
||||
}
|
||||
final PsiClass[] classes = ((PsiJavaFile) psiFile).getClasses();
|
||||
if (classes.length == 1) {
|
||||
return classes[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiClass findClass(@NotNull String qName, @NotNull GlobalSearchScope scope) {
|
||||
for (File file : myClasspath) {
|
||||
final PsiClass psiClass = findClassInClasspathEntry(qName, file);
|
||||
if (psiClass != null) {
|
||||
return psiClass;
|
||||
}
|
||||
}
|
||||
for (String str : myVirtualClasspath) {
|
||||
final PsiClass psiClass = findInVirtualJar(str, qName);
|
||||
if (psiClass != null) {
|
||||
return psiClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiClass findClassInClasspathEntry(String qName, File file) {
|
||||
String fileName = qName.replace(".", "/") + ".class";
|
||||
VirtualFile classFile = findUnderClasspathEntry(file, fileName);
|
||||
|
||||
if (classFile != null) {
|
||||
PsiFile psiFile = myPsiManager.findFile(classFile);
|
||||
if (!(psiFile instanceof PsiJavaFile)) {
|
||||
throw new UnsupportedOperationException("no java file for .class");
|
||||
}
|
||||
final PsiClass[] classes = ((PsiJavaFile) psiFile).getClasses();
|
||||
if (classes.length == 1) {
|
||||
return classes[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiClass[] findClasses(@NotNull String qName, @NotNull GlobalSearchScope scope) {
|
||||
List<PsiClass> result = new ArrayList<PsiClass>();
|
||||
for (File file : myClasspath) {
|
||||
final PsiClass psiClass = findClassInClasspathEntry(qName, file);
|
||||
if (psiClass != null) {
|
||||
result.add(psiClass);
|
||||
}
|
||||
}
|
||||
for (String str : myVirtualClasspath) {
|
||||
final PsiClass psiClass = findInVirtualJar(str, qName);
|
||||
if (psiClass != null) {
|
||||
result.add(psiClass);
|
||||
}
|
||||
}
|
||||
return result.toArray(new PsiClass[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getNonTrivialPackagePrefixes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
public void addToClasspath(File path) {
|
||||
myClasspath.add(path);
|
||||
}
|
||||
|
||||
public void addToClasspath(String path) {
|
||||
myVirtualClasspath.add(path);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,668 +0,0 @@
|
||||
/*
|
||||
* 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.editor.impl;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import com.intellij.util.text.CharArrayCharSequence;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.intellij.util.text.CharSequenceBackedByArray;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* @author cdr
|
||||
*/
|
||||
abstract class CharArray implements CharSequenceBackedByArray {
|
||||
|
||||
@SuppressWarnings("UseOfArchaicSystemPropertyAccessors")
|
||||
private static final boolean DISABLE_DEFERRED_PROCESSING = false;
|
||||
@SuppressWarnings("UseOfArchaicSystemPropertyAccessors")
|
||||
private static final boolean DEBUG_DEFERRED_PROCESSING = false;
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#" + CharArray.class.getName());
|
||||
|
||||
/**
|
||||
* We can't exclude possibility of situation when <code>'defer changes'</code> state is {@link #setDeferredChangeMode(boolean) entered}
|
||||
* but not exited, hence, we want to perform automatic flushing if necessary in order to avoid memory leaks. This constant holds
|
||||
* a value that defines that 'automatic flushing' criteria, i.e. every time number of stored deferred changes exceeds this value,
|
||||
* they are automatically flushed.
|
||||
*/
|
||||
private static final int MAX_DEFERRED_CHANGES_NUMBER = 10000;
|
||||
|
||||
private final AtomicReference<TextChangesStorage> myDeferredChangesStorage = new AtomicReference<TextChangesStorage>();
|
||||
|
||||
private int myStart;
|
||||
/**
|
||||
* This class implements {@link #subSequence(int, int)} by creating object of the same class that partially shares the same
|
||||
* data as the object on which the method is called. So, this field may define interested end offset (if it's non-negative).
|
||||
*/
|
||||
private int myEnd = -1;
|
||||
private int myCount = 0;
|
||||
|
||||
private CharSequence myOriginalSequence;
|
||||
private char[] myArray;
|
||||
private SoftReference<String> myStringRef; // buffers String value - for not to generate it every time
|
||||
private int myBufferSize;
|
||||
private int myDeferredShift;
|
||||
private boolean myDeferredChangeMode;
|
||||
|
||||
// We had a problems with bulk document text processing, hence, debug facilities were introduced. The fields group below work with them.
|
||||
// The main idea is to hold all history of bulk processing iteration in order to be able to retrieve it from client and reproduce the
|
||||
// problem.
|
||||
|
||||
/**
|
||||
* Flag the identifies if current char array should debug bulk processing.
|
||||
*/
|
||||
private final boolean myDebugDeferredProcessing;
|
||||
|
||||
/**
|
||||
* Duplicate instance of the current char array that is used during debug processing as follows - apply every text change
|
||||
* from the bulk changes group to this instance immediately in order to be able to check if the current 'deferred change-aware'
|
||||
* instance functionally behaves at the same way as 'straightforward' one.
|
||||
*/
|
||||
private CharArray myDebugArray;
|
||||
|
||||
/**
|
||||
* Holds deferred changes create during the current bulk processing iteration.
|
||||
*/
|
||||
private List<TextChangeImpl> myDebugDeferredChanges;
|
||||
|
||||
/**
|
||||
* Document text on bulk processing start.
|
||||
*/
|
||||
private String myDebugTextOnBatchUpdateStart;
|
||||
|
||||
// max chars to hold, bufferSize == 0 means unbounded
|
||||
CharArray(int bufferSize) {
|
||||
this(bufferSize, new TextChangesStorage(), null, -1, -1);
|
||||
}
|
||||
|
||||
private CharArray(final int bufferSize, @NotNull TextChangesStorage deferredChangesStorage, @Nullable char[] data, int start, int end) {
|
||||
this(bufferSize, deferredChangesStorage, data, start, end, DEBUG_DEFERRED_PROCESSING);
|
||||
}
|
||||
|
||||
private CharArray(final int bufferSize, @NotNull TextChangesStorage deferredChangesStorage, @Nullable char[] data, int start, int end,
|
||||
boolean debugDeferredProcessing) {
|
||||
myBufferSize = bufferSize;
|
||||
myDeferredChangesStorage.set(deferredChangesStorage);
|
||||
if (data == null) {
|
||||
myOriginalSequence = "";
|
||||
} else {
|
||||
myArray = data;
|
||||
myCount = end - start;
|
||||
}
|
||||
if (start >= 0 && end >= 0) {
|
||||
myStart = start;
|
||||
myEnd = end;
|
||||
}
|
||||
|
||||
myDebugDeferredProcessing = debugDeferredProcessing;
|
||||
if (myDebugDeferredProcessing) {
|
||||
|
||||
myDebugArray = new CharArray(bufferSize, new TextChangesStorage(), data == null ? null : Arrays.copyOf(data, data.length),
|
||||
start, end, false) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected DocumentEvent beforeChangedUpdate(DocumentImpl subj,
|
||||
int offset,
|
||||
CharSequence oldString,
|
||||
CharSequence newString,
|
||||
boolean wholeTextReplaced) {
|
||||
return new DocumentEventImpl(subj, offset, oldString, newString, -1, wholeTextReplaced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp) {
|
||||
}
|
||||
};
|
||||
myDebugDeferredChanges = new ArrayList<TextChangeImpl>();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
myBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract DocumentEvent beforeChangedUpdate(DocumentImpl subj,
|
||||
int offset,
|
||||
@Nullable CharSequence oldString,
|
||||
@Nullable CharSequence newString,
|
||||
boolean wholeTextReplaced);
|
||||
|
||||
protected abstract void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp);
|
||||
|
||||
public void setText(@Nullable final DocumentImpl subj, final CharSequence chars) {
|
||||
myOriginalSequence = chars;
|
||||
myArray = null;
|
||||
myCount = chars.length();
|
||||
myStringRef = null;
|
||||
TextChangesStorage storage = myDeferredChangesStorage.get();
|
||||
storage.getLock().lock();
|
||||
try {
|
||||
if (isSubSequence()) {
|
||||
myDeferredChangesStorage.set(new TextChangesStorage());
|
||||
myStart = 0;
|
||||
myEnd = -1;
|
||||
} else {
|
||||
storage.clear();
|
||||
}
|
||||
} finally {
|
||||
storage.getLock().unlock();
|
||||
}
|
||||
|
||||
if (subj != null) {
|
||||
trimToSize(subj);
|
||||
}
|
||||
|
||||
if (myDebugDeferredProcessing) {
|
||||
myDebugArray.setText(subj, chars);
|
||||
myDebugDeferredChanges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void replace(DocumentImpl subj,
|
||||
int startOffset, int endOffset, CharSequence toDelete, CharSequence newString, long newModificationStamp,
|
||||
boolean wholeTextReplaced) {
|
||||
final DocumentEvent event = beforeChangedUpdate(subj, startOffset, toDelete, newString, wholeTextReplaced);
|
||||
startOffset += myStart;
|
||||
endOffset += myStart;
|
||||
doReplace(startOffset, endOffset, newString);
|
||||
afterChangedUpdate(event, newModificationStamp);
|
||||
}
|
||||
|
||||
private void doReplace(int startOffset, int endOffset, CharSequence newString) {
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl(newString, startOffset, endOffset));
|
||||
if (myDebugDeferredProcessing) {
|
||||
myDebugArray.doReplace(startOffset, endOffset, newString);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int newLength = newString.length();
|
||||
int oldLength = endOffset - startOffset;
|
||||
|
||||
CharArrayUtil.getChars(newString, myArray, startOffset, Math.min(newLength, oldLength));
|
||||
|
||||
if (newLength > oldLength) {
|
||||
doInsert(newString.subSequence(oldLength, newLength), endOffset);
|
||||
} else if (newLength < oldLength) {
|
||||
doRemove(startOffset + newLength, startOffset + oldLength);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(DocumentImpl subj, int startIndex, int endIndex, CharSequence toDelete) {
|
||||
DocumentEvent event = beforeChangedUpdate(subj, startIndex, toDelete, null, false);
|
||||
startIndex += myStart;
|
||||
endIndex += myStart;
|
||||
doRemove(startIndex, endIndex);
|
||||
afterChangedUpdate(event, LocalTimeCounter.currentTime());
|
||||
}
|
||||
|
||||
private void doRemove(final int startIndex, final int endIndex) {
|
||||
if (startIndex == endIndex) {
|
||||
return;
|
||||
}
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl("", startIndex, endIndex));
|
||||
if (myDebugDeferredProcessing) {
|
||||
myDebugArray.doRemove(startIndex, endIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (endIndex < myCount) {
|
||||
System.arraycopy(myArray, endIndex, myArray, startIndex, myCount - endIndex);
|
||||
}
|
||||
myCount -= endIndex - startIndex;
|
||||
}
|
||||
|
||||
public void insert(DocumentImpl subj, CharSequence s, int startIndex) {
|
||||
DocumentEvent event = beforeChangedUpdate(subj, startIndex, null, s, false);
|
||||
startIndex += myStart;
|
||||
doInsert(s, startIndex);
|
||||
|
||||
afterChangedUpdate(event, LocalTimeCounter.currentTime());
|
||||
trimToSize(subj);
|
||||
}
|
||||
|
||||
private void doInsert(final CharSequence s, final int startIndex) {
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl(s, startIndex));
|
||||
if (myDebugDeferredProcessing) {
|
||||
myDebugArray.doInsert(s, startIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int insertLength = s.length();
|
||||
myArray = relocateArray(myArray, myCount + insertLength);
|
||||
if (startIndex < myCount) {
|
||||
System.arraycopy(myArray, startIndex, myArray, startIndex + insertLength, myCount - startIndex);
|
||||
}
|
||||
|
||||
CharArrayUtil.getChars(s, myArray, startIndex);
|
||||
myCount += insertLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores given change at collection of deferred changes (merging it with others if necessary) and updates current object
|
||||
* state ({@link #length() length} etc).
|
||||
*
|
||||
* @param change new change to store
|
||||
*/
|
||||
private void storeChange(@NotNull TextChangeImpl change) {
|
||||
if (!change.isWithinBounds(length())) {
|
||||
LOG.error(String.format(
|
||||
"Invalid change attempt detected - given change bounds are not within the current char array. Change: %d:%d-%d",
|
||||
change.getText().length(), change.getStart(), change.getEnd()
|
||||
), dumpState());
|
||||
return;
|
||||
}
|
||||
TextChangesStorage storage = myDeferredChangesStorage.get();
|
||||
storage.getLock().lock();
|
||||
try {
|
||||
doStoreChange(change);
|
||||
} finally {
|
||||
storage.getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void doStoreChange(@NotNull TextChangeImpl change) {
|
||||
TextChangesStorage storage = myDeferredChangesStorage.get();
|
||||
if (storage.size() >= MAX_DEFERRED_CHANGES_NUMBER) {
|
||||
flushDeferredChanged(storage);
|
||||
}
|
||||
storage.store(change);
|
||||
myDeferredShift += change.getDiff();
|
||||
|
||||
if (myDebugDeferredProcessing) {
|
||||
myDebugDeferredChanges.add(change);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareForModification() {
|
||||
if (myOriginalSequence != null) {
|
||||
myArray = new char[myOriginalSequence.length()];
|
||||
CharArrayUtil.getChars(myOriginalSequence, myArray, 0);
|
||||
myOriginalSequence = null;
|
||||
}
|
||||
myStringRef = null;
|
||||
}
|
||||
|
||||
public CharSequence getCharArray() {
|
||||
if (myOriginalSequence != null) return myOriginalSequence;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String str = myStringRef != null ? myStringRef.get() : null;
|
||||
if (str == null) {
|
||||
if (myOriginalSequence != null) {
|
||||
str = myOriginalSequence.toString();
|
||||
} else if (!hasDeferredChanges()) {
|
||||
str = new String(myArray, myStart, myCount);
|
||||
} else {
|
||||
str = substring(0, length()).toString();
|
||||
}
|
||||
myStringRef = new SoftReference<String>(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 <code>true</code> if this object is at {@link #setDeferredChangeMode(boolean) defer changes} mode;
|
||||
* <code>false</code> 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.
|
||||
* <p/>
|
||||
* This method allows to define that <code>'defer changes'</code> mode usages, i.e. expected usage pattern is as follows:
|
||||
* <pre>
|
||||
* <ol>
|
||||
* <li>
|
||||
* Client of this class enters <code>'defer changes'</code> mode (calls this method with <code>'true'</code> argument).
|
||||
* That means that all subsequent changes will not actually modify backed array data and will be stored separately;
|
||||
* </li>
|
||||
* <li>
|
||||
* 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});
|
||||
* </li>
|
||||
* <li>
|
||||
* Client of this class indicates that <code>'massive change time'</code> is over by calling this method with <code>'false'</code>
|
||||
* 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;
|
||||
* </li>
|
||||
* </ol>
|
||||
* </pre>
|
||||
* <p/>
|
||||
* <b>Note:</b> we can't exclude possibility that <code>'defer changes'</code> mode is started but inadvertently not ended
|
||||
* (due to programming error, unexpected exception etc). Hence, this class is free to automatically end
|
||||
* <code>'defer changes'</code> mode when necessary in order to avoid memory leak with infinite deferred changes storing.
|
||||
*
|
||||
* @param deferredChangeMode flag that defines if <code>'defer changes'</code> 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<TextChangeImpl> 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
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
/*
|
||||
* 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<String> DETECTED_LINE_SEPARATOR_KEY = Key.create("DETECTED_LINE_SEPARATOR_KEY");
|
||||
|
||||
private LoadTextUtil() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<CharSequence, String> 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<Charset, byte[]> doDetectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content) {
|
||||
Charset charset = detectCharset(virtualFile, content);
|
||||
Pair<Charset, byte[]> 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<Charset, CharsetToolkit.GuessedEncoding, byte[]> 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<Charset, CharsetToolkit.GuessedEncoding, byte[]> 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<Charset, byte[]> 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 <code>Writer</code> for this file and sets modification stamp and time stamp to the specified values
|
||||
* after closing the Writer.<p>
|
||||
* <p/>
|
||||
* 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 <code>requestor</code> is <code>null</code>.
|
||||
* 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 <code>Writer</code>
|
||||
* @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<Charset, byte[]> pair = doDetectCharsetAndSetBOM(virtualFile, bytes);
|
||||
final Charset charset = pair.getFirst();
|
||||
byte[] bom = pair.getSecond();
|
||||
int offset = bom == null ? 0 : bom.length;
|
||||
|
||||
final Pair<CharSequence, String> 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<Charset, byte[]> pair = getBOMAndCharset(bytes, charset);
|
||||
byte[] bom = pair.getSecond();
|
||||
int offset = bom == null ? 0 : bom.length;
|
||||
|
||||
final Pair<CharSequence, String> result = convertBytes(bytes, charset, offset);
|
||||
return result.getFirst();
|
||||
}
|
||||
|
||||
// do not need to think about BOM here. it is processed outside
|
||||
@NotNull
|
||||
private static Pair<CharSequence, String> 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<Boolean> CHARSET_WAS_DETECTED_FROM_BYTES = new Key<Boolean>("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);
|
||||
}
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
/*
|
||||
* 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<URL, Icon> ourIconsCache = new ConcurrentHashMap<URL, Icon>(100, 0.9f, 2);
|
||||
|
||||
/**
|
||||
* This cache contains mapping between icons and disabled icons.
|
||||
*/
|
||||
private static final Map<Icon, Icon> ourIcon2DisabledIcon = new WeakHashMap<Icon, Icon>(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 <code>ImageIcon</code> 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<Icon>) 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>(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();
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* 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<Map<Key, Object>> COPYABLE_USER_MAP_KEY = Key.create("COPYABLE_USER_MAP_KEY");
|
||||
|
||||
/**
|
||||
* Concurrent writes to this field are via CASes only, using the {@link #}
|
||||
* When map becomes empty, this field set to null atomically
|
||||
* <p/>
|
||||
* Basic state transitions are as follows:
|
||||
* <p/>
|
||||
* (adding keyvalue) (putUserData(key,value))
|
||||
* [myUserMap=null] -> [myUserMap=(key->value)]
|
||||
* <p/>
|
||||
* (adding another) (putUserData(key2,value2))
|
||||
* [myUserMap=(key->value)] -> [myUserMap=(key->value, key2->value2)]
|
||||
* <p/>
|
||||
* (removing keyvalue) (putUserData(k2,null))
|
||||
* [myUserMap=(key->value, k2->v2)] -> [myUserMap=(key->value)]
|
||||
* <p/>
|
||||
* (removing last entry) (putUserData(key,null))
|
||||
* [myUserMap=(key->value)] -> [myUserMap=null]
|
||||
*/
|
||||
private volatile ConcurrentMap<Key, Object> myUserMap = null;
|
||||
|
||||
protected Object clone() {
|
||||
try {
|
||||
UserDataHolderBase clone = (UserDataHolderBase) super.clone();
|
||||
clone.myUserMap = null;
|
||||
copyCopyableDataTo(clone);
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public String getUserDataString() {
|
||||
final ConcurrentMap<Key, Object> userMap = myUserMap;
|
||||
if (userMap == null) {
|
||||
return "";
|
||||
}
|
||||
final Map copyableMap = getUserData(COPYABLE_USER_MAP_KEY);
|
||||
return userMap.toString() + (copyableMap == null ? "" : copyableMap.toString());
|
||||
}
|
||||
|
||||
public void copyUserDataTo(UserDataHolderBase other) {
|
||||
ConcurrentMap<Key, Object> map = myUserMap;
|
||||
if (map == null) {
|
||||
other.myUserMap = null;
|
||||
} else {
|
||||
ConcurrentMap<Key, Object> fresh = createDataMap(map.size());
|
||||
fresh.putAll(map);
|
||||
other.myUserMap = fresh;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getUserData(@NotNull Key<T> key) {
|
||||
final Map<Key, Object> map = myUserMap;
|
||||
//noinspection unchecked
|
||||
return map == null ? null : (T) map.get(key);
|
||||
}
|
||||
|
||||
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
|
||||
while (true) {
|
||||
try {
|
||||
if (value == null) {
|
||||
ConcurrentMap<Key, Object> map = myUserMap;
|
||||
if (map == null) break;
|
||||
@SuppressWarnings("unchecked")
|
||||
T previous = (T) map.remove(key);
|
||||
boolean removed = previous != null;
|
||||
if (removed) {
|
||||
nullifyMapFieldIfEmpty();
|
||||
}
|
||||
} else {
|
||||
getOrCreateMap().put(key, value);
|
||||
}
|
||||
break;
|
||||
} catch (ConcurrentModificationException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentMap<Key, Object> createDataMap(int initialCapacity) {
|
||||
return new StripedLockConcurrentHashMap<Key, Object>(initialCapacity);
|
||||
}
|
||||
|
||||
public <T> T getCopyableUserData(Key<T> key) {
|
||||
return getCopyableUserDataImpl(key);
|
||||
}
|
||||
|
||||
protected final <T> T getCopyableUserDataImpl(Key<T> key) {
|
||||
Map map = getUserData(COPYABLE_USER_MAP_KEY);
|
||||
//noinspection unchecked
|
||||
return map == null ? null : (T) map.get(key);
|
||||
}
|
||||
|
||||
public <T> void putCopyableUserData(Key<T> key, T value) {
|
||||
putCopyableUserDataImpl(key, value);
|
||||
}
|
||||
|
||||
private Map<Key, Object> getOrCreateCopyableMap(boolean create) {
|
||||
Map<Key, Object> copyMap = getUserData(COPYABLE_USER_MAP_KEY);
|
||||
if (copyMap == null && create) {
|
||||
copyMap = createDataMap(1);
|
||||
copyMap = putUserDataIfAbsent(COPYABLE_USER_MAP_KEY, copyMap);
|
||||
}
|
||||
|
||||
return copyMap;
|
||||
}
|
||||
|
||||
protected final <T> void putCopyableUserDataImpl(Key<T> key, T value) {
|
||||
while (true) {
|
||||
try {
|
||||
Map<Key, Object> copyMap = getOrCreateCopyableMap(value != null);
|
||||
if (copyMap == null) break;
|
||||
|
||||
if (value == null) {
|
||||
copyMap.remove(key);
|
||||
if (copyMap.isEmpty()) {
|
||||
((StripedLockConcurrentHashMap<Key, Object>) copyMap).blockModification();
|
||||
ConcurrentMap<Key, Object> newCopyMap;
|
||||
if (copyMap.isEmpty()) {
|
||||
newCopyMap = null;
|
||||
} else {
|
||||
newCopyMap = createDataMap(copyMap.size());
|
||||
newCopyMap.putAll(copyMap);
|
||||
}
|
||||
boolean replaced = replace(COPYABLE_USER_MAP_KEY, copyMap, newCopyMap);
|
||||
if (!replaced) continue;
|
||||
}
|
||||
} else {
|
||||
copyMap.put(key, value);
|
||||
}
|
||||
break;
|
||||
} catch (ConcurrentModificationException ignored) {
|
||||
// someone blocked modification, retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentMap<Key, Object> getOrCreateMap() {
|
||||
while (true) {
|
||||
ConcurrentMap<Key, Object> map = myUserMap;
|
||||
if (map != null) return map;
|
||||
map = createDataMap(2);
|
||||
boolean updated = true;
|
||||
//throw new UnsupportedOperationException("Exception in (my) UserDataHolderBase");
|
||||
// boolean updated = updater.compareAndSet(this, null, map);
|
||||
if (updated) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T> boolean replace(@NotNull Key<T> key, @Nullable T oldValue, @Nullable T newValue) {
|
||||
while (true) {
|
||||
try {
|
||||
ConcurrentMap<Key, Object> map = getOrCreateMap();
|
||||
if (oldValue == null) {
|
||||
return newValue == null || map.putIfAbsent(key, newValue) == null;
|
||||
}
|
||||
if (newValue == null) {
|
||||
boolean removed = map.remove(key, oldValue);
|
||||
if (removed) {
|
||||
nullifyMapFieldIfEmpty();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
return map.replace(key, oldValue, newValue);
|
||||
} catch (ConcurrentModificationException ignored) {
|
||||
// someone blocked modification, retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T> T putUserDataIfAbsent(@NotNull final Key<T> key, @NotNull final T value) {
|
||||
Object v = getOrCreateMap().get(key);
|
||||
if (v != null) {
|
||||
//noinspection unchecked
|
||||
return (T) v;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
T prev = (T) getOrCreateMap().putIfAbsent(key, value);
|
||||
return prev == null ? value : prev;
|
||||
} catch (ConcurrentModificationException ignored) {
|
||||
// someone blocked modification, retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void copyCopyableDataTo(@NotNull UserDataHolderBase clone) {
|
||||
Map<Key, Object> copyableMap = getUserData(COPYABLE_USER_MAP_KEY);
|
||||
if (copyableMap != null) {
|
||||
ConcurrentMap<Key, Object> copy = createDataMap(copyableMap.size());
|
||||
copy.putAll(copyableMap);
|
||||
copyableMap = copy;
|
||||
}
|
||||
clone.putUserData(COPYABLE_USER_MAP_KEY, copyableMap);
|
||||
}
|
||||
|
||||
protected void clearUserData() {
|
||||
myUserMap = null;
|
||||
}
|
||||
|
||||
// private static final AtomicFieldUpdater<UserDataHolderBase, ConcurrentMap> updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, ConcurrentMap.class);
|
||||
|
||||
private void nullifyMapFieldIfEmpty() {
|
||||
try {
|
||||
while (true) {
|
||||
StripedLockConcurrentHashMap<Key, Object> map = (StripedLockConcurrentHashMap<Key, Object>) myUserMap;
|
||||
if (map == null || !map.isEmpty()) break;
|
||||
map.blockModification(); // we block the map and either replace it with null or fail with replace, in both cases the map is thrown away
|
||||
ConcurrentMap<Key, Object> newMap;
|
||||
if (map.isEmpty()) {
|
||||
newMap = null;
|
||||
} else {
|
||||
// someone managed to add something in the meantime
|
||||
// atomically replace the blocked map with newly created map filled with the data sneaked in
|
||||
newMap = createDataMap(map.size());
|
||||
newMap.putAll(map);
|
||||
}
|
||||
boolean replaced = true;
|
||||
//throw new UnsupportedOperationException("Exception in (my) UserDataHolderBase");
|
||||
//boolean replaced = updater.compareAndSet(this, map, newMap);
|
||||
if (replaced) break;
|
||||
// else someone has replaced map already and pushing back the changes is his responsibility
|
||||
}
|
||||
} catch (ConcurrentModificationException ignored) {
|
||||
// somebody has already blocked the map, back off
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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.vfs.impl.jar;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class CoreJarHandler extends JarHandlerBase {
|
||||
private final Map<String, VirtualFile> myFileMap = new HashMap<String, VirtualFile>();
|
||||
private final CoreJarFileSystem myFileSystem;
|
||||
|
||||
public CoreJarHandler(CoreJarFileSystem fileSystem, String path) {
|
||||
super(fileSystem, path);
|
||||
myFileSystem = fileSystem;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VirtualFile findFileByPath(String pathInJar) {
|
||||
if (getZip() == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile file = myFileMap.get(pathInJar);
|
||||
if (file == null) {
|
||||
if (pathInJar.length() > 0) {
|
||||
EntryInfo entryInfo = getEntryInfo(pathInJar);
|
||||
if (entryInfo == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
file = new CoreJarVirtualFile(myFileSystem, this, pathInJar);
|
||||
myFileMap.put(pathInJar, file);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* 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.vfs.impl.jar;
|
||||
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.TimedReference;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class JarHandlerBase {
|
||||
protected final TimedReference<ZipInputStream> myZipFile = new TimedReference<ZipInputStream>(null);
|
||||
protected SoftReference<Map<String, EntryInfo>> myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(null);
|
||||
protected final Object lock = new Object();
|
||||
private InputStream inputStream;
|
||||
|
||||
protected final String myBasePath;
|
||||
|
||||
protected static class EntryInfo {
|
||||
public EntryInfo(final String shortName, final EntryInfo parent, final boolean directory, byte[] content) {
|
||||
this.shortName = new String(shortName);
|
||||
this.parent = parent;
|
||||
isDirectory = directory;
|
||||
this.content = content;
|
||||
|
||||
}
|
||||
|
||||
final boolean isDirectory;
|
||||
final byte[] content;
|
||||
protected final String shortName;
|
||||
final EntryInfo parent;
|
||||
}
|
||||
|
||||
public JarHandlerBase(CoreJarFileSystem myFileSystem, String path) {
|
||||
if (inputStream == null) {
|
||||
try {
|
||||
inputStream = new VirtualJarFile(myFileSystem, path).getInputStream();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
myBasePath = path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Map<String, EntryInfo> initEntries() {
|
||||
synchronized (lock) {
|
||||
Map<String, EntryInfo> map = myRelPathsToEntries.get();
|
||||
if (map == null) {
|
||||
final ZipInputStream zip = getZip();
|
||||
|
||||
map = new HashMap<String, EntryInfo>();
|
||||
if (zip != null) {
|
||||
map.put("", new EntryInfo("", null, true, new byte[0]));
|
||||
try {
|
||||
ZipEntry entry = zip.getNextEntry();
|
||||
while (entry != null) {
|
||||
final String name = entry.getName();
|
||||
final boolean isDirectory = name.endsWith("/");
|
||||
if (entry.getExtra() == null) {
|
||||
byte[] cont = new byte[(int) entry.getSize()];
|
||||
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
|
||||
InputStream stream = getZip();
|
||||
if (stream != null) {
|
||||
int tmp;
|
||||
if ((tmp = stream.read(cont)) == entry.getSize()) {
|
||||
byteArray.write(cont, 0, tmp);
|
||||
entry.setExtra(byteArray.toByteArray());
|
||||
} else {
|
||||
int readFromIS = tmp;
|
||||
if (tmp < entry.getSize()) {
|
||||
byteArray.write(cont, 0, tmp);
|
||||
while (((tmp = stream.read(cont)) != -1) && (tmp + readFromIS <= entry.getSize())) {
|
||||
byteArray.write(cont, 0, tmp);
|
||||
readFromIS += tmp;
|
||||
}
|
||||
entry.setExtra(byteArray.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getOrCreate(isDirectory ? name.substring(0, name.length() - 1) : name, isDirectory, map, entry.getExtra());
|
||||
|
||||
entry = zip.getNextEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(map);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public File getMirrorFile(File originalFile) {
|
||||
return originalFile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ZipInputStream getZip() {
|
||||
ZipInputStream zip = myZipFile.get();
|
||||
if (zip == null) {
|
||||
if (inputStream == null) {
|
||||
throw new IllegalArgumentException("Input Stream is null");
|
||||
} else {
|
||||
zip = new ZipInputStream(inputStream);
|
||||
}
|
||||
myZipFile.set(zip);
|
||||
}
|
||||
return zip;
|
||||
}
|
||||
|
||||
protected InputStream getOriginalFile() {
|
||||
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
private static EntryInfo getOrCreate(String entryName, boolean isDirectory, Map<String, EntryInfo> map, byte[] content) {
|
||||
EntryInfo info = map.get(entryName);
|
||||
if (info == null) {
|
||||
int idx = entryName.lastIndexOf('/');
|
||||
final String parentEntryName = idx > 0 ? entryName.substring(0, idx) : "";
|
||||
String shortName = idx > 0 ? entryName.substring(idx + 1) : entryName;
|
||||
if (".".equals(shortName)) return getOrCreate(parentEntryName, true, map, content);
|
||||
|
||||
info = new EntryInfo(shortName, getOrCreate(parentEntryName, true, map, content), isDirectory, content);
|
||||
map.put(entryName, info);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String[] list(@NotNull final VirtualFile file) {
|
||||
synchronized (lock) {
|
||||
EntryInfo parentEntry = getEntryInfo(file);
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (EntryInfo info : getEntriesMap().values()) {
|
||||
if (info.parent == parentEntry) {
|
||||
names.add(info.shortName);
|
||||
}
|
||||
}
|
||||
|
||||
return ArrayUtil.toStringArray(names);
|
||||
}
|
||||
}
|
||||
|
||||
protected EntryInfo getEntryInfo(final VirtualFile file) {
|
||||
synchronized (lock) {
|
||||
String parentPath = getRelativePath(file);
|
||||
return getEntryInfo(parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
public EntryInfo getEntryInfo(String parentPath) {
|
||||
return getEntriesMap().get(parentPath);
|
||||
}
|
||||
|
||||
protected Map<String, EntryInfo> getEntriesMap() {
|
||||
return initEntries();
|
||||
}
|
||||
|
||||
private String getRelativePath(final VirtualFile file) {
|
||||
// throw new UnsupportedOperationException(file.getPath());
|
||||
final String path = file.getPath().substring(myBasePath.length() + 1);
|
||||
return path.startsWith("/") ? path.substring(1) : path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ZipEntry convertToEntry(VirtualFile file) {
|
||||
String path = getRelativePath(file);
|
||||
final ZipInputStream zip = getZip();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EntryInfo convertToISEntry(VirtualFile file) {
|
||||
String path = getRelativePath(file);
|
||||
final ZipInputStream zip = getZip();
|
||||
return myRelPathsToEntries.get().get(path);
|
||||
}
|
||||
|
||||
public long getLength(@NotNull final VirtualFile file) {
|
||||
synchronized (lock) {
|
||||
final ZipEntry entry = convertToEntry(file);
|
||||
return entry != null ? entry.getSize() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
|
||||
return new BufferExposingByteArrayInputStream(contentsToByteArray(file));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
|
||||
synchronized (lock) {
|
||||
EntryInfo info = convertToISEntry(file);
|
||||
if (info == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
byte[] content = info.content;
|
||||
if (content == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
return content;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public long getTimeStamp(@NotNull final VirtualFile file) {
|
||||
if (file.getParent() == null) return file.getTimeStamp(); // Optimization
|
||||
// if (file.getParent() == null) return getOriginalFile().lastModified(); // Optimization
|
||||
synchronized (lock) {
|
||||
final ZipEntry entry = convertToEntry(file);
|
||||
return entry != null ? entry.getTime() : -1L;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDirectory(@NotNull final VirtualFile file) {
|
||||
if (file.getParent() == null) return true; // Optimization
|
||||
synchronized (lock) {
|
||||
final String path = getRelativePath(file);
|
||||
final EntryInfo info = getEntryInfo(path);
|
||||
return info == null || info.isDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
|
||||
if (fileOrDirectory.getParent() == null) {
|
||||
// Optimization. Do not build entries if asked for jar root existence.
|
||||
return myZipFile.get() != null;
|
||||
// return myZipFile.get() != null || getOriginalFile().exists();
|
||||
}
|
||||
|
||||
return getEntryInfo(fileOrDirectory) != null;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.intellij.openapi.vfs.impl.jar;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: Natalia.Ukhorskaya
|
||||
* Date: 12/1/11
|
||||
* Time: 5:06 PM
|
||||
*/
|
||||
|
||||
public class VirtualJarFile extends VirtualFile {
|
||||
private byte[] byteArray;
|
||||
private final CoreJarFileSystem myFileSystem;
|
||||
private final String name;
|
||||
|
||||
public VirtualJarFile(CoreJarFileSystem myFileSystem, String pathInJar) {
|
||||
this.myFileSystem = myFileSystem;
|
||||
this.name = pathInJar;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VirtualFileSystem getFileSystem() {
|
||||
return myFileSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFile getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFile[] getChildren() {
|
||||
return VirtualFile.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) throws IOException {
|
||||
return System.out;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public byte[] contentsToByteArray() throws IOException {
|
||||
if (byteArray.length <= 0) {
|
||||
int length;
|
||||
byte[] tmp = new byte[1024];
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InputStream rtJar = VirtualJarFile.class.getResourceAsStream("/" + name);
|
||||
try {
|
||||
while ((length = rtJar.read(tmp)) >= 0) {
|
||||
out.write(tmp, 0, length);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return new byte[0];
|
||||
}
|
||||
byteArray = out.toByteArray();
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return byteArray.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(boolean asynchronous, boolean recursive, Runnable postRunnable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return VirtualJarFile.class.getResourceAsStream("/" + name);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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.vfs.newvfs.persistent;
|
||||
|
||||
public class PersistentFSConstants {
|
||||
public static final long FILE_LENGTH_TO_CACHE_THRESHOLD = 20 * 1024 * 1024; // 20 megabytes
|
||||
/**
|
||||
* always in range [0, PersistentFS.FILE_LENGTH_TO_CACHE_THRESHOLD]
|
||||
*/
|
||||
public static final int MAX_INTELLISENSE_FILESIZE = maxIntellisenseFileSize();
|
||||
// @NonNls private static final String MAX_INTELLISENSE_SIZE_PROPERTY = "idea.max.intellisense.filesize";
|
||||
|
||||
private PersistentFSConstants() {
|
||||
}
|
||||
|
||||
private static int maxIntellisenseFileSize() {
|
||||
final int maxLimitBytes = (int) FILE_LENGTH_TO_CACHE_THRESHOLD;
|
||||
final String userLimitKb = "100";
|
||||
// final String userLimitKb = System.getProperty(MAX_INTELLISENSE_SIZE_PROPERTY);
|
||||
try {
|
||||
return userLimitKb != null ? Math.min(Integer.parseInt(userLimitKb) * 1024, maxLimitBytes) : maxLimitBytes;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return maxLimitBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,535 +0,0 @@
|
||||
/*
|
||||
* 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.psi;
|
||||
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.LanguageParserDefinitions;
|
||||
import com.intellij.lang.ParserDefinition;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.undo.UndoConstants;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.file.exclude.ProjectFileExclusionManager;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
|
||||
import com.intellij.openapi.fileTypes.*;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import com.intellij.openapi.vfs.NonPhysicalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSConstants;
|
||||
import com.intellij.psi.impl.PsiFileEx;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.PsiManagerImpl;
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl;
|
||||
import com.intellij.psi.impl.file.PsiBinaryFileImpl;
|
||||
import com.intellij.psi.impl.source.PsiFileImpl;
|
||||
import com.intellij.psi.impl.source.PsiPlainTextFileImpl;
|
||||
import com.intellij.psi.impl.source.tree.FileElement;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import com.intellij.util.ReflectionCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class SingleRootFileViewProvider extends UserDataHolderBase implements FileViewProvider {
|
||||
private static final Key<Boolean> OUR_NO_SIZE_LIMIT_KEY = Key.create("no.size.limit");
|
||||
private static final Logger LOG = Logger.getInstance("#" + SingleRootFileViewProvider.class.getCanonicalName());
|
||||
private final PsiManager myManager;
|
||||
private final VirtualFile myVirtualFile;
|
||||
private final boolean myEventSystemEnabled;
|
||||
private final boolean myPhysical;
|
||||
private final AtomicReference<PsiFile> myPsiFile = new AtomicReference<PsiFile>();
|
||||
private volatile Content myContent;
|
||||
private volatile SoftReference<Document> myDocument;
|
||||
private final Language myBaseLanguage;
|
||||
private final ProjectFileExclusionManager myExclusionManager;
|
||||
|
||||
public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile file) {
|
||||
this(manager, file, true);
|
||||
}
|
||||
|
||||
public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean physical) {
|
||||
this(manager, virtualFile, physical, calcBaseLanguage(virtualFile, manager.getProject()));
|
||||
}
|
||||
|
||||
protected SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean physical, @NotNull Language language) {
|
||||
myManager = manager;
|
||||
myVirtualFile = virtualFile;
|
||||
myEventSystemEnabled = physical;
|
||||
myBaseLanguage = language;
|
||||
setContent(new VirtualFileContent());
|
||||
myPhysical = isEventSystemEnabled() &&
|
||||
!(virtualFile instanceof LightVirtualFile) &&
|
||||
!(virtualFile.getFileSystem() instanceof NonPhysicalFileSystem);
|
||||
myExclusionManager = ProjectFileExclusionManager.SERVICE.getInstance(manager.getProject());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Language getBaseLanguage() {
|
||||
return myBaseLanguage;
|
||||
}
|
||||
|
||||
private static Language calcBaseLanguage(@NotNull VirtualFile file, @NotNull Project project) {
|
||||
if (file instanceof LightVirtualFile) {
|
||||
final Language language = ((LightVirtualFile) file).getLanguage();
|
||||
if (language != null) {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
FileType fileType = file.getFileType();
|
||||
// Do not load content
|
||||
if (fileType == UnknownFileType.INSTANCE) {
|
||||
fileType = FileTypeRegistry.getInstance().detectFileTypeFromContent(file);
|
||||
}
|
||||
if (fileType.isBinary()) return Language.ANY;
|
||||
if (isTooLarge(file)) return PlainTextLanguage.INSTANCE;
|
||||
|
||||
if (fileType instanceof LanguageFileType) {
|
||||
return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType) fileType).getLanguage(), file, project);
|
||||
}
|
||||
|
||||
final ContentBasedFileSubstitutor[] processors = Extensions.getExtensions(ContentBasedFileSubstitutor.EP_NAME);
|
||||
for (ContentBasedFileSubstitutor processor : processors) {
|
||||
Language language = processor.obtainLanguageForFile(file);
|
||||
if (language != null) return language;
|
||||
}
|
||||
|
||||
return PlainTextLanguage.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Set<Language> getLanguages() {
|
||||
return Collections.singleton(getBaseLanguage());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final PsiFile getPsi(@NotNull Language target) {
|
||||
/*if (!isPhysical()) {
|
||||
((PsiManagerEx) myManager).getFileManager().setViewProvider(getVirtualFile(), this);
|
||||
}*/
|
||||
return getPsiInner(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<PsiFile> getAllFiles() {
|
||||
return Collections.singletonList(getPsi(getBaseLanguage()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected PsiFile getPsiInner(final Language target) {
|
||||
if (target != getBaseLanguage()) {
|
||||
return null;
|
||||
}
|
||||
PsiFile psiFile = myPsiFile.get();
|
||||
if (psiFile == null) {
|
||||
psiFile = createFile();
|
||||
boolean set = myPsiFile.compareAndSet(null, psiFile);
|
||||
if (!set) {
|
||||
psiFile = myPsiFile.get();
|
||||
}
|
||||
}
|
||||
return psiFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeContentsSynchronized() {
|
||||
unsetPsiContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contentsSynchronized() {
|
||||
unsetPsiContent();
|
||||
}
|
||||
|
||||
private void unsetPsiContent() {
|
||||
if (!(myContent instanceof PsiFileContent)) return;
|
||||
final Document cachedDocument = getCachedDocument();
|
||||
setContent(cachedDocument == null ? new VirtualFileContent() : new DocumentContent());
|
||||
}
|
||||
|
||||
public void beforeDocumentChanged() {
|
||||
final PsiFileImpl psiFile = (PsiFileImpl) getCachedPsi(getBaseLanguage());
|
||||
if (psiFile != null && psiFile.isContentsLoaded() && getContent() instanceof DocumentContent) {
|
||||
setContent(new PsiFileContent(psiFile, getModificationStamp()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rootChanged(PsiFile psiFile) {
|
||||
if (((PsiFileEx) psiFile).isContentsLoaded()) {
|
||||
setContent(new PsiFileContent((PsiFileImpl) psiFile, LocalTimeCounter.currentTime()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEventSystemEnabled() {
|
||||
return myEventSystemEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPhysical() {
|
||||
return myPhysical;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getModificationStamp() {
|
||||
return getContent().getModificationStamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsIncrementalReparse(final Language rootLanguage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public PsiFile getCachedPsi(Language target) {
|
||||
return myPsiFile.get();
|
||||
}
|
||||
|
||||
public FileElement[] getKnownTreeRoots() {
|
||||
PsiFile psiFile = myPsiFile.get();
|
||||
if (psiFile == null || !(psiFile instanceof PsiFileImpl)) return new FileElement[0];
|
||||
if (((PsiFileImpl) psiFile).getTreeElement() == null) return new FileElement[0];
|
||||
return new FileElement[]{(FileElement) psiFile.getNode()};
|
||||
}
|
||||
|
||||
private PsiFile createFile() {
|
||||
try {
|
||||
final VirtualFile vFile = getVirtualFile();
|
||||
if (vFile.isDirectory()) return null;
|
||||
if (isIgnored()) return null;
|
||||
|
||||
final Project project = myManager.getProject();
|
||||
if (isPhysical()) { // check directories consistency
|
||||
final VirtualFile parent = vFile.getParent();
|
||||
if (parent == null) return null;
|
||||
final PsiDirectory psiDir = getManager().findDirectory(parent);
|
||||
if (psiDir == null) return null;
|
||||
}
|
||||
|
||||
FileType fileType = vFile.getFileType();
|
||||
PsiFile file = null;
|
||||
if (fileType.isBinary() || vFile.isSpecialFile()) {
|
||||
//TODO check why ClsFileImpl doesn't created automatically with create method
|
||||
file = new ClsFileImpl((PsiManagerImpl) getManager(), this);
|
||||
//file = new PsiBinaryFileImpl((PsiManagerImpl) getManager(), this);
|
||||
} else {
|
||||
if (!isTooLarge(vFile)) {
|
||||
final PsiFile psiFile = createFile(getBaseLanguage());
|
||||
if (psiFile != null) file = psiFile;
|
||||
} else {
|
||||
file = new PsiPlainTextFileImpl(this);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
} catch (ProcessCanceledException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
LOG.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isIgnored() {
|
||||
final VirtualFile file = getVirtualFile();
|
||||
if (file instanceof LightVirtualFile) return false;
|
||||
if (myExclusionManager != null && myExclusionManager.isExcluded(file)) return true;
|
||||
return FileTypeRegistry.getInstance().isFileIgnored(file);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected PsiFile createFile(@NotNull Project project, @NotNull VirtualFile file, @NotNull FileType fileType) {
|
||||
if (fileType.isBinary() || file.isSpecialFile()) {
|
||||
return new PsiBinaryFileImpl((PsiManagerImpl) getManager(), this);
|
||||
}
|
||||
if (!isTooLarge(file)) {
|
||||
final PsiFile psiFile = createFile(getBaseLanguage());
|
||||
if (psiFile != null) return psiFile;
|
||||
}
|
||||
|
||||
return new PsiPlainTextFileImpl(this);
|
||||
}
|
||||
|
||||
public static boolean isTooLarge(@NotNull VirtualFile vFile) {
|
||||
if (!checkFileSizeLimit(vFile)) return false;
|
||||
return fileSizeIsGreaterThan(vFile, PersistentFSConstants.MAX_INTELLISENSE_FILESIZE);
|
||||
}
|
||||
|
||||
private static boolean checkFileSizeLimit(@NotNull VirtualFile vFile) {
|
||||
return !Boolean.TRUE.equals(vFile.getUserData(OUR_NO_SIZE_LIMIT_KEY));
|
||||
}
|
||||
|
||||
public static void doNotCheckFileSizeLimit(@NotNull VirtualFile vFile) {
|
||||
vFile.putUserData(OUR_NO_SIZE_LIMIT_KEY, Boolean.TRUE);
|
||||
}
|
||||
|
||||
public static boolean isTooLarge(@NotNull VirtualFile vFile, final long contentSize) {
|
||||
if (!checkFileSizeLimit(vFile)) return false;
|
||||
return contentSize > PersistentFSConstants.MAX_INTELLISENSE_FILESIZE;
|
||||
}
|
||||
|
||||
private static boolean fileSizeIsGreaterThan(@NotNull VirtualFile vFile, final long maxBytes) {
|
||||
if (vFile instanceof LightVirtualFile) {
|
||||
// This is optimization in order to avoid conversion of [large] file contents to bytes
|
||||
final int lengthInChars = ((LightVirtualFile) vFile).getContent().length();
|
||||
if (lengthInChars < maxBytes / 2) return false;
|
||||
if (lengthInChars > maxBytes) return true;
|
||||
}
|
||||
|
||||
return vFile.getLength() > maxBytes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected PsiFile createFile(Language lang) {
|
||||
if (lang != getBaseLanguage()) return null;
|
||||
final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
|
||||
if (parserDefinition != null) {
|
||||
return parserDefinition.createFile(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiManager getManager() {
|
||||
return myManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CharSequence getContents() {
|
||||
return getContent().getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public VirtualFile getVirtualFile() {
|
||||
return myVirtualFile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Document getCachedDocument() {
|
||||
final Document document = myDocument != null ? myDocument.get() : null;
|
||||
if (document != null) return document;
|
||||
return FileDocumentManager.getInstance().getCachedDocument(getVirtualFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getDocument() {
|
||||
Document document = myDocument != null ? myDocument.get() : null;
|
||||
if (document == null/* TODO[ik] make this change && isEventSystemEnabled()*/) {
|
||||
document = FileDocumentManager.getInstance().getDocument(getVirtualFile());
|
||||
myDocument = new SoftReference<Document>(document);
|
||||
}
|
||||
if (document != null && getContent() instanceof VirtualFileContent) {
|
||||
setContent(new DocumentContent());
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileViewProvider clone() {
|
||||
final VirtualFile origFile = getVirtualFile();
|
||||
LightVirtualFile copy = new LightVirtualFile(origFile.getName(), origFile.getFileType(), getContents(), origFile.getCharset(), getModificationStamp());
|
||||
copy.setOriginalFile(origFile);
|
||||
copy.putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE);
|
||||
copy.setCharset(origFile.getCharset());
|
||||
return createCopy(copy);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SingleRootFileViewProvider createCopy(final VirtualFile copy) {
|
||||
return new SingleRootFileViewProvider(getManager(), copy, false, myBaseLanguage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiReference findReferenceAt(final int offset) {
|
||||
final PsiFileImpl psiFile = (PsiFileImpl) getPsi(getBaseLanguage());
|
||||
return findReferenceAt(psiFile, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement findElementAt(final int offset, final Language language) {
|
||||
final PsiFile psiFile = getPsi(language);
|
||||
return psiFile != null ? findElementAt(psiFile, offset) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiReference findReferenceAt(final int offset, @NotNull final Language language) {
|
||||
final PsiFile psiFile = getPsi(language);
|
||||
return psiFile != null ? findReferenceAt(psiFile, offset) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiReference findReferenceAt(final PsiFile psiFile, final int offset) {
|
||||
if (psiFile == null) return null;
|
||||
int offsetInElement = offset;
|
||||
PsiElement child = psiFile.getFirstChild();
|
||||
while (child != null) {
|
||||
final int length = child.getTextLength();
|
||||
if (length <= offsetInElement) {
|
||||
offsetInElement -= length;
|
||||
child = child.getNextSibling();
|
||||
continue;
|
||||
}
|
||||
return child.findReferenceAt(offsetInElement);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement findElementAt(final int offset) {
|
||||
return findElementAt(getPsi(getBaseLanguage()), offset);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PsiElement findElementAt(int offset, Class<? extends Language> lang) {
|
||||
if (!ReflectionCache.isAssignable(lang, getBaseLanguage().getClass())) return null;
|
||||
return findElementAt(offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static PsiElement findElementAt(final PsiElement psiFile, final int offset) {
|
||||
if (psiFile == null) return null;
|
||||
int offsetInElement = offset;
|
||||
PsiElement child = psiFile.getFirstChild();
|
||||
while (child != null) {
|
||||
final int length = child.getTextLength();
|
||||
if (length <= offsetInElement) {
|
||||
offsetInElement -= length;
|
||||
child = child.getNextSibling();
|
||||
continue;
|
||||
}
|
||||
return child.findElementAt(offsetInElement);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void forceCachedPsi(final PsiFile psiFile) {
|
||||
myPsiFile.set(psiFile);
|
||||
((PsiManagerEx) myManager).getFileManager().setViewProvider(getVirtualFile(), this);
|
||||
}
|
||||
|
||||
private Content getContent() {
|
||||
return myContent;
|
||||
}
|
||||
|
||||
private void setContent(final Content content) {
|
||||
myContent = content;
|
||||
}
|
||||
|
||||
private interface Content {
|
||||
CharSequence getText();
|
||||
|
||||
long getModificationStamp();
|
||||
}
|
||||
|
||||
private class VirtualFileContent implements Content {
|
||||
@Override
|
||||
public CharSequence getText() {
|
||||
final VirtualFile virtualFile = getVirtualFile();
|
||||
if (virtualFile instanceof LightVirtualFile) {
|
||||
Document doc = getCachedDocument();
|
||||
if (doc != null) return doc.getCharsSequence();
|
||||
return ((LightVirtualFile) virtualFile).getContent();
|
||||
}
|
||||
|
||||
final Document document = getDocument();
|
||||
if (document == null) {
|
||||
return LoadTextUtil.loadText(virtualFile);
|
||||
} else {
|
||||
return document.getCharsSequence();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getModificationStamp() {
|
||||
return getVirtualFile().getModificationStamp();
|
||||
}
|
||||
}
|
||||
|
||||
private class DocumentContent implements Content {
|
||||
@Override
|
||||
public CharSequence getText() {
|
||||
final Document document = getDocument();
|
||||
assert document != null;
|
||||
return document.getCharsSequence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getModificationStamp() {
|
||||
Document document = myDocument == null ? null : myDocument.get();
|
||||
if (document != null) return document.getModificationStamp();
|
||||
return myVirtualFile.getModificationStamp();
|
||||
}
|
||||
}
|
||||
|
||||
private class PsiFileContent implements Content {
|
||||
private final PsiFileImpl myFile;
|
||||
private CharSequence myContent = null;
|
||||
private final long myModificationStamp;
|
||||
|
||||
private PsiFileContent(final PsiFileImpl file, final long modificationStamp) {
|
||||
myFile = file;
|
||||
myModificationStamp = modificationStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getText() {
|
||||
if (!myFile.isContentsLoaded()) {
|
||||
unsetPsiContent();
|
||||
return getContents();
|
||||
}
|
||||
if (myContent != null) return myContent;
|
||||
return myContent = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence>() {
|
||||
public CharSequence compute() {
|
||||
return myFile.calcTreeElement().getText();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getModificationStamp() {
|
||||
if (!myFile.isContentsLoaded()) {
|
||||
unsetPsiContent();
|
||||
return SingleRootFileViewProvider.this.getModificationStamp();
|
||||
}
|
||||
return myModificationStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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.psi.text;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.UserDataHolder;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.impl.source.text.DiffLog;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class BlockSupport {
|
||||
public static BlockSupport getInstance(Project project) {
|
||||
return ServiceManager.getService(project, BlockSupport.class);
|
||||
}
|
||||
|
||||
public abstract void reparseRange(PsiFile file, int startOffset, int endOffset, @NonNls CharSequence newText) throws IncorrectOperationException;
|
||||
|
||||
@NotNull
|
||||
public abstract DiffLog reparseRange(@NotNull PsiFile file,
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
int lengthShift,
|
||||
@NotNull CharSequence newText,
|
||||
@NotNull ProgressIndicator progressIndicator) throws IncorrectOperationException;
|
||||
|
||||
public static final Key<Boolean> DO_NOT_REPARSE_INCREMENTALLY = Key.create("DO_NOT_REPARSE_INCREMENTALLY");
|
||||
public static final Key<ASTNode> TREE_TO_BE_REPARSED = Key.create("TREE_TO_BE_REPARSED");
|
||||
|
||||
public static class ReparsedSuccessfullyException extends RuntimeException {
|
||||
private final DiffLog myDiffLog;
|
||||
|
||||
public ReparsedSuccessfullyException(@NotNull DiffLog diffLog) {
|
||||
myDiffLog = diffLog;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DiffLog getDiffLog() {
|
||||
return myDiffLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// maximal tree depth for which incremental reparse is allowed
|
||||
// if tree is deeper then it will be replaced completely - to avoid SOEs
|
||||
// public static final int INCREMENTAL_REPARSE_DEPTH_LIMIT = Registry.intValue("psi.incremental.reparse.depth.limit", 1000);
|
||||
public static final int INCREMENTAL_REPARSE_DEPTH_LIMIT = 1000;
|
||||
|
||||
public static final Key<Boolean> TREE_DEPTH_LIMIT_EXCEEDED = Key.create("TREE_IS_TOO_DEEP");
|
||||
|
||||
public static boolean isTooDeep(final UserDataHolder element) {
|
||||
return element != null && Boolean.TRUE.equals(element.getUserData(TREE_DEPTH_LIMIT_EXCEEDED));
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ReflectionUtil {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.util.ReflectionUtil");
|
||||
|
||||
private ReflectionUtil() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Type resolveVariable(TypeVariable variable, final Class classType) {
|
||||
return resolveVariable(variable, classType, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Type resolveVariable(TypeVariable variable, final Class classType, boolean resolveInInterfacesOnly) {
|
||||
final Class aClass = getRawType(classType);
|
||||
int index = ArrayUtil.find(ReflectionCache.getTypeParameters(aClass), variable);
|
||||
if (index >= 0) {
|
||||
return variable;
|
||||
}
|
||||
|
||||
final Class[] classes = ReflectionCache.getInterfaces(aClass);
|
||||
final Type[] genericInterfaces = ReflectionCache.getGenericInterfaces(aClass);
|
||||
for (int i = 0; i <= classes.length; i++) {
|
||||
Class anInterface;
|
||||
if (i < classes.length) {
|
||||
anInterface = classes[i];
|
||||
} else {
|
||||
anInterface = ReflectionCache.getSuperClass(aClass);
|
||||
if (resolveInInterfacesOnly || anInterface == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final Type resolved = resolveVariable(variable, anInterface);
|
||||
if (resolved instanceof Class || resolved instanceof ParameterizedType) {
|
||||
return resolved;
|
||||
}
|
||||
if (resolved instanceof TypeVariable) {
|
||||
final TypeVariable typeVariable = (TypeVariable) resolved;
|
||||
index = ArrayUtil.find(ReflectionCache.getTypeParameters(anInterface), typeVariable);
|
||||
if (index < 0) {
|
||||
LOG.error("Cannot resolve type variable:\n" + "typeVariable = " + typeVariable + "\n" + "genericDeclaration = " +
|
||||
declarationToString(typeVariable.getGenericDeclaration()) + "\n" + "searching in " + declarationToString(anInterface));
|
||||
}
|
||||
final Type type = i < genericInterfaces.length ? genericInterfaces[i] : aClass.getGenericSuperclass();
|
||||
if (type instanceof Class) {
|
||||
return Object.class;
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
return getActualTypeArguments(((ParameterizedType) type))[index];
|
||||
}
|
||||
throw new AssertionError("Invalid type: " + type);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String declarationToString(final GenericDeclaration anInterface) {
|
||||
return anInterface.toString()
|
||||
+ Arrays.asList(anInterface.getTypeParameters())
|
||||
+ " loaded by " + ((Class) anInterface).getClassLoader();
|
||||
}
|
||||
|
||||
public static Class<?> getRawType(Type type) {
|
||||
if (type instanceof Class) {
|
||||
return (Class) type;
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
return getRawType(((ParameterizedType) type).getRawType());
|
||||
}
|
||||
if (type instanceof GenericArrayType) {
|
||||
//todo[peter] don't create new instance each time
|
||||
return Array.newInstance(getRawType(((GenericArrayType) type).getGenericComponentType()), 0).getClass();
|
||||
}
|
||||
assert false : type;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type[] getActualTypeArguments(final ParameterizedType parameterizedType) {
|
||||
return ReflectionCache.getActualTypeArguments(parameterizedType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Class<?> substituteGenericType(final Type genericType, final Type classType) {
|
||||
if (genericType instanceof TypeVariable) {
|
||||
final Class<?> aClass = getRawType(classType);
|
||||
final Type type = resolveVariable((TypeVariable) genericType, aClass);
|
||||
if (type instanceof Class) {
|
||||
return (Class) type;
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
return (Class<?>) ((ParameterizedType) type).getRawType();
|
||||
}
|
||||
if (type instanceof TypeVariable && classType instanceof ParameterizedType) {
|
||||
final int index = ArrayUtil.find(ReflectionCache.getTypeParameters(aClass), type);
|
||||
if (index >= 0) {
|
||||
return getRawType(getActualTypeArguments(((ParameterizedType) classType))[index]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return getRawType(genericType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArrayList<Field> collectFields(Class clazz) {
|
||||
ArrayList<Field> result = new ArrayList<Field>();
|
||||
collectFields(clazz, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Field findField(Class clazz, @Nullable Class type, String name) throws NoSuchFieldException {
|
||||
final ArrayList<Field> fields = collectFields(clazz);
|
||||
for (Field each : fields) {
|
||||
if (name.equals(each.getName()) && (type == null || each.getType().equals(type))) return each;
|
||||
}
|
||||
|
||||
throw new NoSuchFieldException("Class: " + clazz + " name: " + name + " type: " + type);
|
||||
}
|
||||
|
||||
public static Field findAssignableField(Class clazz, Class type, String name) throws NoSuchFieldException {
|
||||
final ArrayList<Field> fields = collectFields(clazz);
|
||||
for (Field each : fields) {
|
||||
if (name.equals(each.getName()) && type.isAssignableFrom(each.getType())) return each;
|
||||
}
|
||||
|
||||
throw new NoSuchFieldException("Class: " + clazz + " name: " + name + " type: " + type);
|
||||
}
|
||||
|
||||
private static void collectFields(final Class clazz, final ArrayList<Field> result) {
|
||||
final Field[] fields = clazz.getDeclaredFields();
|
||||
result.addAll(Arrays.asList(fields));
|
||||
final Class superClass = clazz.getSuperclass();
|
||||
if (superClass != null) {
|
||||
collectFields(superClass, result);
|
||||
}
|
||||
final Class[] interfaces = clazz.getInterfaces();
|
||||
for (Class each : interfaces) {
|
||||
collectFields(each, result);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetField(Class clazz, Class type, String name) {
|
||||
try {
|
||||
resetField(null, findField(clazz, type, name));
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetField(Object object, Class type, String name) {
|
||||
try {
|
||||
resetField(object, findField(object.getClass(), type, name));
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetField(Object object, String name) {
|
||||
try {
|
||||
resetField(object, findField(object.getClass(), null, name));
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetField(@Nullable final Object object, final Field field) {
|
||||
// field.setAccessible(true);
|
||||
Class<?> type = field.getType();
|
||||
try {
|
||||
if (type.isPrimitive()) {
|
||||
if (boolean.class.equals(type)) {
|
||||
field.set(object, Boolean.FALSE);
|
||||
} else if (int.class.equals(type)) {
|
||||
field.set(object, new Integer(0));
|
||||
} else if (double.class.equals(type)) {
|
||||
field.set(object, new Double(0));
|
||||
} else if (float.class.equals(type)) {
|
||||
field.set(object, new Float(0));
|
||||
}
|
||||
} else {
|
||||
field.set(object, null);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method findMethod(Method[] methods, @NonNls @NotNull String name, Class... parameters) {
|
||||
for (final Method method : methods) {
|
||||
if (name.equals(method.getName()) && Arrays.equals(parameters, method.getParameterTypes())) return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getMethod(@NotNull Class aClass, @NonNls @NotNull String name, Class... parameters) {
|
||||
return findMethod(ReflectionCache.getMethods(aClass), name, parameters);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getDeclaredMethod(@NotNull Class aClass, @NonNls @NotNull String name, Class... parameters) {
|
||||
return findMethod(aClass.getDeclaredMethods(), name, parameters);
|
||||
}
|
||||
|
||||
public static Object getField(Class objectClass, Object object, Class type, @NonNls String name) {
|
||||
try {
|
||||
final Field field = findAssignableField(objectClass, type, name);
|
||||
// field.setAccessible(true);
|
||||
return field.get(object);
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.debug(e);
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.debug(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Type resolveVariableInHierarchy(final TypeVariable variable, final Class aClass) {
|
||||
Type type;
|
||||
Class current = aClass;
|
||||
while ((type = resolveVariable(variable, current, false)) == null) {
|
||||
current = ReflectionCache.getSuperClass(current);
|
||||
if (current == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (type instanceof TypeVariable) {
|
||||
return resolveVariableInHierarchy((TypeVariable) type, aClass);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> Constructor<T> getDefaultConstructor(final Class<T> aClass) {
|
||||
try {
|
||||
final Constructor<T> constructor = aClass.getConstructor();
|
||||
// constructor.setAccessible(true);
|
||||
return constructor;
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOG.error("No default constructor in " + aClass, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> T createInstance(final Constructor<T> constructor, final Object... args) {
|
||||
try {
|
||||
return constructor.newInstance(args);
|
||||
} catch (InstantiationException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import junit.framework.Test;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.JetLiteFixture;
|
||||
|
||||
|
||||
public final class ExampleTestSuite extends JetLiteFixture {
|
||||
private String name;
|
||||
|
||||
public ExampleTestSuite(@NonNls String dataPath, String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "test" + name;
|
||||
}
|
||||
|
||||
public void runTest() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static Test suite() {
|
||||
return JetTestCaseBuilder.suiteForDirectory(JetTestCaseBuilder.getTestDataPathBase(),
|
||||
"/checkerWithErrorTypes/quick", true, new JetTestCaseBuilder.NamedTestFactory() {
|
||||
@NotNull
|
||||
@Override
|
||||
public Test createTest(@NotNull String dataPath, @NotNull String name) {
|
||||
return (new ExampleTestSuite(dataPath, name));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user