com.android.ide.eclipse.adt.internal.editors.layout.gre.ClientRulesEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.adt.internal.editors.layout.gre.ClientRulesEngine.java

Source

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.ide.eclipse.adt.internal.editors.layout.gre;

import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.AUTO_URI;
import static com.android.SdkConstants.CLASS_FRAGMENT;
import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.URI_PREFIX;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.IValidator;
import com.android.ide.common.api.IViewMetadata;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.Margins;
import com.android.ide.common.api.Rect;
import com.android.ide.common.layout.BaseViewRule;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ViewHierarchy;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult;
import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.ui.MarginChooser;
import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog;
import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.ui.IJavaElementSearchConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension;
import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor;
import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension;
import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Implementation of {@link IClientRulesEngine}. This provides {@link IViewRule} clients
 * with a few methods they can use to access functionality from this {@link RulesEngine}.
 */
class ClientRulesEngine implements IClientRulesEngine {
    /** The return code from the dialog for the user choosing "Clear" */
    public static final int CLEAR_RETURN_CODE = -5;
    /** The dialog button ID for the user choosing "Clear" */
    private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE;

    private final RulesEngine mRulesEngine;
    private final String mFqcn;

    public ClientRulesEngine(RulesEngine rulesEngine, String fqcn) {
        mRulesEngine = rulesEngine;
        mFqcn = fqcn;
    }

    @Override
    public @NonNull String getFqcn() {
        return mFqcn;
    }

    @Override
    public void debugPrintf(@NonNull String msg, Object... params) {
        AdtPlugin.printToConsole(mFqcn == null ? "<unknown>" : mFqcn, String.format(msg, params));
    }

    @Override
    public IViewRule loadRule(@NonNull String fqcn) {
        return mRulesEngine.loadRule(fqcn, fqcn);
    }

    @Override
    public void displayAlert(@NonNull String message) {
        MessageDialog.openInformation(AdtPlugin.getShell(), mFqcn, // title
                message);
    }

    @Override
    public boolean rename(INode node) {
        GraphicalEditorPart editor = mRulesEngine.getEditor();
        SelectionManager manager = editor.getCanvasControl().getSelectionManager();
        RenameResult result = manager.performRename(node, null);

        return !result.isCanceled() && !result.isUnavailable();
    }

    @Override
    public String displayInput(@NonNull String message, @Nullable String value, final @Nullable IValidator filter) {
        IInputValidator validator = null;
        if (filter != null) {
            validator = new IInputValidator() {
                @Override
                public String isValid(String newText) {
                    // IValidator has the same interface as SWT's IInputValidator
                    try {
                        return filter.validate(newText);
                    } catch (Exception e) {
                        AdtPlugin.log(e, "Custom validator failed: %s", e.toString());
                        return ""; //$NON-NLS-1$
                    }
                }
            };
        }

        InputDialog d = new InputDialog(AdtPlugin.getShell(), mFqcn, // title
                message, value == null ? "" : value, //$NON-NLS-1$
                validator) {
            @Override
            protected void createButtonsForButtonBar(Composite parent) {
                createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
                super.createButtonsForButtonBar(parent);
            }

            @Override
            protected void buttonPressed(int buttonId) {
                super.buttonPressed(buttonId);

                if (buttonId == CLEAR_BUTTON_ID) {
                    assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL;
                    setReturnCode(CLEAR_RETURN_CODE);
                    close();
                }
            }
        };
        int result = d.open();
        if (result == ResourceChooser.CLEAR_RETURN_CODE) {
            return "";
        } else if (result == Window.OK) {
            return d.getValue();
        }
        return null;
    }

    @Override
    @Nullable
    public Object getViewObject(@NonNull INode node) {
        ViewHierarchy views = mRulesEngine.getEditor().getCanvasControl().getViewHierarchy();
        CanvasViewInfo vi = views.findViewInfoFor(node);
        if (vi != null) {
            return vi.getViewObject();
        }

        return null;
    }

    @Override
    public @NonNull IViewMetadata getMetadata(final @NonNull String fqcn) {
        return new IViewMetadata() {
            @Override
            public @NonNull String getDisplayName() {
                // This also works when there is no "."
                return fqcn.substring(fqcn.lastIndexOf('.') + 1);
            }

            @Override
            public @NonNull FillPreference getFillPreference() {
                return ViewMetadataRepository.get().getFillPreference(fqcn);
            }

            @Override
            public @NonNull Margins getInsets() {
                return mRulesEngine.getEditor().getCanvasControl().getInsets(fqcn);
            }

            @Override
            public @NonNull List<String> getTopAttributes() {
                return ViewMetadataRepository.get().getTopAttributes(fqcn);
            }
        };
    }

    @Override
    public int getMinApiLevel() {
        Sdk currentSdk = Sdk.getCurrent();
        if (currentSdk != null) {
            IAndroidTarget target = currentSdk.getTarget(mRulesEngine.getEditor().getProject());
            if (target != null) {
                return target.getVersion().getApiLevel();
            }
        }

        return -1;
    }

    @Override
    public IValidator getResourceValidator(@NonNull final String resourceTypeName, final boolean uniqueInProject,
            final boolean uniqueInLayout, final boolean exists, final String... allowed) {
        return new IValidator() {
            private ResourceNameValidator mValidator;

            @Override
            public String validate(@NonNull String text) {
                if (mValidator == null) {
                    ResourceType type = ResourceType.getEnum(resourceTypeName);
                    if (uniqueInLayout) {
                        assert !uniqueInProject;
                        assert !exists;
                        Set<String> existing = new HashSet<String>();
                        Document doc = mRulesEngine.getEditor().getModel().getXmlDocument();
                        if (doc != null) {
                            addIds(doc, existing);
                        }
                        for (String s : allowed) {
                            existing.remove(s);
                        }
                        mValidator = ResourceNameValidator.create(false, existing, type);
                    } else {
                        assert allowed.length == 0;
                        IProject project = mRulesEngine.getEditor().getProject();
                        mValidator = ResourceNameValidator.create(false, project, type);
                        if (uniqueInProject) {
                            mValidator.unique();
                        }
                    }
                    if (exists) {
                        mValidator.exist();
                    }
                }

                return mValidator.isValid(text);
            }
        };
    }

    /** Find declared ids under the given DOM node */
    private static void addIds(Node node, Set<String> ids) {
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element element = (Element) node;
            String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
            if (id != null && id.startsWith(NEW_ID_PREFIX)) {
                ids.add(BaseViewRule.stripIdPrefix(id));
            }
        }

        NodeList children = node.getChildNodes();
        for (int i = 0, n = children.getLength(); i < n; i++) {
            Node child = children.item(i);
            addIds(child, ids);
        }
    }

    @Override
    public String displayReferenceInput(@Nullable String currentValue) {
        GraphicalEditorPart graphicalEditor = mRulesEngine.getEditor();
        LayoutEditorDelegate delegate = graphicalEditor.getEditorDelegate();
        IProject project = delegate.getEditor().getProject();
        if (project != null) {
            // get the resource repository for this project and the system resources.
            ResourceRepository projectRepository = ResourceManager.getInstance().getProjectResources(project);
            Shell shell = AdtPlugin.getShell();
            if (shell == null) {
                return null;
            }
            ReferenceChooserDialog dlg = new ReferenceChooserDialog(project, projectRepository, shell);
            dlg.setPreviewHelper(new ResourcePreviewHelper(dlg, graphicalEditor));

            dlg.setCurrentResource(currentValue);

            if (dlg.open() == Window.OK) {
                return dlg.getCurrentResource();
            }
        }

        return null;
    }

    @Override
    public String displayResourceInput(@NonNull String resourceTypeName, @Nullable String currentValue) {
        return displayResourceInput(resourceTypeName, currentValue, null);
    }

    private String displayResourceInput(String resourceTypeName, String currentValue, IInputValidator validator) {
        ResourceType type = ResourceType.getEnum(resourceTypeName);
        GraphicalEditorPart graphicalEditor = mRulesEngine.getEditor();
        return ResourceChooser.chooseResource(graphicalEditor, type, currentValue, validator);
    }

    @Override
    public String[] displayMarginInput(@Nullable String all, @Nullable String left, @Nullable String right,
            @Nullable String top, @Nullable String bottom) {
        GraphicalEditorPart editor = mRulesEngine.getEditor();
        IProject project = editor.getProject();
        if (project != null) {
            Shell shell = AdtPlugin.getShell();
            if (shell == null) {
                return null;
            }
            AndroidTargetData data = editor.getEditorDelegate().getEditor().getTargetData();
            MarginChooser dialog = new MarginChooser(shell, editor, data, all, left, right, top, bottom);
            if (dialog.open() == Window.OK) {
                return dialog.getMargins();
            }
        }

        return null;
    }

    @Override
    public String displayIncludeSourceInput() {
        AndroidXmlEditor editor = mRulesEngine.getEditor().getEditorDelegate().getEditor();
        IInputValidator validator = CyclicDependencyValidator.create(editor.getInputFile());
        return displayResourceInput(ResourceType.LAYOUT.getName(), null, validator);
    }

    @Override
    public void select(final @NonNull Collection<INode> nodes) {
        LayoutCanvas layoutCanvas = mRulesEngine.getEditor().getCanvasControl();
        final SelectionManager selectionManager = layoutCanvas.getSelectionManager();
        selectionManager.select(nodes);
        // ALSO run an async select since immediately after nodes are created they
        // may not be selectable. We can't ONLY run an async exec since
        // code may depend on operating on the selection.
        layoutCanvas.getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                selectionManager.select(nodes);
            }
        });
    }

    @Override
    public String displayFragmentSourceInput() {
        try {
            // Compute a search scope: We need to merge all the subclasses
            // android.app.Fragment and android.support.v4.app.Fragment
            IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
            IProject project = mRulesEngine.getProject();
            final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
            if (javaProject != null) {
                IType oldFragmentType = javaProject.findType(CLASS_V4_FRAGMENT);

                // First check to make sure fragments are available, and if not,
                // warn the user.
                IAndroidTarget target = Sdk.getCurrent().getTarget(project);
                // No, this should be using the min SDK instead!
                if (target.getVersion().getApiLevel() < 11 && oldFragmentType == null) {
                    // Compatibility library must be present
                    MessageDialog dialog = new MessageDialog(Display.getCurrent().getActiveShell(),
                            "Fragment Warning", null,
                            "Fragments require API level 11 or higher, or a compatibility "
                                    + "library for older versions.\n\n"
                                    + " Do you want to install the compatibility library?",
                            MessageDialog.QUESTION, new String[] { "Install", "Cancel" },
                            1 /* default button: Cancel */);
                    int answer = dialog.open();
                    if (answer == 0) {
                        if (!AddSupportJarAction.install(project)) {
                            return null;
                        }
                    } else {
                        return null;
                    }
                }

                // Look up sub-types of each (new fragment class and compatibility fragment
                // class, if any) and merge the two arrays - then create a scope from these
                // elements.
                IType[] fragmentTypes = new IType[0];
                IType[] oldFragmentTypes = new IType[0];
                if (oldFragmentType != null) {
                    ITypeHierarchy hierarchy = oldFragmentType.newTypeHierarchy(new NullProgressMonitor());
                    oldFragmentTypes = hierarchy.getAllSubtypes(oldFragmentType);
                }
                IType fragmentType = javaProject.findType(CLASS_FRAGMENT);
                if (fragmentType != null) {
                    ITypeHierarchy hierarchy = fragmentType.newTypeHierarchy(new NullProgressMonitor());
                    fragmentTypes = hierarchy.getAllSubtypes(fragmentType);
                }
                IType[] subTypes = new IType[fragmentTypes.length + oldFragmentTypes.length];
                System.arraycopy(fragmentTypes, 0, subTypes, 0, fragmentTypes.length);
                System.arraycopy(oldFragmentTypes, 0, subTypes, fragmentTypes.length, oldFragmentTypes.length);
                scope = SearchEngine.createJavaSearchScope(subTypes, IJavaSearchScope.SOURCES);
            }

            Shell parent = AdtPlugin.getShell();
            final AtomicReference<String> returnValue = new AtomicReference<String>();
            final AtomicReference<SelectionDialog> dialogHolder = new AtomicReference<SelectionDialog>();
            final SelectionDialog dialog = JavaUI.createTypeDialog(parent, new ProgressMonitorDialog(parent), scope,
                    IJavaElementSearchConstants.CONSIDER_CLASSES, false,
                    // Use ? as a default filter to fill dialog with matches
                    "?", //$NON-NLS-1$
                    new TypeSelectionExtension() {
                        @Override
                        public Control createContentArea(Composite parentComposite) {
                            Composite composite = new Composite(parentComposite, SWT.NONE);
                            composite.setLayout(new GridLayout(1, false));
                            Button button = new Button(composite, SWT.PUSH);
                            button.setText("Create New...");
                            button.addSelectionListener(new SelectionAdapter() {
                                @Override
                                public void widgetSelected(SelectionEvent e) {
                                    String fqcn = createNewFragmentClass(javaProject);
                                    if (fqcn != null) {
                                        returnValue.set(fqcn);
                                        dialogHolder.get().close();
                                    }
                                }
                            });
                            return composite;
                        }

                        @Override
                        public ITypeInfoFilterExtension getFilterExtension() {
                            return new ITypeInfoFilterExtension() {
                                @Override
                                public boolean select(ITypeInfoRequestor typeInfoRequestor) {
                                    int modifiers = typeInfoRequestor.getModifiers();
                                    if (!Flags.isPublic(modifiers) || Flags.isInterface(modifiers)
                                            || Flags.isEnum(modifiers)) {
                                        return false;
                                    }
                                    return true;
                                }
                            };
                        }
                    });
            dialogHolder.set(dialog);

            dialog.setTitle("Choose Fragment Class");
            dialog.setMessage("Select a Fragment class (? = any character, * = any string):");
            if (dialog.open() == IDialogConstants.CANCEL_ID) {
                return null;
            }
            if (returnValue.get() != null) {
                return returnValue.get();
            }

            Object[] types = dialog.getResult();
            if (types != null && types.length > 0) {
                return ((IType) types[0]).getFullyQualifiedName();
            }
        } catch (JavaModelException e) {
            AdtPlugin.log(e, null);
        } catch (CoreException e) {
            AdtPlugin.log(e, null);
        }
        return null;
    }

    @Override
    public void redraw() {
        mRulesEngine.getEditor().getCanvasControl().redraw();
    }

    @Override
    public void layout() {
        mRulesEngine.getEditor().recomputeLayout();
    }

    @Override
    public Map<INode, Rect> measureChildren(@NonNull INode parent,
            @Nullable IClientRulesEngine.AttributeFilter filter) {
        RenderService renderService = RenderService.create(mRulesEngine.getEditor());
        Map<INode, Rect> map = renderService.measureChildren(parent, filter);
        if (map == null) {
            map = Collections.emptyMap();
        }
        return map;
    }

    @Override
    public int pxToDp(int px) {
        ConfigurationChooser chooser = mRulesEngine.getEditor().getConfigurationChooser();
        float dpi = chooser.getConfiguration().getDensity().getDpiValue();
        return (int) (px * 160 / dpi);
    }

    @Override
    public int dpToPx(int dp) {
        ConfigurationChooser chooser = mRulesEngine.getEditor().getConfigurationChooser();
        float dpi = chooser.getConfiguration().getDensity().getDpiValue();
        return (int) (dp * dpi / 160);
    }

    @Override
    public int screenToLayout(int pixels) {
        return (int) (pixels / mRulesEngine.getEditor().getCanvasControl().getScale());
    }

    String createNewFragmentClass(IJavaProject javaProject) {
        NewClassWizardPage page = new NewClassWizardPage();

        IProject project = mRulesEngine.getProject();
        IAndroidTarget target = Sdk.getCurrent().getTarget(project);
        String superClass;
        if (target.getVersion().getApiLevel() < 11) {
            superClass = CLASS_V4_FRAGMENT;
        } else {
            superClass = CLASS_FRAGMENT;
        }
        page.setSuperClass(superClass, true /* canBeModified */);
        IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
        if (root != null) {
            page.setPackageFragmentRoot(root, true /* canBeModified */);
        }
        ManifestInfo manifestInfo = ManifestInfo.get(project);
        IPackageFragment pkg = manifestInfo.getPackageFragment();
        if (pkg != null) {
            page.setPackageFragment(pkg, true /* canBeModified */);
        }
        OpenNewClassWizardAction action = new OpenNewClassWizardAction();
        action.setConfiguredWizardPage(page);
        action.run();
        IType createdType = page.getCreatedType();
        if (createdType != null) {
            return createdType.getFullyQualifiedName();
        } else {
            return null;
        }
    }

    @Override
    public @NonNull String getUniqueId(@NonNull String fqcn) {
        UiDocumentNode root = mRulesEngine.getEditor().getModel();
        String prefix = fqcn.substring(fqcn.lastIndexOf('.') + 1);
        prefix = Character.toLowerCase(prefix.charAt(0)) + prefix.substring(1);
        return DescriptorsUtils.getFreeWidgetId(root, prefix);
    }

    @Override
    public @NonNull String getAppNameSpace() {
        IProject project = mRulesEngine.getEditor().getProject();

        ProjectState projectState = Sdk.getProjectState(project);
        if (projectState != null && projectState.isLibrary()) {
            return AUTO_URI;
        }

        ManifestInfo info = ManifestInfo.get(project);
        return URI_PREFIX + info.getPackage();
    }
}