com.android.tools.idea.rendering.RenderErrorContributor.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.rendering.RenderErrorContributor.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.tools.idea.rendering;

import com.android.annotations.VisibleForTesting;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.resources.ResourceResolver;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
import com.android.resources.Density;
import com.android.sdklib.IAndroidTarget;
import com.android.tools.idea.model.AndroidModuleInfo;
import com.android.tools.idea.rendering.errors.ui.RenderErrorModel;
import com.android.tools.idea.rendering.errors.ui.RenderErrorPanel;
import com.android.tools.idea.sdk.AndroidSdks;
import com.android.tools.idea.ui.designer.EditorDesignSurface;
import com.android.utils.HtmlBuilder;
import com.android.xml.AndroidManifest;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ui.configuration.ClasspathEditor;
import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.problems.WolfTheProblemSolver;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.android.dom.attrs.AttributeDefinition;
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
import org.jetbrains.android.dom.attrs.AttributeFormat;
import org.jetbrains.android.dom.manifest.Application;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
import org.jetbrains.android.sdk.AndroidSdkType;
import org.jetbrains.android.sdk.AndroidTargetData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.android.SdkConstants.*;
import static com.android.ide.common.rendering.api.LayoutLog.TAG_RESOURCES_PREFIX;
import static com.android.ide.common.rendering.api.LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR;
import static com.android.tools.idea.rendering.RenderLogger.TAG_STILL_BUILDING;
import static com.android.tools.idea.res.ResourceHelper.isViewPackageNeeded;
import static com.android.tools.lint.detector.api.LintUtils.editDistance;
import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
import static com.intellij.openapi.util.SystemInfo.JAVA_VERSION;

/**
 * Class that finds {@link RenderErrorModel.Issue}s in a {@link RenderResult}.
 */
public class RenderErrorContributor {
    private static final String RENDER_SESSION_IMPL_FQCN = RenderSessionImpl.class.getCanonicalName();

    // These priorities can be used to promote certain issues to the top of the list
    protected static final int HIGH_PRIORITY = 100;
    @SuppressWarnings("unused")
    protected static final int MEDIUM_PRIORITY = 10;
    @SuppressWarnings("unused")
    protected static final int LOW_PRIORITY = 10;

    protected static final Logger LOG = Logger.getInstance(RenderErrorPanel.class);

    private final List<RenderErrorModel.Issue> myIssues = new ArrayList<>();
    private final HtmlLinkManager myLinkManager;
    private final HyperlinkListener myLinkHandler;
    private final RenderResult myResult;
    private final DataContext myDataContext;

    protected RenderErrorContributor(@NotNull RenderResult result, @Nullable DataContext dataContext) {
        myResult = result;

        myLinkManager = myResult.getLogger().getLinkManager();
        myLinkHandler = e -> {
            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                JEditorPane pane = (JEditorPane) e.getSource();
                if (e instanceof HTMLFrameHyperlinkEvent) {
                    HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e;
                    HTMLDocument doc = (HTMLDocument) pane.getDocument();
                    doc.processHTMLFrameHyperlinkEvent(evt);
                    return;
                }

                performClick(myResult, e.getDescription());
            }
        };

        myDataContext = dataContext;
    }

    private static boolean isHiddenFrame(@NotNull StackTraceElement frame) {
        String className = frame.getClassName();
        return className.startsWith("sun.reflect.") || className.equals("android.view.BridgeInflater")
                || className.startsWith("com.android.tools.") || className.startsWith("org.jetbrains.");
    }

    private static boolean isInterestingFrame(@NotNull StackTraceElement frame) {
        String className = frame.getClassName();
        return !(className.startsWith("android.") || className.startsWith("org.jetbrains.")
                || className.startsWith("com.android.") || className.startsWith("java.")
                || className.startsWith("javax.") || className.startsWith("sun."));
    }

    private static boolean isFramework(@NotNull StackTraceElement frame) {
        String className = frame.getClassName();
        return (className.startsWith("android.") || className.startsWith("java.") || className.startsWith("javax.")
                || className.startsWith("sun."));
    }

    private static boolean isVisible(@NotNull StackTraceElement frame) {
        String className = frame.getClassName();
        return !(isFramework(frame) || className.startsWith("sun."));
    }

    @NotNull
    private static Collection<PsiClass> findInheritors(@NotNull final Module module, @NotNull final String name) {
        if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
            return ApplicationManager.getApplication().runReadAction(new Computable<Collection<PsiClass>>() {
                @NotNull
                @Override
                public Collection<PsiClass> compute() {
                    return findInheritors(module, name);
                }
            });
        }

        Project project = module.getProject();
        try {
            PsiClass base = JavaPsiFacade.getInstance(project).findClass(name, GlobalSearchScope.allScope(project));
            if (base != null) {
                GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false);
                return ClassInheritorsSearch.search(base, scope, true).findAll();
            }
        } catch (IndexNotReadyException ignored) {
        }
        return Collections.emptyList();
    }

    @NotNull
    private static Collection<String> getAllViews(@Nullable final Module module) {
        if (module == null) {
            return Collections.emptyList();
        }
        if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
            return ApplicationManager.getApplication().runReadAction(new Computable<Collection<String>>() {
                @NotNull
                @Override
                public Collection<String> compute() {
                    return getAllViews(module);
                }
            });
        }

        Set<String> names = new HashSet<>();
        for (PsiClass psiClass : findInheritors(module, CLASS_VIEW)) {
            String name = psiClass.getQualifiedName();
            if (name != null) {
                names.add(name);
            }
        }

        return names;
    }

    static boolean isBuiltByJdk7OrHigher(@NotNull Module module) {
        Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
        if (sdk == null) {
            return false;
        }

        AndroidSdks androidSdks = AndroidSdks.getInstance();
        if (androidSdks.isAndroidSdk(sdk)) {
            AndroidSdkAdditionalData data = androidSdks.getAndroidSdkAdditionalData(sdk);
            if (data != null) {
                Sdk jdk = data.getJavaSdk();
                if (jdk != null) {
                    sdk = jdk;
                }
            }
        }
        return sdk.getSdkType() instanceof JavaSdk
                && JavaSdk.getInstance().isOfVersionOrHigher(sdk, JavaSdkVersion.JDK_1_7);
    }

    private static void collectProblemModules(@NotNull Module module, @NotNull Set<Module> visited,
            @NotNull Collection<Module> result) {
        if (!visited.add(module)) {
            return;
        }

        if (isBuiltByJdk7OrHigher(module)) {
            result.add(module);
        }

        for (Module depModule : ModuleRootManager.getInstance(module).getDependencies(false)) {
            collectProblemModules(depModule, visited, result);
        }
    }

    @NotNull
    private static Set<String> getSdkNamesFromModules(@NotNull Collection<Module> modules) {
        final Set<String> result = new HashSet<>();
        for (Module module : modules) {
            final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();

            if (sdk != null) {
                result.add(sdk.getName());
            }
        }
        return result;
    }

    @NotNull
    private static List<Module> getProblemModules(@NotNull Module root) {
        final List<Module> result = new ArrayList<>();
        collectProblemModules(root, new HashSet<>(), result);
        return result;
    }

    private static void askAndRebuild(Project project) {
        final int r = Messages.showYesNoDialog(project,
                "You have to rebuild project to see the fixed preview. Would you like to do it?", "Rebuild Project",
                Messages.getQuestionIcon());
        if (r == Messages.YES) {
            CompilerManager.getInstance(project).rebuild(null);
        }
    }

    /**
     * Returns a new {@link RenderErrorModel.Issue.Builder} that will add the created issue to the issues list when
     * {@link RenderErrorModel.Issue.Builder#build()} is called.
     * The returned builder also is pre-configured with the default link handler.
     */
    protected RenderErrorModel.Issue.Builder addIssue() {
        return new RenderErrorModel.Issue.Builder() {
            @NotNull
            @Override
            public RenderErrorModel.Issue build() {
                RenderErrorModel.Issue built = super.build();
                myIssues.add(built);
                return built;
            }
        }.setLinkHandler(myLinkHandler);
    }

    private void reportMissingStyles(@NotNull RenderLogger logger) {
        if (logger.seenTagPrefix(TAG_STILL_BUILDING)) {
            addIssue().setSummary("Project Still Building: May cause rendering errors until the build is done")
                    .build();
        } else if (logger.seenTagPrefix(TAG_RESOURCES_RESOLVE_THEME_ATTR)) {
            addIssue().setSummary("Missing styles").setHtmlContent(new HtmlBuilder()
                    .addBold("Missing styles. Is the correct theme chosen for this layout?").newline()
                    .addIcon(HtmlBuilderHelper.getTipIconPath())
                    .add("Use the Theme combo box above the layout to choose a different layout, or fix the theme style references."))
                    .build();
        }
    }

    private void reportOldNinePathRenderLib(@NotNull RenderLogger logger, @NotNull RenderTask renderTask) {
        if (renderTask.getConfiguration().getDensity() != Density.TV) {
            return;
        }

        logger.getTraces().stream().map(Object::toString)
                .filter(s -> s.contains("java.lang.IndexOutOfBoundsException: Index: 2, Size: 2")).findAny()
                .ifPresent(s -> addIssue().setSummary("tvdpi not supported").setHtmlContent(new HtmlBuilder()
                        .addBold(
                                "It looks like you are using a render target where the layout library does not support the tvdpi density.")
                        .newline().newline()
                        .add("Please try either updating to the latest available version (using the SDK manager), or if no updated "
                                + "version is available for this specific version of Android, try using a more recent render target version.")
                        .newline().newline()).build());
    }

    private void reportMissingSize(@NotNull HtmlBuilder builder, @NotNull RenderLogger logger, @NotNull String fill,
            @NotNull XmlTag tag, @NotNull String id, @NotNull String attribute) {
        Module module = logger.getModule();
        if (module == null) {
            return;
        }
        Project project = module.getProject();
        String wrapUrl = myLinkManager
                .createCommandLink(new SetAttributeFix(project, tag, attribute, ANDROID_URI, VALUE_WRAP_CONTENT));
        String fillUrl = myLinkManager
                .createCommandLink(new SetAttributeFix(project, tag, attribute, ANDROID_URI, fill));

        builder.add(String.format("%1$s does not set the required %2$s attribute: ", id, attribute)).newline()
                .addNbsps(4).addLink("Set to wrap_content", wrapUrl).add(", ").addLink("Set to " + fill, fillUrl)
                .newline();
    }

    private void reportMissingSizeAttributes(@NotNull final RenderLogger logger, @NotNull RenderTask renderTask) {
        Module module = logger.getModule();
        if (module == null) {
            return;
        }
        Project project = module.getProject();
        if (logger.isMissingSize()) {
            HtmlBuilder builder = new HtmlBuilder();

            // Emit hyperlink about missing attributes; the action will operate on all of them
            builder.addBold("NOTE: One or more layouts are missing the layout_width or layout_height attributes. "
                    + "These are required in most layouts.").newline();
            final ResourceResolver resourceResolver = renderTask.getResourceResolver();
            XmlFile psiFile = renderTask.getPsiFile();
            if (psiFile == null) {
                LOG.error("PsiFile is missing in RenderTask used in RenderErrorPanel!");
                return;
            }
            AddMissingAttributesFix fix = new AddMissingAttributesFix(project, psiFile, resourceResolver);

            List<XmlTag> missing = fix.findViewsMissingSizes();

            // See whether we should offer match_parent instead of fill_parent
            AndroidModuleInfo moduleInfo = AndroidModuleInfo.get(module);
            final String fill = moduleInfo == null || moduleInfo.getBuildSdkVersion() == null
                    || moduleInfo.getBuildSdkVersion().getApiLevel() >= 8 ? VALUE_MATCH_PARENT : VALUE_FILL_PARENT;

            for (final XmlTag tag : missing) {
                ApplicationManager.getApplication().runReadAction(() -> {
                    boolean missingWidth = !AddMissingAttributesFix.definesWidth(tag, resourceResolver);
                    boolean missingHeight = !AddMissingAttributesFix.definesHeight(tag, resourceResolver);
                    assert missingWidth || missingHeight;

                    String id = tag.getAttributeValue(ATTR_ID);
                    if (id == null || id.length() == 0) {
                        id = '<' + tag.getName() + '>';
                    } else {
                        id = '"' + stripIdPrefix(id) + '"';
                    }

                    if (missingWidth) {
                        reportMissingSize(builder, logger, fill, tag, id, ATTR_LAYOUT_WIDTH);
                    }
                    if (missingHeight) {
                        reportMissingSize(builder, logger, fill, tag, id, ATTR_LAYOUT_HEIGHT);
                    }
                });
            }

            builder.newline().add("Or: ")
                    .addLink("Automatically add all missing attributes", myLinkManager.createCommandLink(fix))
                    .newline().newline().newline();

            addIssue().setSeverity(HighlightSeverity.ERROR)
                    .setSummary("One or more layouts are missing the layout_width or layout_height attributes")
                    .setHtmlContent(builder).build();
        }
    }

    private void reportRenderingFidelityProblems(@NotNull RenderLogger logger,
            @NotNull final RenderTask renderTask) {
        List<RenderProblem> fidelityWarnings = logger.getFidelityWarnings();
        if (fidelityWarnings == null || fidelityWarnings.isEmpty()) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        builder.add("The graphics preview in the layout editor may not be accurate:").newline();
        builder.beginList();
        int count = 0;
        for (final RenderProblem warning : fidelityWarnings) {
            builder.listItem();
            warning.appendHtml(builder.getStringBuilder());
            final Object clientData = warning.getClientData();
            if (clientData != null) {
                builder.addLink(" (Ignore for this session)", myLinkManager.createRunnableLink(() -> {
                    RenderLogger.ignoreFidelityWarning(clientData);
                    EditorDesignSurface surface = renderTask.getDesignSurface();
                    if (surface != null) {
                        surface.requestRender();
                    }
                }));
            }
            builder.newline();
            count++;
            // Only display the first 3 render fidelity issues
            if (count == 3) {
                @SuppressWarnings("ConstantConditions")
                int remaining = fidelityWarnings.size() - count;
                if (remaining > 0) {
                    builder.add("(").addHtml(Integer.toString(remaining))
                            .add(" additional render fidelity issues hidden)");
                    break;
                }
            }
        }
        builder.endList();
        builder.addLink("Ignore all fidelity warnings for this session", myLinkManager.createRunnableLink(() -> {
            RenderLogger.ignoreAllFidelityWarnings();
            EditorDesignSurface surface = renderTask.getDesignSurface();
            if (surface != null) {
                surface.requestRender();
            }
        }));
        builder.newline();

        addIssue().setSeverity(HighlightSeverity.WARNING).setSummary("Layout fidelity warning")
                .setHtmlContent(builder).build();
    }

    @VisibleForTesting
    public void performClick(@NotNull RenderResult result, @NotNull String url) {
        Module module = result.getModule();
        PsiFile file = result.getFile();

        myLinkManager.handleUrl(url, module, file, myDataContext, result);
    }

    private void reportRelevantCompilationErrors(@NotNull RenderLogger logger, @NotNull RenderTask renderTask) {
        Module module = logger.getModule();
        if (module == null) {
            return;
        }

        Project project = module.getProject();
        WolfTheProblemSolver wolfgang = WolfTheProblemSolver.getInstance(project);

        if (!wolfgang.hasProblemFilesBeneath(module)) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        String summary = null;
        if (logger.seenTagPrefix(TAG_RESOURCES_PREFIX)) {
            // Do we have errors in the res/ files?
            // See if it looks like we have aapt problems
            boolean haveResourceErrors = wolfgang
                    .hasProblemFilesBeneath(virtualFile -> virtualFile.getFileType() == StdFileTypes.XML);
            if (haveResourceErrors) {
                summary = "Resource errors";
                builder.addBold("This project contains resource errors, so aapt did not succeed, "
                        + "which can cause rendering failures. Fix resource problems first.").newline().newline();
            }
        } else if (renderTask.getLayoutlibCallback().isUsed()) {
            boolean hasJavaErrors = wolfgang
                    .hasProblemFilesBeneath(virtualFile -> virtualFile.getFileType() == StdFileTypes.JAVA);
            if (hasJavaErrors) {
                summary = "Compilation errors";
                builder.addBold("This project contains Java compilation errors, "
                        + "which can cause rendering failures for custom views. "
                        + "Fix compilation problems first.").newline().newline();
            }
        }

        if (summary == null) {
            return;
        }
        addIssue().setSeverity(HighlightSeverity.ERROR).setSummary(summary).setHtmlContent(builder).build();
    }

    private boolean reportSandboxError(@NotNull Throwable throwable, boolean newlineBefore, boolean newlineAfter) {
        if (!(throwable instanceof SecurityException)) {
            return false;
        }

        HtmlBuilder builder = new HtmlBuilder();
        if (newlineBefore) {
            builder.newline();
        }
        builder.addLink("Turn off custom view rendering sandbox", myLinkManager.createDisableSandboxUrl());

        String lastFailedPath = RenderSecurityManager.getLastFailedPath();
        if (lastFailedPath != null) {
            builder.newline().newline().add("Diagnostic info for Studio bug report:").newline().add("Failed path: ")
                    .add(lastFailedPath).newline();
            String tempDir = System.getProperty("java.io.tmpdir");
            builder.add("Normal temp dir: ").add(tempDir).newline();
            File normalized = new File(tempDir);
            builder.add("Normalized temp dir: ").add(normalized.getPath()).newline();
            try {
                builder.add("Canonical temp dir: ").add(normalized.getCanonicalPath()).newline();
            } catch (IOException e) {
                // ignore
            }
            builder.add("os.name: ").add(SystemInfo.OS_NAME).newline().add("os.version: ")
                    .add(SystemInfo.OS_VERSION).newline().add("java.runtime.version: ")
                    .add(SystemInfo.JAVA_RUNTIME_VERSION);
        }

        if (throwable.getMessage().equals("Unable to create temporary file")) {
            if (JAVA_VERSION.startsWith("1.7.0_")) {
                int version = Integer.parseInt(JAVA_VERSION.substring(JAVA_VERSION.indexOf('_') + 1));
                if (version > 0 && version < 45) {
                    builder.newline().addIcon(HtmlBuilderHelper.getTipIconPath()).add(
                            "Tip: This may be caused by using an older version of JDK 1.7.0; try using at least 1.7.0_45 "
                                    + "(you are using " + JAVA_VERSION + ")");
                }
            }
        }
        if (newlineAfter) {
            builder.newline().newline();
        }

        reportThrowable(builder, throwable, false);
        addRefreshAction(builder);

        addIssue().setSeverity(HighlightSeverity.ERROR).setSummary("Rendering sandbox error")
                .setHtmlContent(builder).build();
        return true;
    }

    /**
     * Display the problem list encountered during a render.
     *
     * @return if the throwable was hidden.
     */
    private boolean reportThrowable(@NotNull HtmlBuilder builder, @NotNull final Throwable throwable,
            boolean hideIfIrrelevant) {
        StackTraceElement[] frames = throwable.getStackTrace();
        int end = -1;
        boolean haveInterestingFrame = false;
        for (int i = 0; i < frames.length; i++) {
            StackTraceElement frame = frames[i];
            if (isInterestingFrame(frame)) {
                haveInterestingFrame = true;
            }
            String className = frame.getClassName();
            if (className.equals(RENDER_SESSION_IMPL_FQCN)) {
                end = i;
                break;
            }
        }

        if (end == -1 || !haveInterestingFrame) {
            // Not a recognized stack trace range: just skip it
            if (hideIfIrrelevant) {
                if (RenderLogger.isLoggingAllErrors()) {
                    ShowExceptionFix detailsFix = new ShowExceptionFix(myResult.getModule().getProject(),
                            throwable);
                    builder.addLink("Show Exception", myLinkManager.createRunnableLink(detailsFix));
                }
                return true;
            } else {
                // List just the top frames
                for (int i = 0; i < frames.length; i++) {
                    StackTraceElement frame = frames[i];
                    if (!isVisible(frame)) {
                        end = i;
                        if (end == 0) {
                            // Find end instead
                            for (int j = 0; j < frames.length; j++) {
                                frame = frames[j];
                                String className = frame.getClassName();
                                if (className.equals(RENDER_SESSION_IMPL_FQCN)) {
                                    end = j;
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }

        builder.addHtml(StringUtil.replace(throwable.toString(), "\n", "<BR/>")).newline();

        boolean wasHidden = false;
        int indent = 2;
        File platformSource = null;
        boolean platformSourceExists = true;
        for (int i = 0; i < end; i++) {
            StackTraceElement frame = frames[i];
            if (isHiddenFrame(frame)) {
                wasHidden = true;
                continue;
            }

            String className = frame.getClassName();
            String methodName = frame.getMethodName();
            builder.addNbsps(indent);
            builder.add("at ").add(className).add(".").add(methodName);
            String fileName = frame.getFileName();
            if (fileName != null && !fileName.isEmpty()) {
                int lineNumber = frame.getLineNumber();
                String location = fileName + ':' + lineNumber;
                if (isInterestingFrame(frame)) {
                    if (wasHidden) {
                        builder.addNbsps(indent).add("    ...").newline();
                        wasHidden = false;
                    }
                    String url = myLinkManager.createOpenStackUrl(className, methodName, fileName, lineNumber);
                    builder.add("(").addLink(location, url).add(")");
                } else {
                    // Try to link to local documentation
                    String url = null;
                    if (isFramework(frame) && platformSourceExists) { // try to link to documentation, if available
                        if (platformSource == null) {
                            IAndroidTarget target = myResult.getRenderTask() != null
                                    ? myResult.getRenderTask().getConfiguration().getRealTarget()
                                    : null;
                            platformSource = target != null ? AndroidSdks.getInstance().findPlatformSources(target)
                                    : null;
                            platformSourceExists = platformSource != null;
                        }

                        if (platformSourceExists) {
                            File classFile = new File(platformSource,
                                    frame.getClassName().replace('.', File.separatorChar) + DOT_JAVA);
                            if (!classFile.exists()) {
                                // Probably an innerclass like foo.bar.Outer.Inner; the above would look for foo/bar/Outer/Inner.java; try
                                // again at foo/bar/
                                File parentFile = classFile.getParentFile();
                                classFile = new File(parentFile.getParentFile(), parentFile.getName() + DOT_JAVA);
                                if (!classFile.exists()) {
                                    classFile = null; // in theory we should keep trying this repeatedly for more deeply nested inner classes
                                }
                            }
                            if (classFile != null) {
                                url = HtmlLinkManager.createFilePositionUrl(classFile, lineNumber, 0);
                            }
                        }
                    }
                    if (url != null) {
                        builder.add("(").addLink(location, url).add(")");
                    } else {
                        builder.add("(").add(location).add(")");
                    }
                }
                builder.newline();
            }
        }

        builder.addLink("Copy stack to clipboard", myLinkManager.createRunnableLink(() -> {
            String text = Throwables.getStackTraceAsString(throwable);
            try {
                CopyPasteManager.getInstance().setContents(new StringSelection(text));
                RenderErrorPanel.showNotification("Stack trace copied to clipboard");
            } catch (Exception ignore) {
            }
        }));
        return false;
    }

    private void addRefreshAction(@NotNull HtmlBuilder builder) {
        builder.newlineIfNecessary().newline().addIcon(HtmlBuilderHelper.getRefreshIconPath())
                .addLink("Tip: Try to ", "refresh", " the layout.", myLinkManager.createRefreshRenderUrl())
                .newline();
    }

    private void reportRtlNotEnabled(@NotNull RenderLogger logger, @Nullable RenderTask task) {
        ApplicationManager.getApplication().runReadAction(() -> {
            Project project = logger.getProject();
            if (project == null || project.isDisposed()) {
                return;
            }

            Module module = logger.getModule();
            if (module == null) {
                return;
            }

            AndroidFacet facet = AndroidFacet.getInstance(module);
            Manifest manifest = facet != null ? facet.getManifest() : null;
            Application application = manifest != null ? manifest.getApplication() : null;
            if (application == null) {
                return;
            }

            final XmlTag applicationTag = application.getXmlTag();
            if (applicationTag == null) {
                return;
            }

            HtmlBuilder builder = new HtmlBuilder();
            builder.add("(").addLink("Add android:supportsRtl=\"true\" to the manifest",
                    logger.getLinkManager().createRunnableLink(() -> {
                        new SetAttributeFix(project, applicationTag, AndroidManifest.ATTRIBUTE_SUPPORTS_RTL,
                                ANDROID_URI, VALUE_TRUE).execute();

                        EditorDesignSurface surface = task != null ? task.getDesignSurface() : null;
                        if (surface != null) {
                            surface.requestRender(true);
                        }
                    })).add(")");

            addIssue().setSeverity(HighlightSeverity.ERROR)
                    .setSummary("RTL support requires android:supportsRtl=\"true\" in the manifest")
                    .setHtmlContent(builder).build();
        });
    }

    private void reportTagResourceFormat(@NotNull RenderResult result, @NotNull RenderProblem message) {
        Object clientData = message.getClientData();
        if (!(clientData instanceof String[])) {
            return;
        }
        String[] strings = (String[]) clientData;
        if (strings.length != 2) {
            return;
        }

        RenderTask renderTask = result.getRenderTask();
        if (renderTask == null) {
            return;
        }
        IAndroidTarget target = renderTask.getConfiguration().getRealTarget();
        if (target == null) {
            return;
        }
        AndroidPlatform platform = renderTask.getPlatform();
        if (platform == null) {
            return;
        }
        AndroidTargetData targetData = platform.getSdkData().getTargetData(target);
        AttributeDefinitions definitionLookup = targetData.getPublicAttrDefs(result.getFile().getProject());
        final String attributeName = strings[0];
        final String currentValue = strings[1];
        if (definitionLookup == null) {
            return;
        }
        AttributeDefinition definition = definitionLookup.getAttrDefByName(attributeName);
        if (definition == null) {
            return;
        }
        Set<AttributeFormat> formats = definition.getFormats();
        if (formats.contains(AttributeFormat.Flag) || formats.contains(AttributeFormat.Enum)) {
            String[] values = definition.getValues();
            if (values.length > 0) {
                HtmlBuilder builder = new HtmlBuilder();
                builder.add("Change ").add(currentValue).add(" to: ");
                boolean first = true;
                for (String value : values) {
                    if (first) {
                        first = false;
                    } else {
                        builder.add(", ");
                    }
                    builder.addLink(value,
                            myLinkManager.createReplaceAttributeValueUrl(attributeName, currentValue, value));
                }

                addRefreshAction(builder);
                addIssue()
                        //TODO: Review
                        .setSummary("Incorrect resource value format").setHtmlContent(builder).build();
            }
        }
    }

    private void reportOtherProblems(@NotNull RenderLogger logger, RenderTask task) {
        List<RenderProblem> messages = logger.getMessages();

        if (messages == null || messages.isEmpty()) {
            return;
        }

        Set<String> seenTags = Sets.newHashSet();
        for (RenderProblem message : messages) {
            String tag = message.getTag();
            if (tag != null && seenTags.contains(tag)) {
                continue;
            }
            seenTags.add(tag);

            if (tag != null) {
                if (LayoutLog.TAG_RESOURCES_FORMAT.equals(tag)) {
                    reportTagResourceFormat(myResult, message);
                    continue;
                } else if (LayoutLog.TAG_RTL_NOT_ENABLED.equals(tag)) {
                    reportRtlNotEnabled(logger, task);
                    continue;
                } else if (LayoutLog.TAG_RTL_NOT_SUPPORTED.equals(tag)) {
                    addIssue().setSeverity(HighlightSeverity.ERROR)
                            .setSummary("RTL support requires API level >= 17")
                            .setHtmlContent(new HtmlBuilder().addHtml(message.getHtml())).build();
                    continue;
                }
            }

            HtmlBuilder builder = new HtmlBuilder();

            String html = message.getHtml();
            Throwable throwable = message.getThrowable();

            String summary = "Render problem";
            if (throwable != null) {
                if (!reportSandboxError(throwable, false, true)) {
                    if (reportThrowable(builder, throwable, !html.isEmpty() || !message.isDefaultHtml())) {
                        // The error was hidden.
                        if (!html.isEmpty()) {
                            builder.getStringBuilder().append(html);
                            builder.newlineIfNecessary();
                        }

                        summary = throwable.getLocalizedMessage() != null ? throwable.getLocalizedMessage()
                                : summary;
                    }
                } else {
                    // This was processed as a Sandbox error
                    continue;
                }
            } else {
                if (html.contains("has been edited more recently")) {
                    summary = "Build out-of-date";
                }

                builder.getStringBuilder().append(html);
                builder.newlineIfNecessary();
            }

            addRefreshAction(builder);
            addIssue().setSeverity(HighlightSeverity.ERROR).setSummary(summary).setHtmlContent(builder).build();
        }
    }

    private boolean addTypoSuggestions(@NotNull HtmlBuilder builder, @NotNull String actual,
            @Nullable Collection<String> views, boolean compareWithPackage) {
        if (views == null || views.isEmpty()) {
            return false;
        }

        // Look for typos and try to match with custom views and android views
        String actualBase = actual.substring(actual.lastIndexOf('.') + 1);
        String match = compareWithPackage ? actual : actualBase;
        int maxDistance = actualBase.length() >= 4 ? 2 : 1;

        if (views.size() > 0) {
            for (String suggested : views) {
                String suggestedBase = suggested.substring(suggested.lastIndexOf('.') + 1);
                String matchWith = compareWithPackage ? suggested : suggestedBase;
                if (Math.abs(actualBase.length() - suggestedBase.length()) > maxDistance) {
                    // The string lengths differ more than the allowed edit distance;
                    // no point in even attempting to compute the edit distance (requires
                    // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
                    continue;
                }

                boolean sameBase = actualBase.equals(suggestedBase);
                if (!compareWithPackage && sameBase) {
                    // This view is an exact match for one of the known views.
                    // That probably means it's a valid class, but the project needs to be built.
                    continue;
                }

                if (compareWithPackage) {
                    if (!sameBase) {
                        // If they differ in the base name, handled by separate call with !compareWithPackage
                        continue;
                    } else if (actualBase.equals(actual) && !actualBase.equals(suggested)
                            && isViewPackageNeeded(suggested, -1)) {
                        // Custom view needs to be specified with a fully qualified path
                        builder.addLink(String.format("Change to %1$s", suggested),
                                myLinkManager.createReplaceTagsUrl(actual, suggested));
                        builder.add(", ");
                        continue;
                    }
                }

                if (compareWithPackage && Math.abs(match.length() - matchWith.length()) > maxDistance) {
                    continue;
                }

                if (match.equals(matchWith)) {
                    // Exact match: Likely that we're looking for a valid package, but project has
                    // not yet been built
                    return true;
                }

                if (editDistance(match, matchWith) <= maxDistance) {
                    // Suggest this class as a typo for the given class
                    String labelClass = (suggestedBase.equals(actual) || actual.indexOf('.') != -1) ? suggested
                            : suggestedBase;
                    builder.addLink(String.format("Change to %1$s", labelClass),
                            myLinkManager.createReplaceTagsUrl(actual,
                                    // Only show full package name if class name
                                    // is the same
                                    (isViewPackageNeeded(suggested, -1) ? suggested : suggestedBase)));
                    builder.add(", ");
                }
            }
        }

        return false;
    }

    private void reportMissingClasses(@NotNull RenderLogger logger) {
        Set<String> missingClasses = logger.getMissingClasses();
        if (missingClasses == null || missingClasses.isEmpty()) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        if (missingClasses.contains("CalendarView")) {
            builder.add("The ").addBold("CalendarView")
                    .add(" widget does not work correctly with this render target. "
                            + "As a workaround, try using the API 15 (Android 4.0.3) render target library by selecting it from the "
                            + "toolbar menu above.");
            if (missingClasses.size() == 1) {
                addIssue().setSeverity(HighlightSeverity.WARNING)
                        .setSummary("CalendarView does not work correctly with this render target")
                        .setHtmlContent(builder).build();
                return;
            }
        }

        boolean missingResourceClass = logger.isMissingResourceClass() && logger.getResourceClass() != null
                && logger.hasLoadedClasses();

        builder.add("The following classes could not be found:");
        builder.beginList();

        Collection<String> customViews = null;
        Collection<String> androidViewClassNames = null;
        Module module = logger.getModule();
        if (module != null) {
            Collection<String> views = getAllViews(module);
            if (!views.isEmpty()) {
                customViews = Lists.newArrayListWithExpectedSize(Math.max(10, views.size() - 80)); // most will be framework views
                androidViewClassNames = Lists.newArrayListWithExpectedSize(views.size());
                for (String fqcn : views) {
                    if (fqcn.startsWith("android.") && !isViewPackageNeeded(fqcn, -1)) {
                        androidViewClassNames.add(fqcn);
                    } else {
                        customViews.add(fqcn);
                    }
                }
            }
        }

        if (missingResourceClass) {
            builder.listItem();
            builder.add(logger.getResourceClass());
        }

        boolean foundCustomView = false;
        for (String className : missingClasses) {
            builder.listItem();
            builder.add(className);
            builder.add(" (");

            foundCustomView |= addTypoSuggestions(builder, className, customViews, false);
            addTypoSuggestions(builder, className, customViews, true);
            addTypoSuggestions(builder, className, androidViewClassNames, false);

            if (myLinkManager == null) {
                return;
            }

            if (CLASS_CONSTRAINT_LAYOUT.equals(className)) {
                builder.newline().addNbsps(3);
                builder.addLink("Add constraint-layout library dependency to the project",
                        myLinkManager.createAddDependencyUrl(CONSTRAINT_LAYOUT_LIB_ARTIFACT));
                builder.add(", ");
            }

            builder.addLink("Fix Build Path", myLinkManager.createEditClassPathUrl());

            //DesignSurface surface = renderTask.getDesignSurface();
            //if (surface != null && surface.getType() == LAYOUT_EDITOR) {
            builder.add(", ");
            builder.addLink("Edit XML", myLinkManager.createShowTagUrl(className));
            //}

            // Offer to create the class, but only if it looks like a custom view
            // TODO: Check to see if it looks like it's the name of a custom view and the
            // user didn't realize a FQN is required here
            if (className.indexOf('.') != -1) {
                builder.add(", ");
                builder.addLink("Create Class", myLinkManager.createNewClassUrl(className));
            }
            builder.add(")");
        }
        builder.endList();

        builder.addIcon(HtmlBuilderHelper.getTipIconPath());
        builder.addLink("Tip: Try to ", "build", " the project.", myLinkManager.createBuildProjectUrl());
        addRefreshAction(builder);
        if (foundCustomView) {
            builder.newline().add(
                    "One or more missing custom views were found in the project, but does not appear to have been compiled yet.");
        }
        builder.newline().newline();

        addIssue().setSeverity(HighlightSeverity.ERROR).setSummary("Missing classes").setHtmlContent(builder)
                .build();
    }

    private void reportBrokenClasses(@NotNull RenderLogger logger) {
        Map<String, Throwable> brokenClasses = logger.getBrokenClasses();
        if (brokenClasses == null || brokenClasses.isEmpty()) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        final Module module = logger.getModule();

        for (Throwable throwable : brokenClasses.values()) {
            if (RenderLogger.isIssue164378(throwable)) {
                RenderLogger.addHtmlForIssue164378(throwable, module, myLinkManager, builder, false);
                break;
            }
        }

        builder.add("The following classes could not be instantiated:");

        Throwable firstThrowable = null;
        builder.beginList();
        for (Map.Entry<String, Throwable> entry : brokenClasses.entrySet()) {
            String className = entry.getKey();
            Throwable throwable = entry.getValue();

            builder.listItem().add(className).add(" (").addLink("Open Class",
                    myLinkManager.createOpenClassUrl(className));
            if (throwable != null && module != null) {
                builder.add(", ");
                ShowExceptionFix detailsFix = new ShowExceptionFix(module.getProject(), throwable);
                builder.addLink("Show Exception", myLinkManager.createRunnableLink(detailsFix));
            }
            builder.add(", ").addLink("Clear Cache", myLinkManager.createClearCacheUrl()).add(")");

            if (firstThrowable == null && throwable != null) {
                firstThrowable = throwable;
            }
        }
        builder.endList().addIcon(HtmlBuilderHelper.getTipIconPath())
                .addLink("Tip: Use ", "View.isInEditMode()",
                        " in your custom views to skip code or show sample data when shown in the IDE.",
                        "http://developer.android.com/reference/android/view/View.html#isInEditMode()")
                .newline().newline().add("If this is an unexpected error you can also try to ")
                .addLink("", "build the project", ", then ", myLinkManager.createBuildProjectUrl())
                .addLink("manually ", "refresh the layout", ".", myLinkManager.createRefreshRenderUrl());

        if (firstThrowable != null) {
            builder.newline().newline().addHeading("Exception Details", HtmlBuilderHelper.getHeaderFontColor())
                    .newline();
            reportThrowable(builder, firstThrowable, false);
            reportSandboxError(firstThrowable, true, false);
        }
        builder.newline().newline();

        addIssue().setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY)
                .setSummary("Failed to instantiate one or more classes").setHtmlContent(builder).build();
    }

    private void reportInstantiationProblems(@NotNull final RenderLogger logger) {
        Map<String, Throwable> classesWithIncorrectFormat = logger.getClassesWithIncorrectFormat();
        if (classesWithIncorrectFormat == null || classesWithIncorrectFormat.isEmpty()) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        builder.add("Preview might be incorrect: unsupported class version.").newline()
                .addIcon(HtmlBuilderHelper.getTipIconPath()).add("Tip: ");

        builder.add(
                "You need to run the IDE with the highest JDK version that you are compiling custom views with. ");

        int highest = ClassConverter.findHighestMajorVersion(classesWithIncorrectFormat.values());
        if (highest > 0 && highest > ClassConverter.getCurrentClassVersion()) {
            String required = ClassConverter.classVersionToJdk(highest);
            builder.add("One or more views have been compiled with JDK ").add(required)
                    .add(", but you are running the IDE on JDK ").add(ClassConverter.getCurrentJdkVersion())
                    .add(". ");
        } else {
            builder.add(
                    "For example, if you are compiling with sourceCompatibility 1.7, you must run the IDE with JDK 1.7. ");
        }
        builder.add(
                "Running on a higher JDK is necessary such that these classes can be run in the layout renderer. "
                        + "(Or, extract your custom views into a library which you compile with a lower JDK version.)")
                .newline().newline()
                .addLink("If you have just accidentally built your code with a later JDK, try to ", "build",
                        " the project.", myLinkManager.createBuildProjectUrl())
                .newline().newline().add("Classes with incompatible format:");

        builder.beginList();
        List<String> names = Lists.newArrayList(classesWithIncorrectFormat.keySet());
        Collections.sort(names);
        for (String className : names) {
            builder.listItem();
            builder.add(className);
            //noinspection ThrowableResultOfMethodCallIgnored
            Throwable throwable = classesWithIncorrectFormat.get(className);
            if (throwable instanceof InconvertibleClassError) {
                InconvertibleClassError error = (InconvertibleClassError) throwable;
                builder.add(" (Compiled with ").add(ClassConverter.classVersionToJdk(error.getMajor())).add(")");
            }
        }
        builder.endList();

        Module module = logger.getModule();
        if (module == null) {
            return;
        }
        final List<Module> problemModules = getProblemModules(module);
        if (!problemModules.isEmpty()) {
            builder.add("The following modules are built with incompatible JDK:").newline();
            for (Iterator<Module> it = problemModules.iterator(); it.hasNext();) {
                Module problemModule = it.next();
                builder.add(problemModule.getName());
                if (it.hasNext()) {
                    builder.add(", ");
                }
            }
            builder.newline();
        }

        AndroidFacet facet = AndroidFacet.getInstance(logger.getModule());
        if (facet != null && !facet.requiresAndroidModel()) {
            Project project = logger.getModule().getProject();
            builder.addLink("Rebuild project with '-target 1.6'",
                    myLinkManager.createRunnableLink(new RebuildWith16Fix(project))).newline();

            if (!problemModules.isEmpty()) {
                builder.addLink("Change Java SDK to 1.6",
                        myLinkManager.createRunnableLink(new SwitchTo16Fix(project, problemModules))).newline();
            }
        }

        addIssue().setSeverity(HighlightSeverity.WARNING).setSummary("Some classes have an unsupported version")
                .setHtmlContent(builder).build();
    }

    private void reportUnknownFragments(@NotNull final RenderLogger logger) {
        List<String> fragmentNames = logger.getMissingFragments();
        if (fragmentNames == null || fragmentNames.isEmpty()) {
            return;
        }

        HtmlBuilder builder = new HtmlBuilder();
        builder.add("A ").addHtml("<code>").add("<fragment>").addHtml("</code>")
                .add(" tag allows a layout file to dynamically include " + "different layouts at runtime. ")
                .add("At layout editing time the specific layout to be used is not known. You can choose which layout you would "
                        + "like previewed while editing the layout.");
        builder.beginList();

        // TODO: Add link to not warn any more for this session

        for (final String className : fragmentNames) {
            builder.listItem();
            boolean isIdentified = className != null && !className.isEmpty();
            boolean isActivityKnown = isIdentified && !className.startsWith(PREFIX_RESOURCE_REF);
            if (isIdentified) {
                builder.add("<fragment ").addBold(className).add(" ...>");
            } else {
                builder.add("<fragment>");
            }
            builder.add(" (");

            if (isActivityKnown) {
                final Module module = logger.getModule();
                ApplicationManager.getApplication().runReadAction(() -> {
                    // TODO: Look up layout references in the given layout, if possible
                    // Find activity class
                    // Look for R references in the layout
                    assert module != null;
                    Project project = module.getProject();
                    GlobalSearchScope scope = GlobalSearchScope.allScope(project);
                    PsiClass clz = JavaPsiFacade.getInstance(project).findClass(className, scope);
                    String layoutName = myResult.getFile().getName();
                    boolean separate = false;
                    if (clz != null) {
                        // TODO: Should instead find all R.layout elements
                        // HACK AHEAD!
                        String matchText = clz.getText();
                        final Pattern LAYOUT_FIELD_PATTERN = Pattern.compile("R\\.layout\\.([a-z0-9_]+)");
                        Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
                        Set<String> layouts = Sets.newTreeSet();
                        int index = 0;
                        while (true) {
                            if (matcher.find(index)) {
                                layouts.add(matcher.group(1));
                                index = matcher.end();
                            } else {
                                break;
                            }
                        }
                        for (String layout : layouts) {
                            if (layout.equals(layoutName)) { // Don't include self
                                continue;
                            }
                            if (separate) {
                                builder.add(", ");
                            }
                            builder.addLink("Use @layout/" + layout,
                                    myLinkManager.createAssignLayoutUrl(className, layout));
                            separate = true;
                        }
                    }

                    if (separate) {
                        builder.add(", ");
                    }
                    builder.addLink("Pick Layout...", myLinkManager.createPickLayoutUrl(className));
                });
            } else {
                builder.addLink("Choose Fragment Class...", myLinkManager.createAssignFragmentUrl(className));
            }
            builder.add(")");
        }
        builder.endList().newline()
                // TODO: URLs
                .addLink("Do not warn about <fragment> tags in this session",
                        myLinkManager.createIgnoreFragmentsUrl())
                .newline();

        addIssue().setSeverity(HighlightSeverity.ERROR).setSummary("Unknown fragments").setHtmlContent(builder)
                .build();
    }

    /**
     * Support lib classes will fail to instantiate if the preview is not using the right theme.
     */
    private void reportAppCompatRequired(@NotNull RenderLogger logger) {
        Map<String, Throwable> brokenClasses = logger.getBrokenClasses();

        if (brokenClasses == null || brokenClasses.isEmpty()) {
            return;
        }

        brokenClasses.values().stream().filter(Objects::nonNull).filter(
                t -> t.getMessage() != null && t.getMessage().startsWith("You need to use a Theme.AppCompat"))
                .findAny().ifPresent(t -> addIssue().setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes
                        .setSummary("Using the design library requires using Theme.AppCompat or a descendant")
                        .setHtmlContent(new HtmlBuilder().add("Select ").addItalic("Theme.AppCompat")
                                .add(" or a descendant in the theme selector."))
                        .build());
    }

    public Collection<RenderErrorModel.Issue> reportIssues() {
        RenderLogger logger = myResult.getLogger();
        RenderTask renderTask = myResult.getRenderTask();

        reportMissingStyles(logger);
        reportAppCompatRequired(logger);
        if (renderTask != null) {
            reportOldNinePathRenderLib(logger, renderTask);
            reportRelevantCompilationErrors(logger, renderTask);
            reportMissingSizeAttributes(logger, renderTask);
            reportMissingClasses(logger);
        }
        reportBrokenClasses(logger);
        reportInstantiationProblems(logger);
        reportOtherProblems(logger, renderTask);
        reportUnknownFragments(logger);

        if (renderTask != null) {
            reportRenderingFidelityProblems(logger, renderTask);
        }
        return getIssues();
    }

    protected RenderResult getResult() {
        return myResult;
    }

    protected HtmlLinkManager getLinkManager() {
        return myLinkManager;
    }

    protected Collection<RenderErrorModel.Issue> getIssues() {
        return Collections.unmodifiableCollection(myIssues);
    }

    private static class RebuildWith16Fix implements Runnable {
        private final Project myProject;

        private RebuildWith16Fix(Project project) {
            myProject = project;
        }

        @Override
        public void run() {
            final JpsJavaCompilerOptions settings = JavacConfiguration.getOptions(myProject,
                    JavacConfiguration.class);
            if (settings.ADDITIONAL_OPTIONS_STRING.length() > 0) {
                settings.ADDITIONAL_OPTIONS_STRING += ' ';
            }
            settings.ADDITIONAL_OPTIONS_STRING += "-target 1.6";
            CompilerManager.getInstance(myProject).rebuild(null);
        }
    }

    private static class SwitchTo16Fix implements Runnable {
        final List<Module> myProblemModules;
        private final Project myProject;

        private SwitchTo16Fix(Project project, List<Module> problemModules) {
            myProject = project;
            myProblemModules = problemModules;
        }

        @Override
        public void run() {
            final Set<String> sdkNames = getSdkNamesFromModules(myProblemModules);
            if (sdkNames.size() == 1) {
                final Sdk sdk = ProjectJdkTable.getInstance().findJdk(sdkNames.iterator().next());
                if (sdk != null && sdk.getSdkType() instanceof AndroidSdkType) {
                    final ProjectStructureConfigurable config = ProjectStructureConfigurable.getInstance(myProject);
                    if (ShowSettingsUtil.getInstance().editConfigurable(myProject, config,
                            () -> config.select(sdk, true))) {
                        askAndRebuild(myProject);
                    }
                    return;
                }
            }

            final String moduleToSelect = myProblemModules.size() > 0 ? myProblemModules.iterator().next().getName()
                    : null;
            if (ModulesConfigurator.showDialog(myProject, moduleToSelect, ClasspathEditor.NAME)) {
                askAndRebuild(myProject);
            }
        }
    }

    public static class Provider {
        static final ExtensionPointName<Provider> EP_NAME = new ExtensionPointName<>(
                "com.android.rendering.renderErrorContributor");

        public boolean isApplicable(Project project) {
            return true;
        }

        public RenderErrorContributor getContributor(@NotNull RenderResult result,
                @Nullable DataContext dataContext) {
            return new RenderErrorContributor(result, dataContext);
        }
    }
}