From c0b5e501cb102d7c3abb9a6ad1117d95b59db284 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 4 Dec 2014 19:30:22 +0300 Subject: [PATCH 1/7] [cvs] IDEA-129253 escape from write action --- .../checkout/CvsCheckoutProvider.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/checkout/CvsCheckoutProvider.java b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/checkout/CvsCheckoutProvider.java index edd9502914b7..11fc82386d7b 100644 --- a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/checkout/CvsCheckoutProvider.java +++ b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/checkout/CvsCheckoutProvider.java @@ -25,6 +25,7 @@ import com.intellij.cvsSupport2.cvsExecution.ModalityContext; import com.intellij.cvsSupport2.cvshandlers.CommandCvsHandler; import com.intellij.cvsSupport2.cvshandlers.CvsHandler; import com.intellij.cvsSupport2.ui.experts.checkout.CheckoutWizard; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vcs.CheckoutProvider; @@ -74,14 +75,19 @@ public class CvsCheckoutProvider implements CheckoutProvider { public void refreshAfterCheckout(final Listener listener, final CvsElement[] selectedElements, final File checkoutDirectory, final boolean useAlternateCheckoutPath) { + VirtualFileManager.getInstance().asyncRefresh(new Runnable() { public void run() { - // shouldn't hold write action when calling this (IDEADEV-20086) - for (CvsElement element : selectedElements) { - final File path = useAlternateCheckoutPath ? checkoutDirectory : new File(checkoutDirectory, element.getCheckoutPath()); - listener.directoryCheckedOut(path, CvsVcs2.getKey()); - } - listener.checkoutCompleted(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + for (CvsElement element : selectedElements) { + final File path = useAlternateCheckoutPath ? checkoutDirectory : new File(checkoutDirectory, element.getCheckoutPath()); + listener.directoryCheckedOut(path, CvsVcs2.getKey()); + } + listener.checkoutCompleted(); + } + }); } }); } From e231941e2a2fae4c2292c80aa23771b8b5909f67 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 4 Dec 2014 17:54:08 +0100 Subject: [PATCH 2/7] don't leave (open) files after test finishes --- java/java-tests/testSrc/com/intellij/index/IndexTest.groovy | 3 +-- java/java-tests/testSrc/com/intellij/index/StringIndex.java | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/index/IndexTest.groovy b/java/java-tests/testSrc/com/intellij/index/IndexTest.groovy index 9f4de6950d72..bb5b4a248273 100644 --- a/java/java-tests/testSrc/com/intellij/index/IndexTest.groovy +++ b/java/java-tests/testSrc/com/intellij/index/IndexTest.groovy @@ -109,8 +109,7 @@ public class IndexTest extends JavaCodeInsightFixtureTestCase { assertDataEquals(index.getFilesByWord("h")); } finally { - indexStorage.close(); - FileUtil.delete(storageFile); + index.dispose() } } diff --git a/java/java-tests/testSrc/com/intellij/index/StringIndex.java b/java/java-tests/testSrc/com/intellij/index/StringIndex.java index 0b5d48dcfa91..851a389679b8 100644 --- a/java/java-tests/testSrc/com/intellij/index/StringIndex.java +++ b/java/java-tests/testSrc/com/intellij/index/StringIndex.java @@ -50,6 +50,10 @@ public class StringIndex { public void update(final String path, @Nullable String content, @Nullable String oldContent) throws StorageException { myIndex.update(path.hashCode(), toInput(path, content)).compute(); } + + public void dispose() { + myIndex.dispose(); + } @Nullable private PathContentPair toInput(@NotNull String path, @Nullable String content) { From 9b813bb5779762a5149b8165ee3c5da7bc9b5136 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 4 Dec 2014 17:55:45 +0100 Subject: [PATCH 3/7] use single file descriptor for reading / writing data for PersistentHashMap --- .../io/PersistentHashMapValueStorage.java | 99 ++++++++- .../util/io/RandomAccessFileWrapper.java | 188 ++++++++++++++++++ 2 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 platform/util/src/com/intellij/util/io/RandomAccessFileWrapper.java diff --git a/platform/util/src/com/intellij/util/io/PersistentHashMapValueStorage.java b/platform/util/src/com/intellij/util/io/PersistentHashMapValueStorage.java index 1c94078f62f6..07d91a34332b 100644 --- a/platform/util/src/com/intellij/util/io/PersistentHashMapValueStorage.java +++ b/platform/util/src/com/intellij/util/io/PersistentHashMapValueStorage.java @@ -21,6 +21,7 @@ package com.intellij.util.io; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.ByteSequence; +import com.intellij.util.SystemProperties; import com.intellij.util.containers.SLRUCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,14 +43,41 @@ public class PersistentHashMapValueStorage { private static final int CACHE_PROTECTED_QUEUE_SIZE = 10; private static final int CACHE_PROBATIONAL_QUEUE_SIZE = 20; + // cache size is twice larger than constants because (when used) it replaces two caches + private static final FileAccessorCache ourRandomAccessFileCache = new FileAccessorCache( + 2*CACHE_PROTECTED_QUEUE_SIZE, 2*CACHE_PROBATIONAL_QUEUE_SIZE) { + @Override + @NotNull + public CacheValue createValue(final String path) { + try { + return new CacheValue(new RandomAccessFileWrapper(path)) { + @Override + protected void disposeAccessor(RandomAccessFileWrapper accessor) { + try { + accessor.close(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + }; + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + + private static final boolean useSingleFileDescriptor = SystemProperties.getBooleanProperty("idea.use.single.file.descriptor.for.persistent.hash.map", true); + private static final FileAccessorCache ourAppendersCache = new FileAccessorCache(CACHE_PROTECTED_QUEUE_SIZE, CACHE_PROBATIONAL_QUEUE_SIZE) { @Override @NotNull public CacheValue createValue(String path) { try { - return new CachedAppender(new DataOutputStream(new BufferedOutputStream(new FileOutputStream(path, true)))); + OutputStream out = useSingleFileDescriptor ? new OutputStreamOverRandomAccessFileCache(path):new FileOutputStream(path, true); + return new CachedAppender(new DataOutputStream(new BufferedOutputStream(out))); } - catch (FileNotFoundException e) { + catch (IOException e) { throw new RuntimeException(e); } } @@ -59,7 +87,8 @@ public class PersistentHashMapValueStorage { @Override @NotNull public CacheValue createValue(String path) { - return new CachedReader(new FileReader(new File(path))); + RAReader reader = useSingleFileDescriptor ? new ReaderOverRandomAccessFileCache(path) : new FileReader(new File(path)); + return new CachedReader(reader); } }; @@ -70,6 +99,7 @@ public class PersistentHashMapValueStorage { if (mySize == 0) { appendBytes(new ByteSequence("Header Record For PersistentHashMapValueStorage".getBytes()), 0); + // avoid corruption issue when disk fails to write first record synchronously, code depends on correct value of mySize (IDEA-106306) CacheValue streamCacheValue = ourAppendersCache.getIfCached(myPath); if (streamCacheValue != null) { @@ -85,8 +115,10 @@ public class PersistentHashMapValueStorage { } long currentLength = myFile.length(); - if (currentLength != mySize) Logger.getInstance(getClass().getName()).info("Avoided PSHM corruption due to write failure"); - mySize = currentLength; // volatile write + if (currentLength > mySize) { // if real file length (unexpectedly) increases + Logger.getInstance(getClass().getName()).info("Avoided PSHM corruption due to write failure"); + mySize = currentLength; // volatile write + } } } @@ -400,6 +432,8 @@ public class PersistentHashMapValueStorage { ourReadersCache.remove(myPath); ourAppendersCache.remove(myPath); + ourRandomAccessFileCache.remove(myPath); + if (myCompactionModeReader != null) { myCompactionModeReader.dispose(); myCompactionModeReader = null; @@ -408,6 +442,8 @@ public class PersistentHashMapValueStorage { public void switchToCompactionMode() { ourReadersCache.remove(myPath); + + ourRandomAccessFileCache.remove(myPath); // in compaction mode use faster reader myCompactionModeReader = new FileReader(myFile); myCompactionMode = true; @@ -422,6 +458,31 @@ public class PersistentHashMapValueStorage { void dispose(); } + private static class ReaderOverRandomAccessFileCache implements RAReader { + private String myPath; + + private ReaderOverRandomAccessFileCache(String path) { + myPath = path; + } + + @Override + public void get(final long addr, final byte[] dst, final int off, final int len) throws IOException { + CacheValue fileAccessor = ourRandomAccessFileCache.get(myPath); + + try { + RandomAccessFileWrapper file = fileAccessor.get(); + file.seek(addr); + file.read(dst, off, len); + } finally { + fileAccessor.release(); + } + } + + @Override + public void dispose() { + } + } + private static class FileReader implements RAReader { private final RandomAccessFile myFile; @@ -542,4 +603,32 @@ public class PersistentHashMapValueStorage { protected abstract void disposeAccessor(T accesor); } + + private static class OutputStreamOverRandomAccessFileCache extends OutputStream { + private final String myPath; + + public OutputStreamOverRandomAccessFileCache(String path) throws IOException { + myPath = path; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + CacheValue fileAccessor = ourRandomAccessFileCache.get(myPath); + RandomAccessFileWrapper file = fileAccessor.get(); + + try { + file.seek(file.length()); + file.write(b, off, len); + } + finally { + fileAccessor.release(); + } + } + + @Override + public void write(int b) throws IOException { + byte[] r = {(byte)(b & 0xFF)}; + write(r); + } + } } diff --git a/platform/util/src/com/intellij/util/io/RandomAccessFileWrapper.java b/platform/util/src/com/intellij/util/io/RandomAccessFileWrapper.java new file mode 100644 index 000000000000..75932eacb105 --- /dev/null +++ b/platform/util/src/com/intellij/util/io/RandomAccessFileWrapper.java @@ -0,0 +1,188 @@ +/* + * Copyright 2000-2014 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.io; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.SystemProperties; + +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * Replacement of RandomAccessFile("rw") with shadow file pointer / size, valid when file manipulations happen in with this class only. + * Note that sharing policy is the same as RandomAccessFile + */ +class RandomAccessFileWrapper extends RandomAccessFile { + private static final Logger LOG = Logger.getInstance(RandomAccessFileWrapper.class.getName()); + private static final boolean doAssertions = SystemProperties.getBooleanProperty("idea.do.random.access.wrapper.assertions", false); + + private final String myPath; + private volatile long mySize; + private volatile long myPointer; + + public RandomAccessFileWrapper(String name) throws IOException { + super(name, "rw"); + mySize = super.length(); + myPath = name; + + if (LOG.isDebugEnabled()) { + LOG.debug("Inst:" + this + "," + Thread.currentThread() + "," + getClass().getClassLoader()); + } + } + + @Override + public void seek(long pos) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Seek:" + this + "," + Thread.currentThread() + "," + pos + "," + myPointer + "," + mySize); + } + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + if (myPointer == pos) { + return; + } + super.seek(pos); + myPointer = pos; + } + + @Override + public long length() throws IOException { + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + return mySize; + } + + @Override + public void write(int b) throws IOException { + write(new byte[]{ (byte)(b & 0xFF)}); + } + + private void checkSizeAndPointerAssertions() throws IOException { + if (myPointer != super.getFilePointer()) { + assert false; + } + if (mySize != super.length()) { + assert false; + } + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("write:" + this + "," + Thread.currentThread() + "," + len + "," + myPointer + "," + mySize); + } + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + + long pointer = myPointer; + super.write(b, off, len); + + if (pointer == 0) { // first write can introduce extra bytes, reload the position to avoid position tracking problem, e.g. IDEA-106306 + pointer = super.getFilePointer(); + } else { + pointer += len; + } + myPointer = pointer; + mySize = Math.max(pointer, mySize); + if (LOG.isDebugEnabled()) { + LOG.debug("after write:" + this + "," + Thread.currentThread() + "," + myPointer + "," + mySize ); + } + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + } + + @Override + public void setLength(long newLength) throws IOException { + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + super.setLength(newLength); + mySize = newLength; + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("read:" + this + "," + Thread.currentThread() + "," + len + "," + myPointer ); + } + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + int read = super.read(b, off, len); + if (read != -1) myPointer += read; + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + return read; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read() throws IOException { + int read = super.read(); + ++myPointer; + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + return read; + } + + @Override + public long getFilePointer() throws IOException { + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + + return myPointer; + } + + @Override + public int skipBytes(int n) throws IOException { + int i = super.skipBytes(n); + if (doAssertions) { + checkSizeAndPointerAssertions(); + } + return i; + } + + @Override + public void close() throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Closed:" + this + "," + Thread.currentThread() ); + } + super.close(); + } + + @Override + public String toString() { + return myPath + "@" + Integer.toHexString(hashCode()); + } +} From 5e634c44d3202641fadaf09e1b270f56bcad87fe Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Wed, 3 Dec 2014 13:21:10 +0200 Subject: [PATCH 4/7] AbstractJavaBlock: method renamed --- .../com/intellij/psi/formatter/java/AbstractJavaBlock.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java index 62e47570e221..2ede7a9bbffe 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java @@ -16,8 +16,6 @@ package com.intellij.psi.formatter.java; import com.intellij.formatting.*; -import com.intellij.formatting.alignment.AlignmentInColumnsConfig; -import com.intellij.formatting.alignment.AlignmentInColumnsHelper; import com.intellij.formatting.alignment.AlignmentStrategy; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; @@ -38,7 +36,6 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.impl.source.tree.java.ClassElement; import com.intellij.psi.jsp.JspElementType; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.TokenSet; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; @@ -389,13 +386,13 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo @Nullable protected Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, @NotNull ASTNode child) { - if (preferSlaveAlignment(child)) { + if (isTernaryOperatorToken(child)) { return alignment2; } return alignment; } - private boolean preferSlaveAlignment(@NotNull final ASTNode child) { + private boolean isTernaryOperatorToken(@NotNull final ASTNode child) { final IElementType nodeType = myNode.getElementType(); if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { From e3be03110f2dfd2dfa4f01800d4959143c2c876c Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Wed, 3 Dec 2014 13:58:44 +0200 Subject: [PATCH 5/7] AbstractJavaBlock: method moved to SimpleJavaBlock --- .../psi/formatter/java/AbstractJavaBlock.java | 11 +---------- .../intellij/psi/formatter/java/SimpleJavaBlock.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java index 2ede7a9bbffe..f97eabe69e0b 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java @@ -375,15 +375,6 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo return null; } - @Nullable - protected Alignment createChildAlignment2(@Nullable Alignment base) { - final IElementType nodeType = myNode.getElementType(); - if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { - return base == null ? createAlignment(mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null) : createAlignment(base, mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null); - } - return null; - } - @Nullable protected Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, @NotNull ASTNode child) { if (isTernaryOperatorToken(child)) { @@ -772,7 +763,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo } @Nullable - private static Alignment createAlignment(Alignment base, final boolean alignOption, @Nullable final Alignment defaultAlignment) { + public static Alignment createAlignment(Alignment base, final boolean alignOption, @Nullable final Alignment defaultAlignment) { return alignOption ? createAlignmentOrDefault(base, defaultAlignment) : defaultAlignment; } diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java index cadcd51a7ec0..55bc787ccdda 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java @@ -29,6 +29,7 @@ import com.intellij.psi.impl.source.tree.StdTokenSets; import com.intellij.psi.tree.IElementType; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -71,6 +72,15 @@ public class SimpleJavaBlock extends AbstractJavaBlock { return result; } + @Nullable + protected Alignment createChildAlignment2(@Nullable Alignment base) { + final IElementType nodeType = myNode.getElementType(); + if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { + return base == null ? createAlignment(mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null) : createAlignment(base, mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null); + } + return null; + } + private void processRemainingChildren(List result, Wrap childWrap) { while (myCurrentChild != null) { if (isNotEmptyNode(myCurrentChild)) { From 2a2b009535a49380ecc3c8078f934ae0fdf386ee Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Wed, 3 Dec 2014 15:19:08 +0200 Subject: [PATCH 6/7] SimpleJavaBlock: removed unnecessary strategy calculation - it will be overwritten in AbstractJavaBlock#processChild after retrieving alignment from AbstractJavaBlock#arrangeChildWrap and wrapping it into strategy. Field in column alignment will be retrived in arrangeChildWrap method by invoking getVariableDeclarationSubElementAlignment. --- .../com/intellij/psi/formatter/java/SimpleJavaBlock.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java index 55bc787ccdda..3733b156a671 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java @@ -85,7 +85,7 @@ public class SimpleJavaBlock extends AbstractJavaBlock { while (myCurrentChild != null) { if (isNotEmptyNode(myCurrentChild)) { final ASTNode astNode = myCurrentChild; - AlignmentStrategy alignmentStrategyToUse = getAlignmentStrategy(myCurrentChild); + AlignmentStrategy alignmentStrategyToUse = AlignmentStrategy.wrap(chooseAlignment(myReservedAlignment, myReservedAlignment2, myCurrentChild)); myCurrentChild = processChild(result, astNode, alignmentStrategyToUse, childWrap, myCurrentIndent, myCurrentOffset); if (astNode != myCurrentChild && myCurrentChild != null) { myCurrentOffset = myCurrentChild.getTextRange().getStartOffset(); @@ -124,12 +124,6 @@ public class SimpleJavaBlock extends AbstractJavaBlock { } } - private AlignmentStrategy getAlignmentStrategy(ASTNode child) { - return JavaElementType.FIELD == myNode.getElementType() - ? myAlignmentStrategy - : AlignmentStrategy.wrap(chooseAlignment(myReservedAlignment, myReservedAlignment2, child)); - } - private boolean isNotEmptyNode(@NotNull ASTNode child) { return !FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0; } From 0b767366cb114794aed23326d9739997d686f1f2 Mon Sep 17 00:00:00 2001 From: Andrey Starovoyt Date: Thu, 4 Dec 2014 21:15:05 +0300 Subject: [PATCH 7/7] postfix templates incorrect expand IDEA-133867 --- .../templates/assert/incompleteExpression.java | 9 +++++++++ .../assert/incompleteExpression_after.java | 9 +++++++++ .../assert/simpleWithSemicolon_after.java | 4 ++-- .../templates/if/incompleteExpression.java | 9 +++++++++ .../templates/if/incompleteExpression_after.java | 9 +++++++++ .../templates/if/simpleWithSemicolon_after.java | 6 ++---- .../templates/return/incompleteConstructor.java | 5 +++++ .../return/incompleteConstructor_after.java | 5 +++++ .../templates/return/incompleteExpression.java | 9 +++++++++ .../return/incompleteExpressionWithParam.java | 9 +++++++++ .../incompleteExpressionWithParam_after.java | 9 +++++++++ .../return/incompleteExpression_after.java | 9 +++++++++ .../templates/return/incompleteParentheses.java | 9 +++++++++ .../return/incompleteParentheses_after.java | 9 +++++++++ .../templates/sout/incompleteExpression.java | 9 +++++++++ .../sout/incompleteExpression_after.java | 9 +++++++++ .../templates/throw/incompleteExpression.java | 11 +++++++++++ .../throw/incompleteExpression_after.java | 11 +++++++++++ .../throw/simpleWithSemicolon_after.java | 2 +- .../templates/while/incompleteExpression.java | 9 +++++++++ .../while/incompleteExpression_after.java | 9 +++++++++ .../AssertStatementPostfixTemplateTest.java | 4 ++++ .../IfStatementPostfixTemplateTest.java | 4 +++- .../templates/ReturnPostfixTemplateTest.java | 16 ++++++++++++++++ .../templates/SoutPostfixTemplateTest.java | 4 ++++ .../ThrowStatementPostfixTemplateTest.java | 3 +++ .../WhileStatementPostfixTemplateTest.java | 4 ++++ .../templates/TopmostExpressionSelector.java | 7 ++++++- 28 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression_after.java diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression.java new file mode 100644 index 000000000000..03557d19456d --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.assert + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression_after.java new file mode 100644 index 000000000000..1b106d58ca0a --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/incompleteExpression_after.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.assert + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/assert/simpleWithSemicolon_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/simpleWithSemicolon_after.java index 11e8bdefe6d0..281c2cb0e477 100644 --- a/java/java-tests/testData/codeInsight/template/postfix/templates/assert/simpleWithSemicolon_after.java +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/assert/simpleWithSemicolon_after.java @@ -1,8 +1,8 @@ public class Foo { void m() { - assert is(); + is();.assert } - + boolean is() { return false; } diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression.java new file mode 100644 index 000000000000..89e175b71528 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.if + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression_after.java new file mode 100644 index 000000000000..0bec0f546b37 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/if/incompleteExpression_after.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.if + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/if/simpleWithSemicolon_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/if/simpleWithSemicolon_after.java index a05e92eb1136..33fb6b67e64c 100644 --- a/java/java-tests/testData/codeInsight/template/postfix/templates/if/simpleWithSemicolon_after.java +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/if/simpleWithSemicolon_after.java @@ -1,10 +1,8 @@ public class Foo { void m() { - if (is()) { - - } + is();.if } - + boolean is() { return false; } diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor.java new file mode 100644 index 000000000000..819537abdd7a --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor.java @@ -0,0 +1,5 @@ +public class Foo { + Object m() { + new Object(.return + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor_after.java new file mode 100644 index 000000000000..74f1d9bdfd76 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteConstructor_after.java @@ -0,0 +1,5 @@ +public class Foo { + Object m() { + new Object(.return + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression.java new file mode 100644 index 000000000000..4fbf6c3b15b5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + methodCall(.return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam.java new file mode 100644 index 000000000000..19e39246319c --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + methodCall("string".return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam_after.java new file mode 100644 index 000000000000..b0a06731b9dc --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpressionWithParam_after.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + methodCall("string".return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression_after.java new file mode 100644 index 000000000000..ee7934353e77 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteExpression_after.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + methodCall(.return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses.java new file mode 100644 index 000000000000..6ac08982e0a9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + (methodCall("").return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses_after.java new file mode 100644 index 000000000000..c4fe06951b45 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/return/incompleteParentheses_after.java @@ -0,0 +1,9 @@ +public class Foo { + String m() { + (methodCall("").return + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression.java new file mode 100644 index 000000000000..3b7d8a7d2723 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.sout + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression_after.java new file mode 100644 index 000000000000..a89cde17a89c --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/sout/incompleteExpression_after.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.sout + } + + String methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression.java new file mode 100644 index 000000000000..ca1ebf528f80 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression.java @@ -0,0 +1,11 @@ +import java.lang.Exception; + +public class Foo { + void m() { + methodCall(.throw + } + + Exception methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression_after.java new file mode 100644 index 000000000000..fe40f7679dd6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/incompleteExpression_after.java @@ -0,0 +1,11 @@ +import java.lang.Exception; + +public class Foo { + void m() { + methodCall(.throw + } + + Exception methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simpleWithSemicolon_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simpleWithSemicolon_after.java index a0219e42ffc7..982aaa66f38e 100644 --- a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simpleWithSemicolon_after.java +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simpleWithSemicolon_after.java @@ -2,6 +2,6 @@ import java.lang.RuntimeException; public class Foo { void m() { - throw new RuntimeException("error"); + new RuntimeException("error");.throw } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression.java b/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression.java new file mode 100644 index 000000000000..d75b150d544f --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.while + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression_after.java new file mode 100644 index 000000000000..4927a0149e76 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/while/incompleteExpression_after.java @@ -0,0 +1,9 @@ +public class Foo { + void m() { + methodCall(.while + } + + boolean methodCall(String s) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/AssertStatementPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/AssertStatementPostfixTemplateTest.java index bad38639b907..3040a518f0bc 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/AssertStatementPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/AssertStatementPostfixTemplateTest.java @@ -71,5 +71,9 @@ public class AssertStatementPostfixTemplateTest extends PostfixTemplateTestCase public void testSimpleWithSemicolon() { doTest(); } + + public void testIncompleteExpression() { + doTest(); + } } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/IfStatementPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/IfStatementPostfixTemplateTest.java index 3c2c50a64120..5209a568b3ee 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/IfStatementPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/IfStatementPostfixTemplateTest.java @@ -69,5 +69,7 @@ public class IfStatementPostfixTemplateTest extends PostfixTemplateTestCase { doTest(); } - + public void testIncompleteExpression() { + doTest(); + } } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ReturnPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ReturnPostfixTemplateTest.java index 028ddaab611f..b975fd9f91a1 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ReturnPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ReturnPostfixTemplateTest.java @@ -38,4 +38,20 @@ public class ReturnPostfixTemplateTest extends PostfixTemplateTestCase { public void testComposite2() { doTest(); } + + public void testIncompleteExpression() { + doTest(); + } + + public void testIncompleteConstructor() { + doTest(); + } + + public void testIncompleteExpressionWithParam() { + doTest(); + } + + public void testIncompleteParentheses() { + doTest(); + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/SoutPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/SoutPostfixTemplateTest.java index 838a458bde1e..ddace5dbdeee 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/SoutPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/SoutPostfixTemplateTest.java @@ -31,4 +31,8 @@ public class SoutPostfixTemplateTest extends PostfixTemplateTestCase { public void testVoid() { doTest(); } + + public void testIncompleteExpression() { + doTest(); + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ThrowStatementPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ThrowStatementPostfixTemplateTest.java index c3981e4a137b..ce8cb10c985a 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ThrowStatementPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/ThrowStatementPostfixTemplateTest.java @@ -37,4 +37,7 @@ public class ThrowStatementPostfixTemplateTest extends PostfixTemplateTestCase { public void testSimpleWithSemicolon() { doTest(); } + public void testIncompleteExpression() { + doTest(); + } } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/WhileStatementPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/WhileStatementPostfixTemplateTest.java index 46878241e55b..79875f815689 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/WhileStatementPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/WhileStatementPostfixTemplateTest.java @@ -34,6 +34,10 @@ public class WhileStatementPostfixTemplateTest extends PostfixTemplateTestCase { doTest(); } + public void testIncompleteExpression() { + doTest(); + } + @NotNull @Override protected String getSuffix() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java index bd6028d2d8d2..3832ee3fd6b7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java @@ -19,6 +19,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; @@ -41,7 +42,11 @@ public class TopmostExpressionSelector implements PostfixTemplateExpressionSelec @NotNull Document copyDocument, int newOffset) { PsiElement topmostExpression = template.getPsiInfo().getTopmostExpression(context); - return topmostExpression != null && myCondition.value(topmostExpression); + + return topmostExpression != null && + topmostExpression.getTextRange().getEndOffset() == newOffset && + !PsiTreeUtil.hasErrorElements(topmostExpression) && + myCondition.value(topmostExpression); } @Override