org.eclipse.wb.internal.swt.model.layout.LayoutDataInfo.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wb.internal.swt.model.layout.LayoutDataInfo.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.internal.swt.model.layout;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import org.eclipse.wb.core.eval.AstEvaluationEngine;
import org.eclipse.wb.core.eval.EvaluationContext;
import org.eclipse.wb.core.model.JavaInfo;
import org.eclipse.wb.core.model.ObjectInfo;
import org.eclipse.wb.core.model.broadcast.JavaEventListener;
import org.eclipse.wb.core.model.broadcast.JavaInfoAddProperties;
import org.eclipse.wb.core.model.broadcast.ObjectEventListener;
import org.eclipse.wb.core.model.broadcast.ObjectInfoTreeComplete;
import org.eclipse.wb.internal.core.model.JavaInfoEvaluationHelper;
import org.eclipse.wb.internal.core.model.JavaInfoUtils;
import org.eclipse.wb.internal.core.model.creation.ConstructorCreationSupport;
import org.eclipse.wb.internal.core.model.creation.CreationSupport;
import org.eclipse.wb.internal.core.model.description.ComponentDescription;
import org.eclipse.wb.internal.core.model.generation.GenerationSettings;
import org.eclipse.wb.internal.core.model.generation.statement.block.BlockStatementGeneratorDescription;
import org.eclipse.wb.internal.core.model.presentation.IObjectPresentation;
import org.eclipse.wb.internal.core.model.property.ComplexProperty;
import org.eclipse.wb.internal.core.model.property.GenericPropertyImpl;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.property.accessor.ExpressionAccessor;
import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
import org.eclipse.wb.internal.core.model.util.ScriptUtils;
import org.eclipse.wb.internal.core.model.variable.EmptyVariableSupport;
import org.eclipse.wb.internal.core.model.variable.LocalUniqueVariableSupport;
import org.eclipse.wb.internal.core.model.variable.VariableSupport;
import org.eclipse.wb.internal.core.utils.ast.AstEditor;
import org.eclipse.wb.internal.core.utils.ast.AstNodeUtils;
import org.eclipse.wb.internal.core.utils.ast.DomGenerics;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
import org.eclipse.wb.internal.core.utils.state.EditorState;
import org.eclipse.wb.internal.swt.model.widgets.CompositeInfo;
import org.eclipse.wb.internal.swt.model.widgets.ControlInfo;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.RowData;

import org.apache.commons.lang.StringUtils;

import java.util.List;
import java.util.Map;

/**
 * Model for layout data object, for example {@link GridData}, {@link RowData}, etc.
 * 
 * @author scheglov_ke
 * @author lobas_av
 * @coverage swt.model.layout
 */
public abstract class LayoutDataInfo extends JavaInfo implements ILayoutDataInfo {
    final LayoutDataInfo m_this = this;

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public LayoutDataInfo(AstEditor editor, ComponentDescription description, CreationSupport creationSupport)
            throws Exception {
        super(editor, description, creationSupport);
        removeIfCompositeHasNoLayout();
        contributeLayoutDataProperties_toControl();
        addMaterializeSupport();
        turnIntoBlock_whenEnsureVariable();
        new LayoutDataNameSupport(this);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Broadcast events
    //
    ////////////////////////////////////////////////////////////////////////////
    private void removeIfCompositeHasNoLayout() {
        addBroadcastListener(new ObjectInfoTreeComplete() {
            public void invoke() throws Exception {
                removeBroadcastListener(this);
                // if dangling LayoutData, ignore it
                if (getParent() == null) {
                    return;
                }
                if (!getParent().getChildren().contains(m_this)) {
                    return;
                }
                // if no parent with Layout, remove this LayoutData
                if (!hasCompositeLayout()) {
                    getParent().removeChild(m_this);
                }
            }

            private boolean hasCompositeLayout() {
                ObjectInfo composite = getParent().getParent();
                if (composite != null && wantsLayoutData_forChildChild(composite.getParent())) {
                    return true;
                }
                if (composite instanceof CompositeInfo) {
                    List<LayoutInfo> layouts = composite.getChildren(LayoutInfo.class);
                    if (layouts.isEmpty()) {
                        return false;
                    }
                    return isCompatibleWithLayout(layouts.get(0));
                }
                return false;
            }

            private boolean wantsLayoutData_forChildChild(ObjectInfo object) {
                if (object != null) {
                    List<JavaInfo> children = object.getChildren(JavaInfo.class);
                    for (JavaInfo child : children) {
                        if (JavaInfoUtils.hasTrueParameter(child, "layout.managesChildChild")) {
                            return true;
                        }
                    }
                }
                return false;
            }
        });
    }

    /**
     * @return <code>true</code> if this {@link LayoutDataInfo} with given {@link LayoutInfo}.
     */
    protected boolean isCompatibleWithLayout(LayoutInfo layout) {
        {
            String compatibleLayout = JavaInfoUtils.getParameter(this, "layoutData.compatibleLayout");
            if (compatibleLayout != null) {
                return ReflectionUtils.isSuccessorOf(layout.getDescription().getComponentClass(), compatibleLayout);
            }
        }
        return true;
    }

    /**
     * Contribute "LayoutData" complex property to our {@link ControlInfo}.
     */
    private void contributeLayoutDataProperties_toControl() {
        addBroadcastListener(new JavaInfoAddProperties() {
            public void invoke(JavaInfo javaInfo, List<Property> properties) throws Exception {
                if (isActiveForControl(javaInfo)) {
                    addLayoutDataProperties(properties);
                }
            }

            private boolean isActiveForControl(JavaInfo control) {
                return control.getChildren().contains(m_this);
            }
        });
    }

    /**
     * When we set value of property for virtual {@link LayoutDataInfo}, often we want to set this
     * value in constructor. But constructor does not exists yet, because our {@link LayoutDataInfo}
     * is virtual. So virtual {@link CreationSupport} adds no any new {@link ExpressionAccessor}. This
     * method adds the listener which forces the {@link LayoutDataInfo} materialization, so
     * {@link VariableSupport} has a chance to replace the {@link CreationSupport} instance for
     * underlying {@link JavaInfo}.
     */
    private void addMaterializeSupport() {
        addBroadcastListener(new JavaEventListener() {
            @Override
            public void setPropertyExpression(GenericPropertyImpl property, String[] source, Object[] value,
                    boolean[] shouldSet) throws Exception {
                if (property.getJavaInfo() == m_this) {
                    materialize();
                }
            }
        });
    }

    protected final void materialize() throws Exception {
        if (isVirtual()) {
            ((VirtualLayoutDataVariableSupport) getVariableSupport()).materialize();
        }
    }

    /**
     * @return <code>true</code> if this {@link LayoutDataInfo} is virtual.
     */
    private boolean isVirtual() {
        return getVariableSupport() instanceof VirtualLayoutDataVariableSupport;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Code generation
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Performs following optimizations:
     * <ul>
     * <li>When <code>LayoutData</code> gets converted from simple
     * <code>setLayoutData(new SomeData())</code> into real variable, wrap its {@link Statement}s with
     * {@link Block}.</li>
     * <li>When <code>LayoutData</code> has {@link LocalUniqueVariableSupport}, but it is used just to
     * call <code>setLayoutData()</code>, then variable may be inlined, and enclosing {@link Block}
     * probably too.</li>
     * <li>When <code>LayoutData</code> has all default values, then we can delete it at all.</li>
     * </ul>
     */
    private void turnIntoBlock_whenEnsureVariable() {
        // empty -> Block
        addBroadcastListener(new JavaEventListener() {
            @Override
            public void variable_emptyMaterializeBefore(EmptyVariableSupport variableSupport) throws Exception {
                if (variableSupport.getJavaInfo() == m_this && isBlockMode()) {
                    ASTNode creationNode = variableSupport.getInitializer();
                    Statement creationStatement = AstNodeUtils.getEnclosingStatement(creationNode);
                    getEditor().encloseInBlock(creationStatement);
                }
            }

            private boolean isBlockMode() {
                GenerationSettings settings = getDescription().getToolkit().getGenerationSettings();
                return settings.getStatement() == BlockStatementGeneratorDescription.INSTANCE;
            }
        });
        // no invocations/fields -> inline Block
        addBroadcastListener(new ObjectEventListener() {
            @Override
            public void endEdit_aboutToRefresh() throws Exception {
                if (getVariableSupport() instanceof LocalUniqueVariableSupport) {
                    LocalUniqueVariableSupport variableSupport = (LocalUniqueVariableSupport) getVariableSupport();
                    if (variableSupport.canInline()) {
                        variableSupport.inline();
                        inlineBlockIfSingleStatement();
                    }
                }
            }

            private void inlineBlockIfSingleStatement() throws Exception {
                ASTNode node = ((EmptyVariableSupport) getVariableSupport()).getInitializer();
                Statement statement = AstNodeUtils.getEnclosingStatement(node);
                if (statement != null) {
                    Block block = (Block) statement.getParent();
                    if (block.statements().size() == 1) {
                        getEditor().inlineBlock(block);
                    }
                }
            }
        });
        // is default -> delete
        addBroadcastListener(new ObjectEventListener() {
            @Override
            public void endEdit_aboutToRefresh() throws Exception {
                if (!isDeleted() && getCreationSupport() instanceof ConstructorCreationSupport
                        && getMethodInvocations().isEmpty() && getFieldAssignments().isEmpty()) {
                    ConstructorCreationSupport creationSupport = (ConstructorCreationSupport) getCreationSupport();
                    ClassInstanceCreation creation = creationSupport.getCreation();
                    String signature = creationSupport.getDescription().getSignature();
                    // prepare arguments
                    List<Expression> arguments = DomGenerics.arguments(creation);
                    if (!AstNodeUtils.areLiterals(arguments)) {
                        return;
                    }
                    // evaluate arguments
                    List<Object> argumentValues;
                    {
                        EditorState state = JavaInfoUtils.getState(m_this);
                        EvaluationContext context = new EvaluationContext(state.getEditorLoader(),
                                state.getFlowDescription());
                        argumentValues = Lists.newArrayList();
                        for (Expression argument : arguments) {
                            Object value = AstEvaluationEngine.evaluate(context, argument);
                            JavaInfoEvaluationHelper.setValue(argument, value);
                            argumentValues.add(value);
                        }
                    }
                    // delete, if default constructor arguments
                    if (isDefault(signature, argumentValues)) {
                        delete();
                    }
                }
            }

            /**
             * @return <code>true</code> if existing <code>isDefault</code> script says that object of
             *         this {@link LayoutDataInfo} is in default state.
             */
            private boolean isDefault(String signature, List<Object> args) throws Exception {
                String script = JavaInfoUtils.getParameter(m_this, "isDefault");
                if (script != null) {
                    Map<String, Object> variables = ImmutableMap.of("signature", signature, "args", args);
                    return (Boolean) ScriptUtils.evaluate(JavaInfoUtils.getClassLoader(m_this), script, variables);
                }
                return false;
            }
        });
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // "LayoutData" property
    //
    ////////////////////////////////////////////////////////////////////////////
    private ComplexProperty m_complexProperty;

    /**
     * Adds properties of this {@link LayoutDataInfo} to the properties of its {@link ControlInfo}.
     */
    private void addLayoutDataProperties(List<Property> properties) throws Exception {
        // prepare complex property
        if (m_complexProperty == null) {
            String text;
            {
                Class<?> componentClass = getDescription().getComponentClass();
                text = "(" + componentClass.getName() + ")";
            }
            // prepare ComplexProperty
            m_complexProperty = new ComplexProperty("LayoutData", text) {
                @Override
                public boolean isModified() throws Exception {
                    return true;
                }

                @Override
                public void setValue(Object value) throws Exception {
                    if (value == UNKNOWN_VALUE) {
                        delete();
                    }
                }
            };
            m_complexProperty.setCategory(PropertyCategory.system(5));
            // set sub-properties
            m_complexProperty.setProperties(getFilteredProperties());
        }
        // add property
        properties.add(m_complexProperty);
    }

    /**
     * @return the {@link Property}'s to show in complex property of {@link LayoutDataInfo} parent.
     */
    private Property[] getFilteredProperties() throws Exception {
        Property[] properties = getProperties();
        // For some layout data it needs to exclude some properties, such as "Class" or "Constructor".
        // This can be done using "layoutData.exclude-properties" parameter of class description.
        String propertiesExcludeString = JavaInfoUtils.getParameter(this, "layoutData.exclude-properties");
        if (propertiesExcludeString != null) {
            List<Property> filteredProperties = Lists.newArrayList();
            String[] propertiesExclude = StringUtils.split(propertiesExcludeString);
            props: for (Property property : properties) {
                for (String propertyExclude : propertiesExclude) {
                    if (property.getTitle().equals(propertyExclude)) {
                        continue props;
                    }
                }
                filteredProperties.add(property);
            }
            properties = filteredProperties.toArray(new Property[filteredProperties.size()]);
        }
        return properties;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Presentation
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public final IObjectPresentation getPresentation() {
        return new LayoutDataPresentation(this);
    }
}