com.android.tools.idea.uibuilder.actions.GenerateLayoutTestSkeletonAction.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.uibuilder.actions.GenerateLayoutTestSkeletonAction.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.uibuilder.actions;

import com.android.ide.common.rendering.api.ViewInfo;
import com.android.tools.idea.rendering.AttributeSnapshot;
import com.android.tools.idea.templates.TemplateUtils;
import com.android.tools.idea.uibuilder.editor.NlEditor;
import com.android.tools.idea.uibuilder.editor.NlPreviewManager;
import com.android.tools.idea.uibuilder.model.NlComponent;
import com.android.tools.idea.uibuilder.model.NlModel;
import com.android.tools.idea.uibuilder.surface.DesignSurface;
import com.android.tools.idea.uibuilder.surface.ScreenView;
import com.android.tools.idea.uibuilder.util.NlTreeDumper;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilBase;
import icons.AndroidIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

import static com.android.SdkConstants.*;

public class GenerateLayoutTestSkeletonAction extends AnAction {
    private static final Pattern XML_PROLOG = Pattern.compile("^<\\?xml version.*$");

    public GenerateLayoutTestSkeletonAction() {
        super("Generate LayoutTest Skeleton");
    }

    @Override
    public void update(AnActionEvent event) {
        event.getPresentation().setEnabled(getModel(event.getProject()) != null);
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent event) {
        NlModel model = getModel(event.getProject());
        if (model == null) {
            return;
        }
        int option = Messages.showDialog(model.getProject(),
                "Generate LayoutTest skeleton with the current layout components.", "Generate LayoutTest Skeleton",
                new String[] { "Copy to Clipboard", "Cancel" }, 0, AndroidIcons.AndroidTestRoot);

        if (option == 0) {
            CopyPasteManager.getInstance().setContents(new StringSelection(generateModelFixture(model)));
        }
    }

    @Nullable
    private static NlModel getModel(@Nullable Project project) {
        if (project == null) {
            return null;
        }
        DesignSurface surface = getSurface(project);
        if (surface == null) {
            return null;
        }
        ScreenView screenView = surface.getCurrentScreenView();
        if (screenView == null) {
            return null;
        }
        return screenView.getModel();
    }

    @Nullable
    private static DesignSurface getSurface(@NotNull Project project) {
        FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
        FileEditor[] editors = fileEditorManager.getSelectedEditors();
        for (FileEditor fileEditor : editors) {
            if (fileEditor instanceof NlEditor) {
                return ((NlEditor) fileEditor).getComponent().getSurface();
            }
        }

        Editor editor = fileEditorManager.getSelectedTextEditor();
        if (editor == null) {
            return null;
        }

        NlPreviewManager previewManager = NlPreviewManager.getInstance(project);
        if (previewManager.isWindowVisible()) {
            return previewManager.getPreviewForm().getSurface();
        }

        PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project);
        if (file == null) {
            return null;
        }

        for (FileEditor fileEditor : fileEditorManager.getEditors(file.getVirtualFile())) {
            if (fileEditor instanceof NlEditor) {
                return ((NlEditor) fileEditor).getComponent().getSurface();
            }
        }

        return null;
    }

    @NotNull
    private static String generateModelFixture(@NotNull NlModel model) {
        List<NlComponent> components = model.getComponents();
        StringBuilder builder = new StringBuilder();
        builder.append("import com.android.tools.idea.uibuilder.LayoutTestCase;\n")
                .append("import com.android.tools.idea.uibuilder.fixtures.ModelBuilder;\n")
                .append("import com.android.tools.idea.uibuilder.model.NlModel;\n")
                .append("import com.android.tools.idea.uibuilder.util.NlTreeDumper;\n")
                .append("import org.jetbrains.annotations.NotNull;\n").append("\n")
                .append("import static com.android.SdkConstants.*;\n").append("\n")
                .append("public class NewTest extends LayoutTestCase {\n").append("\n")
                .append("  // TODO: Rename this test method\n").append("  public void testRenameThis() {\n")
                .append("    NlModel model = createModel();\n").append("  }\n").append("\n").append("  @NotNull\n")
                .append("  private NlModel createModel() {\n").append("    ModelBuilder builder = model(\"")
                .append(model.getFile().getName()).append("\",\n");

        for (NlComponent component : components) {
            appendComponent(component, "\"    ModelBuilder builder = model(".length(), builder);
        }

        builder.append(");\n");

        builder.append("    NlModel model = builder.build();\n").append("    format(model.getFile());\n")
                .append("    assertEquals(").append(components.size()).append(", model.getComponents().size());\n");

        appendTreeComparison(components, builder);
        appendXmlComparison(model, builder);
        builder.append("\n").append("    return model;\n").append("  }\n").append("}\n");

        return builder.toString();
    }

    private static void appendComponent(@NotNull NlComponent component, int indent,
            @NotNull StringBuilder builder) {
        builder.append(StringUtil.repeat(" ", indent)).append("component(")
                .append(getTagSymbol(component.getTagName())).append(")\n")
                .append(StringUtil.repeat(" ", indent + 2)).append(makeBounds(component)).append("\n");

        for (AttributeSnapshot attribute : component.getAttributes()) {
            appendAttribute(attribute, indent + 4, builder);
        }

        if (component.getChildCount() > 0) {
            builder.append(StringUtil.repeat(" ", indent + 2)).append(".children(\n");
            for (NlComponent child : component.getChildren()) {
                appendComponent(child, indent + 4, builder);
                // Replace last \n with a comma separator:
                builder.replace(builder.length() - 1, builder.length(), ",\n\n");
            }
            // Replace last comma separator with end paren
            builder.replace(builder.length() - 3, builder.length(), ")\n");
        }
    }

    private static void appendAttribute(@NotNull AttributeSnapshot attribute, int indent,
            @NotNull StringBuilder builder) {
        if (implicitAttribute(attribute)) {
            return;
        }
        assert attribute.value != null;
        if (ANDROID_NS_NAME.equals(attribute.prefix)) {
            switch (attribute.name) {
            case ATTR_ID:
                appendAttribute(indent, ".id", attribute.value, builder);
                return;
            case ATTR_TEXT:
                appendAttribute(indent, ".text", attribute.value, builder);
                return;
            case ATTR_LAYOUT_HEIGHT:
                appendHeightOrWidth(indent, attribute.value, "height", builder);
                return;
            case ATTR_LAYOUT_WIDTH:
                appendHeightOrWidth(indent, attribute.value, "width", builder);
                return;
            }
        }
        appendAttribute(indent, ".withAttribute", attribute.prefix, attribute.name, attribute.value, builder);
    }

    private static void appendHeightOrWidth(int indent, @NotNull String value, @NotNull String orientation,
            @NotNull StringBuilder builder) {
        switch (value) {
        case VALUE_WRAP_CONTENT:
            appendAttribute(indent, ".wrapContent" + StringUtil.capitalize(orientation), builder);
            break;
        case VALUE_MATCH_PARENT:
            appendAttribute(indent, ".matchParent" + StringUtil.capitalize(orientation), builder);
            break;
        default:
            appendAttribute(indent, "." + orientation, value, builder);
            break;
        }
    }

    private static void appendAttribute(int indent, @NotNull String with, @NotNull StringBuilder builder) {
        builder.append(StringUtil.repeat(" ", indent)).append(with).append("()\n");
    }

    private static void appendAttribute(int indent, @NotNull String with, @Nullable String value,
            @NotNull StringBuilder builder) {
        appendAttribute(indent, with, null, null, value, builder);
    }

    private static void appendAttribute(int indent, @NotNull String with, @Nullable String prefix,
            @Nullable String name, @Nullable String value, @NotNull StringBuilder builder) {
        if (value == null) {
            return;
        }
        builder.append(StringUtil.repeat(" ", indent)).append(with).append("(\"");
        if (prefix != null) {
            builder.append(prefix).append(":");
        }
        if (name != null) {
            builder.append(name).append("\", \"");
        }
        builder.append(value).append("\")\n");
    }

    private static boolean implicitAttribute(@NotNull AttributeSnapshot attribute) {
        if (XMLNS.equals(attribute.prefix)) {
            return true;
        }
        if (attribute.value == null) {
            return true;
        }
        return false;
    }

    @NotNull
    private static String makeBounds(@NotNull NlComponent component) {
        Rectangle bounds = getBounds(component);
        return String.format(".withBounds(%d, %d, %d, %d)", bounds.x, bounds.y, bounds.width, bounds.height);
    }

    @NotNull
    private static Rectangle getBounds(@NotNull NlComponent component) {
        NlComponent parent = component.getParent();
        Rectangle parentBounds = parent != null ? getBounds(parent) : new Rectangle(0, 0, 1000, 1000);
        ViewInfo viewInfo = component.viewInfo;
        if (viewInfo == null) {
            return new Rectangle(parentBounds.x, parentBounds.y, 100, 100);
        }
        return new Rectangle(viewInfo.getLeft() + parentBounds.x, viewInfo.getTop() + parentBounds.y,
                viewInfo.getRight() - viewInfo.getLeft(), viewInfo.getBottom() - viewInfo.getTop());
    }

    @NotNull
    private static String getTagSymbol(@NotNull String tagName) {
        tagName = tagName.substring(1 + tagName.lastIndexOf('.'));
        return TemplateUtils.camelCaseToUnderlines(tagName).toUpperCase(Locale.ROOT);
    }

    private static void appendTreeComparison(@NotNull List<NlComponent> components,
            @NotNull StringBuilder builder) {
        appendMultilineAssert(NlTreeDumper.dumpTree(components), "NlTreeDumper.dumpTree(model.getComponents())",
                builder, ImmutableList.of(), false);
    }

    private static void appendXmlComparison(@NotNull NlModel model, @NotNull StringBuilder builder) {
        appendMultilineAssert(model.getFile().getText(), "model.getFile().getText()", builder,
                ImmutableList.of(XML_PROLOG), true);
    }

    private static void appendMultilineAssert(@NotNull String expectedMultilineResult, @NotNull String subject,
            @NotNull StringBuilder builder, @NotNull List<Pattern> lineFilters, boolean addNewlineToLastLine) {
        String assertEquals = "    assertEquals(";
        builder.append(assertEquals);

        String lastLine = "";
        int indent = 0;
        for (String line : Splitter.on("\n").split(expectedMultilineResult)) {
            if (matches(line, lineFilters)) {
                continue;
            }
            builder.append(StringUtil.repeat(" ", indent));
            builder.append("\"").append(line.replace("\"", "\\\"")).append("\\n\" +\n");
            indent = assertEquals.length();
            lastLine = line;
        }
        if (indent > 0) {
            builder.setLength(builder.length() - 6);
            if (lastLine.isEmpty()) {
                builder.setLength(builder.length() - 1 - indent - 6);
            }
            if (addNewlineToLastLine) {
                builder.append("\\n");
            }
            builder.append("\",\n");
        } else {
            builder.append("\"\",\n");
        }
        builder.append(StringUtil.repeat(" ", assertEquals.length()));
        builder.append(subject).append(");\n");
    }

    private static boolean matches(@NotNull String line, @NotNull List<Pattern> lineFilters) {
        for (Pattern pattern : lineFilters) {
            if (pattern.matcher(line).matches()) {
                return true;
            }
        }
        return false;
    }
}