io.flutter.dart.FlutterDartAnalysisServer.java Source code

Java tutorial

Introduction

Here is the source code for io.flutter.dart.FlutterDartAnalysisServer.java

Source

/*
 * Copyright 2018 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
package io.flutter.dart;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.ReflectionUtil;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import org.dartlang.analysis.server.protocol.FlutterOutline;
import org.dartlang.analysis.server.protocol.FlutterService;
import org.dartlang.analysis.server.protocol.SourceChange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class FlutterDartAnalysisServer {
    private static final String FLUTTER_DESIGN_TIME_CONSTRUCTOR = "flutter.getChangeAddForDesignTimeConstructor";
    private static final String FLUTTER_NOTIFICATION_OUTLINE = "flutter.outline";

    @NotNull
    final DartAnalysisServerService analysisService;

    /**
     * Each key is a notification identifier.
     * Each value is the set of files subscribed to the notification.
     */
    private final Map<String, List<String>> subscriptions = new HashMap<>();

    private final Map<String, List<FlutterOutlineListener>> fileOutlineListeners = new HashMap<>();

    /**
     * Each key is a request identifier.
     * Each value is the {@link Consumer} for the response.
     */
    private final Map<String, Consumer<JsonObject>> responseConsumers = new HashMap<>();

    @NotNull
    public static FlutterDartAnalysisServer getInstance(@NotNull final Project project) {
        return ServiceManager.getService(project, FlutterDartAnalysisServer.class);
    }

    private FlutterDartAnalysisServer(@NotNull Project project) {
        analysisService = DartPlugin.getInstance().getAnalysisService(project);
        analysisService.addResponseListener(FlutterDartAnalysisServer.this::processResponse);
    }

    public void addOutlineListener(@NotNull final String filePath, @NotNull final FlutterOutlineListener listener) {
        final List<FlutterOutlineListener> listeners = fileOutlineListeners.computeIfAbsent(filePath,
                k -> new ArrayList<>());
        if (listeners.add(listener)) {
            addSubscription(FlutterService.OUTLINE, filePath);
        }
    }

    public void removeOutlineListener(@NotNull final String filePath,
            @NotNull final FlutterOutlineListener listener) {
        final List<FlutterOutlineListener> listeners = fileOutlineListeners.get(filePath);
        if (listeners != null && listeners.remove(listener)) {
            removeSubscription(FlutterService.OUTLINE, filePath);
        }
    }

    private void addSubscription(@NotNull final String service, @NotNull final String filePath) {
        final List<String> files = subscriptions.computeIfAbsent(service, k -> new ArrayList<>());
        if (files.add(filePath)) {
            sendSubscriptions();
        }
    }

    private void removeSubscription(@NotNull final String service, @NotNull final String filePath) {
        final List<String> files = subscriptions.get(service);
        if (files != null && files.remove(filePath)) {
            sendSubscriptions();
        }
    }

    private void sendSubscriptions() {
        final String id = analysisService.generateUniqueId();
        analysisService.sendRequest(id,
                FlutterRequestUtilities.generateAnalysisSetSubscriptions(id, subscriptions));
    }

    @NotNull
    public List<SourceChange> edit_getAssists(@NotNull VirtualFile file, int offset, int length) {
        return analysisService.edit_getAssists(file, offset, length);
    }

    @Nullable
    public SourceChange flutter_getChangeAddForDesignTimeConstructor(@NotNull VirtualFile file, int _offset) {
        final String filePath = FileUtil.toSystemDependentName(file.getPath());
        final int offset = getOriginalOffset(file, _offset);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<SourceChange> result = new AtomicReference<>();
        final String id = analysisService.generateUniqueId();
        responseConsumers.put(id, (resultObject) -> {
            try {
                final JsonObject changeObject = resultObject.getAsJsonObject("change");
                final SourceChange change = SourceChange.fromJson(changeObject);
                result.set(change);
            } catch (Throwable ignored) {
            }
            latch.countDown();
        });

        final JsonObject request = FlutterRequestUtilities.generateFlutterGetChangeAddForDesignTimeConstructor(id,
                filePath, offset);
        analysisService.sendRequest(id, request);

        Uninterruptibles.awaitUninterruptibly(latch, 100, TimeUnit.MILLISECONDS);
        return result.get();
    }

    /**
     * Handle the given {@link JsonObject} response.
     */
    private void processResponse(JsonObject response) {
        if (processNotification(response)) {
            return;
        }

        if (response.has("error")) {
            return;
        }

        final JsonObject resultObject = response.getAsJsonObject("result");
        if (resultObject == null) {
            return;
        }

        final JsonPrimitive idJsonPrimitive = (JsonPrimitive) response.get("id");
        if (idJsonPrimitive == null) {
            return;
        }
        final String idString = idJsonPrimitive.getAsString();

        final Consumer<JsonObject> consumer = responseConsumers.remove(idString);
        if (consumer == null) {
            return;
        }

        consumer.consume(resultObject);
    }

    /**
     * Attempts to handle the given {@link JsonObject} as a notification.
     */
    private boolean processNotification(JsonObject response) {
        final JsonElement eventElement = response.get("event");
        if (eventElement == null || !eventElement.isJsonPrimitive()) {
            return false;
        }
        final String event = eventElement.getAsString();
        if (event.equals(FLUTTER_NOTIFICATION_OUTLINE)) {
            final JsonObject paramsObject = response.get("params").getAsJsonObject();
            final String file = paramsObject.get("file").getAsString();

            final JsonElement instrumentedCodeElement = paramsObject.get("instrumentedCode");
            final String instrumentedCode = instrumentedCodeElement != null ? instrumentedCodeElement.getAsString()
                    : null;

            final JsonObject outlineObject = paramsObject.get("outline").getAsJsonObject();
            final FlutterOutline outline = FlutterOutline.fromJson(outlineObject);

            final List<FlutterOutlineListener> listeners = fileOutlineListeners.get(file);
            if (listeners != null) {
                for (FlutterOutlineListener listener : Lists.newArrayList(listeners)) {
                    listener.outlineUpdated(file, outline, instrumentedCode);
                }
            }
        }
        return true;
    }

    /**
     * Must use it right before sending any offsets and lengths to the AnalysisServer.
     */
    private int getOriginalOffset(@Nullable final VirtualFile file, final int convertedOffset) {
        // TODO(scheglov) Remove reflection when the method is made public everywhere.
        // https://github.com/JetBrains/intellij-plugins/pull/572
        final Method method = ReflectionUtil.getDeclaredMethod(analysisService.getClass(), "getOriginalOffset",
                VirtualFile.class, int.class);
        if (method != null) {
            try {
                return (Integer) method.invoke(analysisService, file, convertedOffset);
            } catch (Throwable ignored) {
            }
        }
        return convertedOffset;
    }
}