fixes in module structure.
add workaround code for applet. add method needed for applet.
This commit is contained in:
Generated
+2
@@ -13,6 +13,8 @@
|
|||||||
<element id="extracted-dir" path="$PROJECT_DIR$/translator/lib/junit-4.10.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/translator/lib/junit-4.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/translator/lib/guava-10.0.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/translator/lib/guava-10.0.1.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/../../Kotlin/jet/lib/asm-util-3.3.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/../../Kotlin/jet/lib/asm-util-3.3.1.jar" path-in-jar="/" />
|
||||||
|
<element id="module-output" name="idea_fix" />
|
||||||
|
<element id="file-copy" path="E:/rt.jar" />
|
||||||
</root>
|
</root>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
Generated
+1
@@ -7,6 +7,7 @@
|
|||||||
<module fileurl="file://$PROJECT_DIR$/../Kotlin/jet/compiler/frontend/frontend.iml" filepath="$PROJECT_DIR$/../Kotlin/jet/compiler/frontend/frontend.iml" />
|
<module fileurl="file://$PROJECT_DIR$/../Kotlin/jet/compiler/frontend/frontend.iml" filepath="$PROJECT_DIR$/../Kotlin/jet/compiler/frontend/frontend.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/../../Kotlin/jet/compiler/frontend.java/frontend.java.iml" filepath="$PROJECT_DIR$/../../Kotlin/jet/compiler/frontend.java/frontend.java.iml" />
|
<module fileurl="file://$PROJECT_DIR$/../../Kotlin/jet/compiler/frontend.java/frontend.java.iml" filepath="$PROJECT_DIR$/../../Kotlin/jet/compiler/frontend.java/frontend.java.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/../Kotlin/jet/compiler/frontend.java/frontend.java.iml" filepath="$PROJECT_DIR$/../Kotlin/jet/compiler/frontend.java/frontend.java.iml" />
|
<module fileurl="file://$PROJECT_DIR$/../Kotlin/jet/compiler/frontend.java/frontend.java.iml" filepath="$PROJECT_DIR$/../Kotlin/jet/compiler/frontend.java/frontend.java.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/idea_fix/idea_fix.iml" filepath="$PROJECT_DIR$/idea_fix/idea_fix.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/examples/jetTests.iml" filepath="$PROJECT_DIR$/examples/jetTests.iml" />
|
<module fileurl="file://$PROJECT_DIR$/examples/jetTests.iml" filepath="$PROJECT_DIR$/examples/jetTests.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/js/js.iml" filepath="$PROJECT_DIR$/js/js.iml" />
|
<module fileurl="file://$PROJECT_DIR$/js/js.iml" filepath="$PROJECT_DIR$/js/js.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/../../Kotlin/jet/stdlib/stdlib.iml" filepath="$PROJECT_DIR$/../../Kotlin/jet/stdlib/stdlib.iml" />
|
<module fileurl="file://$PROJECT_DIR$/../../Kotlin/jet/stdlib/stdlib.iml" filepath="$PROJECT_DIR$/../../Kotlin/jet/stdlib/stdlib.iml" />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="IDEA IC-110.408" jdkType="IDEA JDK" />
|
<orderEntry type="jdk" jdkName="IDEA IU-112.121" jdkType="IDEA JDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="module" module-name="translator" />
|
<orderEntry type="module" module-name="translator" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* 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
@@ -0,0 +1,668 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2009 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.intellij.openapi.fileEditor.impl;
|
||||||
|
|
||||||
|
import com.intellij.lang.properties.charset.Native2AsciiCharset;
|
||||||
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
|
import com.intellij.openapi.fileTypes.BinaryFileDecompiler;
|
||||||
|
import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers;
|
||||||
|
import com.intellij.openapi.fileTypes.FileType;
|
||||||
|
import com.intellij.openapi.fileTypes.LanguageFileType;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.util.Key;
|
||||||
|
import com.intellij.openapi.util.Pair;
|
||||||
|
import com.intellij.openapi.util.Trinity;
|
||||||
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
|
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
import com.intellij.openapi.vfs.encoding.EncodingRegistry;
|
||||||
|
import com.intellij.testFramework.LightVirtualFile;
|
||||||
|
import com.intellij.util.ArrayUtil;
|
||||||
|
import com.intellij.util.text.CharArrayUtil;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
|
|
||||||
|
public final class LoadTextUtil {
|
||||||
|
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.LoadTextUtil");
|
||||||
|
private static final Key<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2011 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.intellij.openapi.util;
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
|
import com.intellij.reference.SoftReference;
|
||||||
|
import com.intellij.util.ImageLoader;
|
||||||
|
import com.intellij.util.containers.ConcurrentHashMap;
|
||||||
|
import com.intellij.util.containers.WeakHashMap;
|
||||||
|
import com.intellij.util.ui.UIUtil;
|
||||||
|
import org.jetbrains.annotations.NonNls;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
//import sun.reflect.Reflection;
|
||||||
|
|
||||||
|
public final class IconLoader {
|
||||||
|
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
|
||||||
|
|
||||||
|
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||||
|
private static final ConcurrentHashMap<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2009 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.intellij.openapi.util;
|
||||||
|
|
||||||
|
|
||||||
|
//import com.intellij.util.concurrency.AtomicFieldUpdater;
|
||||||
|
|
||||||
|
import com.intellij.util.containers.StripedLockConcurrentHashMap;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.TestOnly;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
|
||||||
|
private static final Key<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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,535 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="1.6" jdkType="JavaSDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="lib" level="project" />
|
<orderEntry type="library" name="libs" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.intellij.psi.impl.PsiFileFactoryImpl;
|
|||||||
import com.intellij.testFramework.LightVirtualFile;
|
import com.intellij.testFramework.LightVirtualFile;
|
||||||
import org.jetbrains.annotations.NonNls;
|
import org.jetbrains.annotations.NonNls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.JetCoreEnvironment;
|
import org.jetbrains.jet.JetCoreEnvironment;
|
||||||
import org.jetbrains.jet.compiler.CompileEnvironment;
|
import org.jetbrains.jet.compiler.CompileEnvironment;
|
||||||
import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowDataTraceFactory;
|
import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowDataTraceFactory;
|
||||||
@@ -20,56 +21,74 @@ import org.jetbrains.jet.lang.resolve.BindingContext;
|
|||||||
import org.jetbrains.jet.plugin.JetLanguage;
|
import org.jetbrains.jet.plugin.JetLanguage;
|
||||||
import org.jetbrains.k2js.generate.CodeGenerator;
|
import org.jetbrains.k2js.generate.CodeGenerator;
|
||||||
import org.jetbrains.k2js.translate.general.Translation;
|
import org.jetbrains.k2js.translate.general.Translation;
|
||||||
import org.jetbrains.k2js.utils.JetTestUtils;
|
import org.jetbrains.k2js.utils.GenerationUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import static org.jetbrains.k2js.translate.utils.BindingUtils.getNamespaceDescriptor;
|
||||||
|
import static org.jetbrains.k2js.translate.utils.DescriptorUtils.nameForNamespace;
|
||||||
|
import static org.jetbrains.k2js.utils.JetTestUtils.analyzeNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Talanov Pavel
|
* @author Talanov Pavel
|
||||||
*/
|
*/
|
||||||
public final class K2JSTranslator {
|
public final class K2JSTranslator {
|
||||||
|
|
||||||
private final JetCoreEnvironment myEnvironment = new JetCoreEnvironment(new Disposable() {
|
@NotNull
|
||||||
|
private final JetCoreEnvironment environment = new JetCoreEnvironment(new Disposable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BindingContext bindingContext = null;
|
||||||
|
|
||||||
public K2JSTranslator() {
|
public K2JSTranslator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void translateFile(@NotNull String inputFile, @NotNull String outputFile) throws Exception {
|
public void translateFile(@NotNull String inputFile, @NotNull String outputFile) throws Exception {
|
||||||
|
|
||||||
JetFile PsiFile = loadPsiFile(inputFile);
|
JetFile PsiFile = loadPsiFile(inputFile);
|
||||||
|
includeRtJar();
|
||||||
JsProgram program = generateProgram(PsiFile);
|
JsProgram program = generateProgram(PsiFile);
|
||||||
|
|
||||||
CodeGenerator generator = new CodeGenerator();
|
CodeGenerator generator = new CodeGenerator();
|
||||||
generator.generateToFile(program, new File(outputFile));
|
generator.generateToFile(program, new File(outputFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String translateString(@NotNull String programText) {
|
public String translateStringWithCallToMain(@NotNull String programText, @NotNull String argumentsString) {
|
||||||
JetFile PsiFile = createPsiFile("test", programText);
|
JetFile file = createPsiFile("test", programText);
|
||||||
JsProgram program = generateProgram(PsiFile);
|
String programCode = generateProgramCode(file);
|
||||||
|
String callToMain = generateCallToMain(file, argumentsString);
|
||||||
|
return programCode + callToMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateProgramCode(JetFile psiFile) {
|
||||||
|
JsProgram program = generateProgram(psiFile);
|
||||||
CodeGenerator generator = new CodeGenerator();
|
CodeGenerator generator = new CodeGenerator();
|
||||||
return generator.generateToString(program);
|
return generator.generateToString(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private JsProgram generateProgram(@NotNull JetFile psiFile) {
|
private JsProgram generateProgram(@NotNull JetFile psiFile) {
|
||||||
final File rtJar = CompileEnvironment.findRtJar(true);
|
|
||||||
myEnvironment.addToClasspath(rtJar);
|
|
||||||
|
|
||||||
JetNamespace namespace = psiFile.getRootNamespace();
|
JetNamespace namespace = psiFile.getRootNamespace();
|
||||||
|
|
||||||
BindingContext bindingContext = JetTestUtils.analyzeNamespace(namespace,
|
bindingContext = analyzeNamespace(namespace,
|
||||||
JetControlFlowDataTraceFactory.EMPTY);
|
JetControlFlowDataTraceFactory.EMPTY);
|
||||||
|
assert bindingContext != null;
|
||||||
|
|
||||||
return Translation.generateAst(bindingContext, namespace, myEnvironment.getProject());
|
return Translation.generateAst(bindingContext, namespace, environment.getProject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void includeRtJar() {
|
||||||
|
final File rtJar = CompileEnvironment.findRtJar(true);
|
||||||
|
environment.addToClasspath(rtJar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -102,7 +121,34 @@ public final class K2JSTranslator {
|
|||||||
protected PsiFile createFile(@NonNls String name, String text) {
|
protected PsiFile createFile(@NonNls String name, String text) {
|
||||||
LightVirtualFile virtualFile = new LightVirtualFile(name, JetLanguage.INSTANCE, text);
|
LightVirtualFile virtualFile = new LightVirtualFile(name, JetLanguage.INSTANCE, text);
|
||||||
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
|
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
|
||||||
return ((PsiFileFactoryImpl) PsiFileFactory.getInstance(myEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
|
return ((PsiFileFactoryImpl) PsiFileFactory.getInstance(environment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String generateCallToMain(@NotNull JetFile file, String argumentString) {
|
||||||
|
String namespaceName = getRootNamespaceName(file);
|
||||||
|
|
||||||
|
List<String> arguments = parseString(argumentString);
|
||||||
|
return GenerationUtils.generateCallToMain(namespaceName, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private List<String> parseString(@NotNull String argumentString) {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
StringTokenizer stringTokenizer = new StringTokenizer(argumentString);
|
||||||
|
while (stringTokenizer.hasMoreTokens()) {
|
||||||
|
result.add(stringTokenizer.nextToken());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: make "anonymous" a constant
|
||||||
|
@NotNull
|
||||||
|
private String getRootNamespaceName(@NotNull JetFile psiFile) {
|
||||||
|
JetNamespace namespace = psiFile.getRootNamespace();
|
||||||
|
assert bindingContext != null;
|
||||||
|
return nameForNamespace(getNamespaceDescriptor(bindingContext, namespace));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import java.applet.Applet;
|
|||||||
public final class K2JSTranslatorApplet extends Applet {
|
public final class K2JSTranslatorApplet extends Applet {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String translate(@NotNull String code) {
|
public String translate(@NotNull String code, @NotNull String arguments) {
|
||||||
String generatedCode = (new K2JSTranslator()).translateString(code);
|
String generatedCode = (new K2JSTranslator()).translateStringWithCallToMain(code, arguments);
|
||||||
System.out.println("GENERATED JAVASCRIPT CODE:\n-----------------------------------\n");
|
System.out.println("GENERATED JAVASCRIPT CODE:\n-----------------------------------\n");
|
||||||
System.out.println(generatedCode);
|
System.out.println(generatedCode);
|
||||||
return generatedCode;
|
return generatedCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.jetbrains.jet.lang.descriptors.*;
|
import org.jetbrains.jet.lang.descriptors.*;
|
||||||
|
|
||||||
import static org.jetbrains.k2js.translate.utils.DescriptorUtils.getOwnDeclarations;
|
import static org.jetbrains.k2js.translate.utils.DescriptorUtils.getOwnDeclarations;
|
||||||
|
import static org.jetbrains.k2js.translate.utils.DescriptorUtils.nameForNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Talanov Pavel
|
* @author Talanov Pavel
|
||||||
@@ -123,15 +124,6 @@ public final class DeclarationVisitor extends DeclarationDescriptorVisitor<Void,
|
|||||||
return context.innerDeclaration(namespaceScope, namespaceName);
|
return context.innerDeclaration(namespaceScope, namespaceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String nameForNamespace(@NotNull NamespaceDescriptor descriptor) {
|
|
||||||
String name = descriptor.getName();
|
|
||||||
if (name.equals("")) {
|
|
||||||
return "Anonymous";
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void declareMembers(@NotNull NamespaceDescriptor descriptor, @NotNull DeclarationContext context) {
|
private void declareMembers(@NotNull NamespaceDescriptor descriptor, @NotNull DeclarationContext context) {
|
||||||
for (DeclarationDescriptor memberDescriptor :
|
for (DeclarationDescriptor memberDescriptor :
|
||||||
descriptor.getMemberScope().getAllDescriptors()) {
|
descriptor.getMemberScope().getAllDescriptors()) {
|
||||||
|
|||||||
@@ -121,4 +121,13 @@ public final class DescriptorUtils {
|
|||||||
public static boolean isExtensionFunction(@NotNull FunctionDescriptor functionDescriptor) {
|
public static boolean isExtensionFunction(@NotNull FunctionDescriptor functionDescriptor) {
|
||||||
return (functionDescriptor.getReceiverParameter().exists());
|
return (functionDescriptor.getReceiverParameter().exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static String nameForNamespace(@NotNull NamespaceDescriptor descriptor) {
|
||||||
|
String name = descriptor.getName();
|
||||||
|
if (name.equals("")) {
|
||||||
|
return "Anonymous";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.jetbrains.k2js.utils;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Talanov Pavel
|
||||||
|
*/
|
||||||
|
public final class GenerationUtils {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static String generateCallToMain(@NotNull String namespaceName, @NotNull List<String> arguments) {
|
||||||
|
String constructArguments = "var args = Kotlin.array(" + arguments.size() + ");\n";
|
||||||
|
int index = 0;
|
||||||
|
for (String argument : arguments) {
|
||||||
|
constructArguments = constructArguments + "args.set(" + index + ", \"" + argument + "\");\n";
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
String callMain = namespaceName + ".main(args);\n";
|
||||||
|
return constructArguments + callMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,6 @@ public final class AppletTest extends TranslationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simpleTest() throws Exception {
|
public void simpleTest() throws Exception {
|
||||||
System.out.println((new K2JSTranslatorApplet()).translate("fun main(args : Array<String>) {}"));
|
(new K2JSTranslatorApplet()).translate("fun main(args : Array<String>) {}", " a 3 1 2134");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.jetbrains.k2js.test;
|
package org.jetbrains.k2js.test;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.k2js.utils.GenerationUtils;
|
||||||
import org.mozilla.javascript.Context;
|
import org.mozilla.javascript.Context;
|
||||||
import org.mozilla.javascript.Scriptable;
|
import org.mozilla.javascript.Scriptable;
|
||||||
|
|
||||||
@@ -36,18 +37,7 @@ public final class RhinoSystemOutputChecker implements RhinoResultChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void runMain(Context context, Scriptable scope) {
|
private void runMain(Context context, Scriptable scope) {
|
||||||
context.evaluateString(scope, execMain(), "function call", 0, null);
|
String callToMain = GenerationUtils.generateCallToMain("Anonymous", arguments);
|
||||||
}
|
context.evaluateString(scope, callToMain, "function call", 0, null);
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String execMain() {
|
|
||||||
String constructArguments = "var args = Kotlin.array(" + arguments.size() + ");\n";
|
|
||||||
int index = 0;
|
|
||||||
for (String argument : arguments) {
|
|
||||||
constructArguments = constructArguments + "args.set(" + index + ", \"" + argument + "\");\n";
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
String callMain = "Anonymous.main(args);\n";
|
|
||||||
return constructArguments + callMain;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function $A(iterable) {
|
|||||||
extend(Object, {
|
extend(Object, {
|
||||||
extend:extend,
|
extend:extend,
|
||||||
keys:Object.keys || keys,
|
keys:Object.keys || keys,
|
||||||
values:values,
|
values:values
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user