Do not store instances of Application in strong static references

Introduce ExtensionProvider which takes care of caching application using weak reference
Fixes a memory leak for scenarios with application being substituted (Jps build does that when compiling multiple modules)
This commit is contained in:
Pavel V. Talanov
2015-03-26 14:23:18 +03:00
parent 827d9d48c1
commit f3191a39a5
4 changed files with 88 additions and 137 deletions
@@ -16,13 +16,8 @@
package org.jetbrains.kotlin.diagnostics.rendering;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@@ -38,9 +33,11 @@ import org.jetbrains.kotlin.renderer.MultiRenderer;
import org.jetbrains.kotlin.renderer.Renderer;
import org.jetbrains.kotlin.resolve.varianceChecker.VarianceChecker.VarianceConflictDiagnosticData;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.util.MappedExtensionProvider;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -58,61 +55,36 @@ public class DefaultErrorMessages {
}
private static final DiagnosticFactoryToRendererMap MAP = new DiagnosticFactoryToRendererMap();
private static List<DiagnosticFactoryToRendererMap> maps = null;
private static Application application = ApplicationManager.getApplication();
private static DispatchingDiagnosticRenderer renderer = null;
private static final MappedExtensionProvider<Extension, List<DiagnosticFactoryToRendererMap>> RENDERER_MAPS = MappedExtensionProvider.create(
Extension.EP_NAME,
new Function1<List<? extends Extension>, List<DiagnosticFactoryToRendererMap>>() {
@Override
public List<DiagnosticFactoryToRendererMap> invoke(List<? extends Extension> extensions) {
List<DiagnosticFactoryToRendererMap> result = new ArrayList<DiagnosticFactoryToRendererMap>(extensions.size() + 1);
for (Extension extension : extensions) {
result.add(extension.getMap());
}
result.add(MAP);
return result;
}
});
@NotNull
public static String render(@NotNull Diagnostic diagnostic) {
return getRenderer().render(diagnostic);
}
@NotNull
private static DiagnosticRenderer<Diagnostic> getRenderer() {
boolean mapsChanged = resetMapsIfNeeded();
// Renderer is changed in tests only
if (renderer == null || mapsChanged) {
renderer = new DispatchingDiagnosticRenderer(maps);
for (DiagnosticFactoryToRendererMap map : RENDERER_MAPS.get()) {
DiagnosticRenderer renderer = map.get(diagnostic.getFactory());
if (renderer != null) {
//noinspection unchecked
return renderer.render(diagnostic);
}
}
return renderer;
}
private static boolean resetMapsIfNeeded() {
boolean needToResetMaps = maps == null;
Application newApp = ApplicationManager.getApplication();
if (application != newApp) {
assert newApp.isUnitTestMode(): "Expected application switch only in tests";
application = newApp;
needToResetMaps = true;
}
if (!needToResetMaps) return false;
maps = ImmutableList.<DiagnosticFactoryToRendererMap>builder()
.addAll(
KotlinPackage.map(
Extensions.getExtensions(Extension.EP_NAME),
new Function1<Extension, DiagnosticFactoryToRendererMap>() {
@Override
public DiagnosticFactoryToRendererMap invoke(Extension extension) {
return extension.getMap();
}
}
)
)
.add(MAP)
.build();
return true;
throw new IllegalArgumentException("Don't know how to render diagnostic of type " + diagnostic.getFactory().getName());
}
@TestOnly
@Nullable
public static DiagnosticRenderer getRendererForDiagnostic(@NotNull Diagnostic diagnostic) {
for (DiagnosticFactoryToRendererMap map : maps) {
for (DiagnosticFactoryToRendererMap map : RENDERER_MAPS.get()) {
DiagnosticRenderer renderer = map.get(diagnostic.getFactory());
if (renderer != null) return renderer;
@@ -642,8 +614,6 @@ public class DefaultErrorMessages {
}
}
}
resetMapsIfNeeded();
}
private DefaultErrorMessages() {
@@ -1,44 +0,0 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.diagnostics.rendering;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import java.util.List;
public class DispatchingDiagnosticRenderer implements DiagnosticRenderer<Diagnostic> {
@NotNull
private final List<DiagnosticFactoryToRendererMap> maps;
public DispatchingDiagnosticRenderer(@NotNull List<DiagnosticFactoryToRendererMap> maps) {
this.maps = maps;
}
@NotNull
@Override
public String render(@NotNull Diagnostic diagnostic) {
for (DiagnosticFactoryToRendererMap map : maps) {
DiagnosticRenderer renderer = map.get(diagnostic.getFactory());
if (renderer != null) {
//noinspection unchecked
return renderer.render(diagnostic);
}
}
throw new IllegalArgumentException("Don't know how to render diagnostic of type " + diagnostic.getFactory().getName());
}
}
@@ -17,11 +17,8 @@
package org.jetbrains.kotlin.resolve.diagnostics;
import com.google.common.collect.ImmutableSet;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.psi.PsiElement;
@@ -35,15 +32,12 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.Severity;
import org.jetbrains.kotlin.psi.JetAnnotated;
import org.jetbrains.kotlin.psi.JetAnnotationEntry;
import org.jetbrains.kotlin.psi.JetFile;
import org.jetbrains.kotlin.psi.JetStubbedPsiUtil;
import org.jetbrains.kotlin.psi.PsiPackage;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.constants.ArrayValue;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.constants.StringValue;
import org.jetbrains.kotlin.util.ExtensionProvider;
import java.util.*;
@@ -64,36 +58,10 @@ public class DiagnosticsWithSuppression implements Diagnostics {
private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
private static Application app = null;
private static SuppressStringProvider[] ADDITIONAL_SUPPRESS_STRING_PROVIDERS = null;
private static DiagnosticSuppressor[] DIAGNOSTIC_SUPPRESSORS = null;
static {
resetExtensionsIfNeed();
}
private static SuppressStringProvider[] getAdditionalSuppressStringProviders() {
resetExtensionsIfNeed();
return ADDITIONAL_SUPPRESS_STRING_PROVIDERS;
}
private static DiagnosticSuppressor[] getDiagnosticSuppressors() {
resetExtensionsIfNeed();
return DIAGNOSTIC_SUPPRESSORS;
}
// We need to update extensions in tests because they may be different for different tests.
private static void resetExtensionsIfNeed() {
if (app != null && !app.isUnitTestMode()) return;
Application newApp = ApplicationManager.getApplication();
if (app == newApp) return;
app = newApp;
ADDITIONAL_SUPPRESS_STRING_PROVIDERS = Extensions.getExtensions(SuppressStringProvider.EP_NAME);
DIAGNOSTIC_SUPPRESSORS = Extensions.getExtensions(DiagnosticSuppressor.EP_NAME);
}
private static final ExtensionProvider<SuppressStringProvider> ADDITIONAL_SUPPRESS_STRING_PROVIDERS
= ExtensionProvider.create(SuppressStringProvider.EP_NAME);
private static final ExtensionProvider<DiagnosticSuppressor> DIAGNOSTIC_SUPPRESSORS
= ExtensionProvider.create(DiagnosticSuppressor.EP_NAME);
private final BindingContext context;
private final Collection<Diagnostic> diagnostics;
@@ -153,7 +121,7 @@ public class DiagnosticsWithSuppression implements Diagnostics {
if (PsiPackage.getDoNotAnalyze((JetFile) file) != null) return true;
}
for (DiagnosticSuppressor suppressor : getDiagnosticSuppressors()) {
for (DiagnosticSuppressor suppressor : DIAGNOSTIC_SUPPRESSORS.get()) {
if (suppressor.isSuppressed(diagnostic)) return true;
}
@@ -239,7 +207,7 @@ public class DiagnosticsWithSuppression implements Diagnostics {
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
if (annotationDescriptor == null) continue;
for (SuppressStringProvider suppressStringProvider : getAdditionalSuppressStringProviders()) {
for (SuppressStringProvider suppressStringProvider : ADDITIONAL_SUPPRESS_STRING_PROVIDERS.get()) {
builder.addAll(suppressStringProvider.get(annotationDescriptor));
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.util
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.ExtensionPointName
import java.lang.ref.WeakReference
import kotlin.platform.platformStatic
public open class MappedExtensionProvider<T, R> protected (private val epName: ExtensionPointName<T>, private val map: (List<T>) -> R) {
private var cached = WeakReference<Pair<Application, R>>(null)
public fun get(): R {
val cached = cached.get() ?: return update()
val (app, extensions) = cached
if (app == ApplicationManager.getApplication()) {
return extensions
}
else {
return update()
}
}
private fun update(): R {
val newVal = ApplicationManager.getApplication().let { app ->
Pair(app, map(app.getExtensions(epName).toList()))
}
cached = WeakReference(newVal)
return newVal.second
}
companion object {
platformStatic public fun <T, R> create(epName: ExtensionPointName<T>, map: (List<T>) -> R): MappedExtensionProvider<T, R>
= MappedExtensionProvider(epName, map)
}
}
public class ExtensionProvider<T>(epName: ExtensionPointName<T>) : MappedExtensionProvider<T, List<T>>(epName, { it }) {
companion object {
platformStatic public fun <T> create(epName: ExtensionPointName<T>): ExtensionProvider<T> = ExtensionProvider(epName)
}
}