diff --git a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java index f4bc20dbbd1f..81bf3fbec597 100644 --- a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java @@ -28,12 +28,13 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.tabs.impl.TabLabel; import com.intellij.util.Alarm; -import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Function; +import com.intellij.util.concurrency.BoundedTaskExecutor; import com.intellij.util.containers.TransferToEDTQueue; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.ide.PooledThreadExecutor; import javax.swing.*; import javax.swing.plaf.TreeUI; @@ -41,7 +42,7 @@ import javax.swing.plaf.basic.BasicTreeUI; import java.awt.*; import java.util.LinkedHashSet; import java.util.Set; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.Executor; public class DeferredIconImpl implements DeferredIcon { private static final int MIN_AUTO_UPDATE_MILLIS = 950; @@ -58,10 +59,13 @@ public class DeferredIconImpl implements DeferredIcon { private long myLastCalcTime; private long myLastTimeSpent; - private static final ThreadPoolExecutor ourIconsCalculatingExecutor = ConcurrencyUtil.newSingleThreadExecutor("Icons"); + private static final Executor ourIconsCalculatingExecutor = new BoundedTaskExecutor(PooledThreadExecutor.INSTANCE, 1); private final IconListener myEvalListener; private static final TransferToEDTQueue ourLaterInvocator = TransferToEDTQueue.createRunnableMerger("Deferred icon later invocator", 200); + private static class Holder { + private static final boolean CHECK_CONSISTENCY = ApplicationManager.getApplication().isUnitTestMode(); + } public DeferredIconImpl(Icon baseIcon, T param, @NotNull Function evaluator, @NotNull IconListener listener, boolean autoUpdatable) { this(baseIcon, param, true, evaluator, listener, autoUpdatable); @@ -99,7 +103,7 @@ public class DeferredIconImpl implements DeferredIcon { final Component target = getTarget(c); final Component paintingParent = SwingUtilities.getAncestorOfClass(PaintingParent.class, c); final Rectangle paintingParentRec = paintingParent == null ? null : ((PaintingParent)paintingParent).getChildRec(c); - ourIconsCalculatingExecutor.submit(new Runnable() { + ourIconsCalculatingExecutor.execute(new Runnable() { @Override public void run() { int oldWidth = myDelegateIcon.getIconWidth(); @@ -113,12 +117,7 @@ public class DeferredIconImpl implements DeferredIcon { IconDeferrerImpl.evaluateDeferred(new Runnable() { @Override public void run() { - try { - evaluated[0] = nonNull(myEvaluator.fun(myParam)); - } - catch (IndexNotReadyException e) { - evaluated[0] = EMPTY_ICON; - } + evaluated[0] = evaluate(); } }); if (myAutoUpdatable) { @@ -136,7 +135,7 @@ public class DeferredIconImpl implements DeferredIcon { IconDeferrerImpl.evaluateDeferred(new Runnable() { @Override public void run() { - evaluated[0] = nonNull(myEvaluator.fun(myParam)); + evaluated[0] = evaluate(); } }); if (myAutoUpdatable) { @@ -246,7 +245,9 @@ public class DeferredIconImpl implements DeferredIcon { result = EMPTY_ICON; } - checkDoesntReferenceThis(result); + if (Holder.CHECK_CONSISTENCY) { + checkDoesntReferenceThis(result); + } return result; } @@ -341,13 +342,19 @@ public class DeferredIconImpl implements DeferredIcon { void evalDone(DeferredIconImpl source, T key, @NotNull Icon result); } - public static boolean equalIcons(Icon icon1, Icon icon2) { + static boolean equalIcons(Icon icon1, Icon icon2) { if (icon1 instanceof DeferredIconImpl) { - icon1 = ((DeferredIconImpl)icon1).myDelegateIcon; + return ((DeferredIconImpl)icon1).isDeferredAndEqual(icon2); } if (icon2 instanceof DeferredIconImpl) { - icon2 = ((DeferredIconImpl)icon2).myDelegateIcon; + return ((DeferredIconImpl)icon2).isDeferredAndEqual(icon1); } return Comparing.equal(icon1, icon2); } + + private boolean isDeferredAndEqual(Icon icon) { + return icon instanceof DeferredIconImpl && + Comparing.equal(myParam, ((DeferredIconImpl)icon).myParam) && + equalIcons(myDelegateIcon, ((DeferredIconImpl)icon).myDelegateIcon); + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java new file mode 100644 index 000000000000..16f9ed46c69d --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java @@ -0,0 +1,246 @@ +/* + * Copyright 2000-2015 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.util.Iconable; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.ui.DeferredIcon; +import com.intellij.util.FileContentUtilCore; +import com.intellij.util.IconUtil; +import com.intellij.util.TimeoutUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.ImageObserver; +import java.io.IOException; +import java.text.AttributedCharacterIterator; + +public class IconUtilTest extends PlatformTestCase { + public void testIconDeferrerDoesNotDeferIconsAdInfinitum() throws IOException { + VirtualFile file = createTempFile("txt", null, "hkjh", CharsetToolkit.UTF8_CHARSET); + + Icon icon = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertTrue(icon instanceof DeferredIcon); + + Graphics g = new Graphics() { + @Override + public Graphics create() { + return this; + } + + @Override + public void translate(int x, int y) { + + } + + @Override + public Color getColor() { + return null; + } + + @Override + public void setColor(Color c) { + + } + + @Override + public void setPaintMode() { + + } + + @Override + public void setXORMode(Color c1) { + + } + + @Override + public Font getFont() { + return null; + } + + @Override + public void setFont(Font font) { + + } + + @Override + public FontMetrics getFontMetrics(Font f) { + return null; + } + + @Override + public Rectangle getClipBounds() { + return null; + } + + @Override + public void clipRect(int x, int y, int width, int height) { + + } + + @Override + public void setClip(int x, int y, int width, int height) { + + } + + @Override + public Shape getClip() { + return null; + } + + @Override + public void setClip(Shape clip) { + + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + + } + + @Override + public void fillRect(int x, int y, int width, int height) { + + } + + @Override + public void clearRect(int x, int y, int width, int height) { + + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + + } + + @Override + public void drawOval(int x, int y, int width, int height) { + + } + + @Override + public void fillOval(int x, int y, int width, int height) { + + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + + } + + @Override + public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + + } + + @Override + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void drawString(String str, int x, int y) { + + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, + int dx1, + int dy1, + int dx2, + int dy2, + int sx1, + int sy1, + int sx2, + int sy2, + Color bgcolor, + ImageObserver observer) { + return false; + } + + @Override + public void dispose() { + + } + }; + icon.paintIcon(new JLabel(), g, 0, 0); // force to eval + TimeoutUtil.sleep(1000); // give chance to evaluate + + Icon icon2 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertSame(icon, icon2); + + FileContentUtilCore.reparseFiles(file); + Icon icon3 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertNotSame(icon2, icon3); + } + + @Override + protected boolean isRunInWriteAction() { + return false; + } +}