replaced <code></code> with more concise {@code}
[idea/community.git] / platform / util / src / com / intellij / openapi / util / IconLoader.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.util;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.registry.Registry;
20 import com.intellij.openapi.util.registry.RegistryValue;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.reference.SoftReference;
23 import com.intellij.ui.RetrievableIcon;
24 import com.intellij.util.*;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.intellij.util.containers.WeakHashMap;
27 import com.intellij.util.ui.ImageUtil;
28 import com.intellij.util.ui.JBImageIcon;
29 import com.intellij.util.ui.JBUI;
30 import com.intellij.util.ui.JBUI.ScaleType;
31 import com.intellij.util.ui.UIUtil;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37 import java.awt.*;
38 import java.awt.image.BufferedImage;
39 import java.awt.image.ImageFilter;
40 import java.lang.ref.Reference;
41 import java.lang.reflect.Field;
42 import java.net.URL;
43 import java.util.*;
44 import java.util.List;
45 import java.util.concurrent.Callable;
46 import java.util.concurrent.ConcurrentMap;
47
48 public final class IconLoader {
49   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
50   @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
51   private static final ConcurrentMap<URL, CachedImageIcon> ourIconsCache = ContainerUtil.newConcurrentMap(100, 0.9f, 2);
52   /**
53    * This cache contains mapping between icons and disabled icons.
54    */
55   private static final Map<Icon, Icon> ourIcon2DisabledIcon = new WeakHashMap<Icon, Icon>(200);
56   @NonNls private static final List<IconPathPatcher> ourPatchers = new ArrayList<IconPathPatcher>(2);
57   public static boolean STRICT = false;
58
59   private static boolean USE_DARK_ICONS = UIUtil.isUnderDarcula();
60
61   private static ImageFilter IMAGE_FILTER;
62
63   static {
64     installPathPatcher(new DeprecatedDuplicatesIconPathPatcher());
65   }
66
67   private static final ImageIcon EMPTY_ICON = new ImageIcon(UIUtil.createImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)) {
68     @NonNls
69     public String toString() {
70       return "Empty icon " + super.toString();
71     }
72   };
73
74   private static boolean ourIsActivated = false;
75
76   private IconLoader() { }
77
78   public static void installPathPatcher(IconPathPatcher patcher) {
79     ourPatchers.add(patcher);
80     clearCache();
81   }
82
83   @Deprecated
84   public static Icon getIcon(@NotNull final Image image) {
85     return new JBImageIcon(image);
86   }
87
88   public static void setUseDarkIcons(boolean useDarkIcons) {
89     USE_DARK_ICONS = useDarkIcons;
90     clearCache();
91   }
92
93   public static void setFilter(ImageFilter filter) {
94     if (IMAGE_FILTER != filter) {
95       IMAGE_FILTER = filter;
96       clearCache();
97     }
98   }
99
100   private static void clearCache() {
101     ourIconsCache.clear();
102     ourIcon2DisabledIcon.clear();
103   }
104
105   //TODO[kb] support iconsets
106   //public static Icon getIcon(@NotNull final String path, @NotNull final String darkVariantPath) {
107   //  return new InvariantIcon(getIcon(path), getIcon(darkVariantPath));
108   //}
109
110   @NotNull
111   public static Icon getIcon(@NonNls @NotNull final String path) {
112     Class callerClass = ReflectionUtil.getGrandCallerClass();
113
114     assert callerClass != null : path;
115     return getIcon(path, callerClass);
116   }
117
118   @Nullable
119   private static Icon getReflectiveIcon(@NotNull String path, ClassLoader classLoader) {
120     try {
121       @NonNls String pckg = path.startsWith("AllIcons.") ? "com.intellij.icons." : "icons.";
122       Class cur = Class.forName(pckg + path.substring(0, path.lastIndexOf('.')).replace('.', '$'), true, classLoader);
123       Field field = cur.getField(path.substring(path.lastIndexOf('.') + 1));
124
125       return (Icon)field.get(null);
126     }
127     catch (Exception e) {
128       return null;
129     }
130   }
131
132   /**
133    * Might return null if icon was not found.
134    * Use only if you expected null return value, otherwise see {@link IconLoader#getIcon(String)}
135    */
136   @Nullable
137   public static Icon findIcon(@NonNls @NotNull String path) {
138     Class callerClass = ReflectionUtil.getGrandCallerClass();
139     if (callerClass == null) return null;
140     return findIcon(path, callerClass);
141   }
142
143   @NotNull
144   public static Icon getIcon(@NotNull String path, @NotNull final Class aClass) {
145     final Icon icon = findIcon(path, aClass);
146     if (icon == null) {
147       LOG.error("Icon cannot be found in '" + path + "', aClass='" + aClass + "'");
148     }
149     return icon;
150   }
151
152   public static void activate() {
153     ourIsActivated = true;
154   }
155
156   private static boolean isLoaderDisabled() {
157     return !ourIsActivated;
158   }
159
160   /**
161    * Might return null if icon was not found.
162    * Use only if you expected null return value, otherwise see {@link IconLoader#getIcon(String, Class)}
163    */
164   @Nullable
165   public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass) {
166     return findIcon(path, aClass, false);
167   }
168
169   @Nullable
170   public static Icon findIcon(@NotNull String path, @NotNull final Class aClass, boolean computeNow) {
171     return findIcon(path, aClass, computeNow, STRICT);
172   }
173
174   @Nullable
175   public static Icon findIcon(@NotNull String path, @NotNull Class aClass, boolean computeNow, boolean strict) {
176     String originalPath = path;
177     Pair<String, Class> patchedPath = patchPath(path);
178     path = patchedPath.first;
179     if (patchedPath.second != null) {
180       aClass = patchedPath.second;
181     }
182     if (isReflectivePath(path)) return getReflectiveIcon(path, aClass.getClassLoader());
183
184     URL myURL = aClass.getResource(path);
185     if (myURL == null) {
186       if (strict) throw new RuntimeException("Can't find icon in '" + path + "' near " + aClass);
187       return null;
188     }
189     final Icon icon = findIcon(myURL);
190     if (icon instanceof CachedImageIcon) {
191       ((CachedImageIcon)icon).myOriginalPath = originalPath;
192       ((CachedImageIcon)icon).myClassLoader = aClass.getClassLoader();
193     }
194     return icon;
195   }
196
197   @NotNull
198   private static Pair<String, Class> patchPath(@NotNull String path) {
199     for (IconPathPatcher patcher : ourPatchers) {
200       String newPath = patcher.patchPath(path);
201       if (newPath != null) {
202         return Pair.create(newPath, patcher.getContextClass(path));
203       }
204     }
205     return Pair.create(path, null);
206   }
207
208   private static boolean isReflectivePath(@NotNull String path) {
209     List<String> paths = StringUtil.split(path, ".");
210     return paths.size() > 1 && paths.get(0).endsWith("Icons");
211   }
212
213   @Nullable
214   public static Icon findIcon(URL url) {
215     return findIcon(url, true);
216   }
217
218   @Nullable
219   public static Icon findIcon(URL url, boolean useCache) {
220     if (url == null) {
221       return null;
222     }
223     CachedImageIcon icon = ourIconsCache.get(url);
224     if (icon == null) {
225       icon = new CachedImageIcon(url);
226       if (useCache) {
227         icon = ConcurrencyUtil.cacheOrGet(ourIconsCache, url, icon);
228       }
229     }
230     return icon;
231   }
232
233   @Nullable
234   public static Icon findIcon(@NotNull String path, @NotNull ClassLoader classLoader) {
235     String originalPath = path;
236     Pair<String, Class> patchedPath = patchPath(path);
237     path = patchedPath.first;
238     if (patchedPath.second != null) {
239       classLoader = patchedPath.second.getClassLoader();
240     }
241     if (isReflectivePath(path)) return getReflectiveIcon(path, classLoader);
242     if (!StringUtil.startsWithChar(path, '/')) return null;
243
244     final URL url = classLoader.getResource(path.substring(1));
245     final Icon icon = findIcon(url);
246     if (icon instanceof CachedImageIcon) {
247       ((CachedImageIcon)icon).myOriginalPath = originalPath;
248       ((CachedImageIcon)icon).myClassLoader = classLoader;
249     }
250     return icon;
251   }
252
253   @Nullable
254   public static Image toImage(@NotNull Icon icon) {
255     if (icon instanceof CachedImageIcon) {
256       icon = ((CachedImageIcon)icon).getRealIcon();
257     }
258     if (icon instanceof ImageIcon) {
259       return ((ImageIcon)icon).getImage();
260     }
261     else {
262       final int w = icon.getIconWidth();
263       final int h = icon.getIconHeight();
264       final BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment()
265         .getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(w, h, Transparency.TRANSLUCENT);
266       final Graphics2D g = image.createGraphics();
267       icon.paintIcon(null, g, 0, 0);
268       g.dispose();
269       return image;
270     }
271   }
272
273   @Nullable
274   private static ImageIcon checkIcon(final Image image, @NotNull URL url) {
275     if (image == null || image.getHeight(null) < 1) { // image wasn't loaded or broken
276       return null;
277     }
278
279     final Icon icon = getIcon(image);
280     if (!isGoodSize(icon)) {
281       LOG.error("Invalid icon: " + url); // # 22481
282       return EMPTY_ICON;
283     }
284     assert icon instanceof ImageIcon;
285     return (ImageIcon)icon;
286   }
287
288   public static boolean isGoodSize(@NotNull final Icon icon) {
289     return icon.getIconWidth() > 0 && icon.getIconHeight() > 0;
290   }
291
292   /**
293    * Gets (creates if necessary) disabled icon based on the passed one.
294    *
295    * @return {@code ImageIcon} constructed from disabled image of passed icon.
296    */
297   @Nullable
298   public static Icon getDisabledIcon(Icon icon) {
299     if (icon instanceof LazyIcon) icon = ((LazyIcon)icon).getOrComputeIcon();
300     if (icon == null) return null;
301
302     Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
303     if (disabledIcon == null) {
304       if (!isGoodSize(icon)) {
305         LOG.error(icon); // # 22481
306         return EMPTY_ICON;
307       }
308       if (icon instanceof CachedImageIcon) {
309         disabledIcon = ((CachedImageIcon)icon).asDisabledIcon();
310       } else {
311         final float scale = UIUtil.isJreHiDPI() ? JBUI.sysScale() : 1f;  // [tav] todo: no screen available
312         @SuppressWarnings("UndesirableClassUsage")
313         BufferedImage image = new BufferedImage((int)(scale * icon.getIconWidth()), (int)(scale * icon.getIconHeight()), BufferedImage.TYPE_INT_ARGB);
314         final Graphics2D graphics = image.createGraphics();
315
316         graphics.setColor(UIUtil.TRANSPARENT_COLOR);
317         graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
318         graphics.scale(scale, scale);
319         icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0);
320
321         graphics.dispose();
322
323         Image img = ImageUtil.filter(image, UIUtil.getGrayFilter());
324         if (UIUtil.isJreHiDPI()) img = RetinaImage.createFrom(img, scale, null);
325
326         disabledIcon = new JBImageIcon(img);
327       }
328       ourIcon2DisabledIcon.put(icon, disabledIcon);
329     }
330     return disabledIcon;
331   }
332
333   public static Icon getTransparentIcon(@NotNull final Icon icon) {
334     return getTransparentIcon(icon, 0.5f);
335   }
336
337   public static Icon getTransparentIcon(@NotNull final Icon icon, final float alpha) {
338     return new RetrievableIcon() {
339       @Nullable
340       @Override
341       public Icon retrieveIcon() {
342         return icon;
343       }
344
345       @Override
346       public int getIconHeight() {
347         return icon.getIconHeight();
348       }
349
350       @Override
351       public int getIconWidth() {
352         return icon.getIconWidth();
353       }
354
355       @Override
356       public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
357         final Graphics2D g2 = (Graphics2D)g;
358         final Composite saveComposite = g2.getComposite();
359         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
360         icon.paintIcon(c, g2, x, y);
361         g2.setComposite(saveComposite);
362       }
363     };
364   }
365
366   /**
367    * Gets a snapshot of the icon, immune to changes made by these calls:
368    * {@link IconLoader#setFilter(ImageFilter)}, {@link IconLoader#setUseDarkIcons(boolean)}
369    *
370    * @param icon the source icon
371    * @return the icon snapshot
372    */
373   @NotNull
374   public static Icon getIconSnapshot(@NotNull Icon icon) {
375     if (icon instanceof CachedImageIcon) {
376       return ((CachedImageIcon)icon).getRealIcon();
377     }
378     return icon;
379   }
380
381   private static final class CachedImageIcon extends JBUI.UpdatingJBIcon implements ScalableIcon {
382     private volatile Object myRealIcon;
383     private String myOriginalPath;
384     private ClassLoader myClassLoader;
385     @NotNull
386     private URL myUrl;
387     private volatile boolean dark;
388     private volatile int numberOfPatchers = ourPatchers.size();
389     private boolean svg;
390
391     private ImageFilter[] myFilters;
392     private final MyScaledIconsCache myScaledIconsCache = new MyScaledIconsCache();
393
394     private CachedImageIcon(@NotNull CachedImageIcon icon) {
395       myRealIcon = null; // to be computed
396       myOriginalPath = icon.myOriginalPath;
397       myClassLoader = icon.myClassLoader;
398       myUrl = icon.myUrl;
399       dark = icon.dark;
400       numberOfPatchers = icon.numberOfPatchers;
401       myFilters = icon.myFilters;
402       svg = myOriginalPath != null ? myOriginalPath.toLowerCase().endsWith("svg") : false;
403     }
404
405     public CachedImageIcon(@NotNull URL url) {
406       myUrl = url;
407       dark = USE_DARK_ICONS;
408       myFilters = new ImageFilter[] {IMAGE_FILTER};
409       svg = url.toString().endsWith("svg");
410     }
411
412     private void setGlobalFilter(ImageFilter globalFilter) {
413       myFilters[0] = globalFilter;
414     }
415
416     private ImageFilter getGlobalFilter() {
417       return myFilters[0];
418     }
419
420     @Override
421     public boolean updateJBUIScale(Graphics2D g) {
422       if (needUpdateJBUIScale(g)) {
423         getRealIcon(g); // force update
424         return true;
425       }
426       return false;
427     }
428
429     @NotNull
430     private synchronized ImageIcon getRealIcon() {
431       return getRealIcon(null);
432     }
433
434     @NotNull
435     private synchronized ImageIcon getRealIcon(@Nullable Graphics g) {
436       if (!isValid() || needUpdateJBUIScale((Graphics2D)g)) {
437         if (isLoaderDisabled()) return EMPTY_ICON;
438         myRealIcon = null;
439         dark = USE_DARK_ICONS;
440         super.updateJBUIScale((Graphics2D)g);
441         setGlobalFilter(IMAGE_FILTER);
442         if (!isValid()) myScaledIconsCache.clear();
443         if (numberOfPatchers != ourPatchers.size()) {
444           numberOfPatchers = ourPatchers.size();
445           Pair<String, Class> patchedPath = patchPath(myOriginalPath);
446           String path = myOriginalPath == null ? null : patchedPath.first;
447           if (patchedPath.second != null) {
448             myClassLoader = patchedPath.second.getClassLoader();
449           }
450           if (myClassLoader != null && path != null && path.startsWith("/")) {
451             path = path.substring(1);
452             final URL url = myClassLoader.getResource(path);
453             if (url != null) {
454               myUrl = url;
455             }
456           }
457         }
458       }
459       Object realIcon = myRealIcon;
460       if (realIcon instanceof Icon) return (ImageIcon)realIcon;
461
462       ImageIcon icon;
463       if (realIcon instanceof Reference) {
464         icon = ((Reference<ImageIcon>)realIcon).get();
465         if (icon != null) return icon;
466       }
467
468       icon = myScaledIconsCache.getOrLoadIcon(getJBUIScale(ScaleType.PIX));
469
470       if (icon != null) {
471         if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) {
472           realIcon = icon;
473         }
474         else {
475           realIcon = new SoftReference<ImageIcon>(icon);
476         }
477         myRealIcon = realIcon;
478       }
479
480       return icon == null ? EMPTY_ICON : icon;
481     }
482
483     private boolean isValid() {
484       return myRealIcon != null && dark == USE_DARK_ICONS && getGlobalFilter() == IMAGE_FILTER && numberOfPatchers == ourPatchers.size();
485     }
486
487     @Override
488     public void paintIcon(Component c, Graphics g, int x, int y) {
489       getRealIcon(g).paintIcon(c, g, x, y);
490     }
491
492     @Override
493     public int getIconWidth() {
494       return getRealIcon().getIconWidth();
495     }
496
497     @Override
498     public int getIconHeight() {
499       return getRealIcon().getIconHeight();
500     }
501
502     @Override
503     public String toString() {
504       return myUrl.toString();
505     }
506
507     @Override
508     public float getScale() {
509       return 1f;
510     }
511
512     @Override
513     public Icon scale(float scale) {
514       if (scale == 1f) return this;
515
516       getRealIcon(); // force state update & cache reset
517
518       Icon icon = myScaledIconsCache.getOrScaleIcon(getJBUIScale(ScaleType.PIX), scale);
519       if (icon != null) {
520         return icon;
521       }
522       return this;
523     }
524
525     private Icon asDisabledIcon() {
526       CachedImageIcon icon = new CachedImageIcon(this);
527       icon.myFilters = new ImageFilter[] {getGlobalFilter(), UIUtil.getGrayFilter()};
528       return icon;
529     }
530
531     private class MyScaledIconsCache {
532       // Map {false -> image}, {true -> image@2x}
533       private Map<Boolean, SoftReference<Image>> origImagesCache = Collections.synchronizedMap(new HashMap<Boolean, SoftReference<Image>>(2));
534
535       private static final int SCALED_ICONS_CACHE_LIMIT = 5;
536
537       // Map {pixel scale -> icon}
538       private Map<Float, SoftReference<ImageIcon>> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap<Float, SoftReference<ImageIcon>>(SCALED_ICONS_CACHE_LIMIT) {
539         @Override
540         public boolean removeEldestEntry(Map.Entry<Float, SoftReference<ImageIcon>> entry) {
541           return size() > SCALED_ICONS_CACHE_LIMIT;
542         }
543       });
544
545       /**
546        * Retrieves the orig image (1x, 2x) based on the pixScale.
547        */
548       private Image getOrLoadOrigImage(boolean needRetinaImage) {
549         Image image = SoftReference.dereference(origImagesCache.get(needRetinaImage));
550         if (image != null) return image;
551
552         image = ImageLoader.loadFromUrl(myUrl, false, myFilters, needRetinaImage ? 2f : 1f);
553         if (image == null) return null;
554         origImagesCache.put(needRetinaImage, new SoftReference<Image>(image));
555         return image;
556       }
557
558       /**
559        * Retrieves the orig icon based on the pixScale, then scale it by the instanceScale.
560        */
561       public ImageIcon getOrScaleIcon(float pixScale, float instanceScale) {
562         final float effectiveScale = pixScale * instanceScale;
563         ImageIcon icon = SoftReference.dereference(scaledIconsCache.get(effectiveScale));
564         if (icon != null) {
565           return icon;
566         }
567
568         Image image;
569         if (svg) {
570           image = doWithTmpRegValue("ide.svg.icon", true, new Callable<Image>() {
571             @Override
572             public Image call() {
573               return ImageLoader.loadFromUrl(myUrl, true, myFilters, effectiveScale);
574             }
575           });
576         }
577         else {
578           boolean needRetinaImage = JBUI.isHiDPI(effectiveScale);
579           image = getOrLoadOrigImage(needRetinaImage);
580           if (image == null) return null;
581
582           if (!UIUtil.isJreHiDPIEnabled() && needRetinaImage) {
583             instanceScale = effectiveScale / 2f; // the image is 2x raw BufferedImage, compensate it
584           }
585
586           image = ImageUtil.scaleImage(image, instanceScale);
587         }
588         icon = checkIcon(image, myUrl);
589         if (icon != null && (icon.getIconWidth() * icon.getIconHeight() * 4) < ImageLoader.CACHED_IMAGE_MAX_SIZE) {
590           scaledIconsCache.put(effectiveScale, new SoftReference<ImageIcon>(icon));
591         }
592         return icon;
593       }
594
595       /**
596        * Retrieves the orig icon based on the pixScale.
597        */
598       public ImageIcon getOrLoadIcon(float pixScale) {
599         return getOrScaleIcon(pixScale, 1f);
600       }
601
602       public void clear() {
603         scaledIconsCache.clear();
604         origImagesCache.clear();
605       }
606     }
607   }
608
609   public abstract static class LazyIcon extends JBUI.UpdatingJBIcon {
610     private boolean myWasComputed;
611     private Icon myIcon;
612     private boolean isDarkVariant = USE_DARK_ICONS;
613     private int numberOfPatchers = ourPatchers.size();
614     private ImageFilter filter = IMAGE_FILTER;
615
616     @Override
617     public void paintIcon(Component c, Graphics g, int x, int y) {
618       final Icon icon = getOrComputeIcon(g);
619       if (icon != null) {
620         icon.paintIcon(c, g, x, y);
621       }
622     }
623
624     @Override
625     public int getIconWidth() {
626       final Icon icon = getOrComputeIcon();
627       return icon != null ? icon.getIconWidth() : 0;
628     }
629
630     @Override
631     public int getIconHeight() {
632       final Icon icon = getOrComputeIcon();
633       return icon != null ? icon.getIconHeight() : 0;
634     }
635
636     protected final synchronized Icon getOrComputeIcon() {
637       return getOrComputeIcon(null);
638     }
639
640     protected final synchronized Icon getOrComputeIcon(@Nullable Graphics g) {
641       if (!myWasComputed || isDarkVariant != USE_DARK_ICONS || needUpdateJBUIScale((Graphics2D)g) || filter != IMAGE_FILTER || numberOfPatchers != ourPatchers.size()) {
642         isDarkVariant = USE_DARK_ICONS;
643         updateJBUIScale((Graphics2D)g);
644         filter = IMAGE_FILTER;
645         myWasComputed = true;
646         numberOfPatchers = ourPatchers.size();
647         myIcon = compute();
648       }
649
650       return myIcon;
651     }
652
653     public final void load() {
654       getIconWidth();
655     }
656
657     protected abstract Icon compute();
658
659     public Icon inOriginalScale() {
660       Icon icon = getOrComputeIcon();
661       if (icon != null) {
662         if (icon instanceof CachedImageIcon) {
663           Image img = ((CachedImageIcon)icon).myScaledIconsCache.getOrLoadOrigImage(false);
664           if (img != null) {
665             icon = new ImageIcon(img);
666           }
667         }
668       }
669       return icon;
670     }
671   }
672
673   private static class LabelHolder {
674     /**
675      * To get disabled icon with paint it into the image. Some icons require
676      * not null component to paint.
677      */
678     private static final JComponent ourFakeComponent = new JLabel();
679   }
680
681   /**
682    * Do something with the temporarily registry value.
683    */
684   private static <T> T doWithTmpRegValue(String key, Boolean tempValue, Callable<T> action) {
685     RegistryValue regVal = Registry.get(key);
686     boolean regValOrig = regVal.asBoolean();
687     regVal.setValue(tempValue);
688     try {
689       return action.call();
690     }
691     catch (Exception ignore) {
692       return null;
693     }
694     finally {
695       regVal.setValue(regValOrig);
696     }
697   }
698 }