com.google.gdt.eclipse.designer.util.GwtInvocationEvaluatorInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gdt.eclipse.designer.util.GwtInvocationEvaluatorInterceptor.java

Source

/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * 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
 *
 * 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.google.gdt.eclipse.designer.util;

import com.google.common.collect.Maps;
import com.google.gdt.eclipse.designer.IExceptionConstants;

import org.eclipse.wb.core.eval.AstEvaluationEngine;
import org.eclipse.wb.core.eval.DefaultMethodInterceptor;
import org.eclipse.wb.core.eval.EvaluationContext;
import org.eclipse.wb.core.eval.InvocationEvaluatorInterceptor;
import org.eclipse.wb.internal.core.model.description.ComponentDescription;
import org.eclipse.wb.internal.core.model.description.helpers.ComponentDescriptionHelper;
import org.eclipse.wb.internal.core.model.util.PlaceholderUtils;
import org.eclipse.wb.internal.core.model.util.ScriptUtils;
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.exception.DesignerException;
import org.eclipse.wb.internal.core.utils.exception.FatalDesignerException;
import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
import org.eclipse.wb.internal.core.utils.state.EditorState;

import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Map;

/**
 * Resolves methods of <code>JavaScriptObject</code> objects.
 * 
 * @author scheglov_ke
 * @coverage gwt.util
 */
public final class GwtInvocationEvaluatorInterceptor extends InvocationEvaluatorInterceptor {
    private static final String JSO_NAME = "com.google.gwt.core.client.JavaScriptObject";

    ////////////////////////////////////////////////////////////////////////////
    //
    // InvocationEvaluatorInterceptor
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public Method resolveMethod(Class<?> clazz, String signature) throws Exception {
        if (isJavaScriptObject_rewrittenInterface(clazz)) {
            String implClassName = clazz.getName() + "$";
            Class<?> implClass = clazz.getClassLoader().loadClass(implClassName);
            return ReflectionUtils.getMethodBySignature(implClass, signature);
        }
        return null;
    }

    @Override
    public Object evaluateAnonymous(EvaluationContext context, ClassInstanceCreation expression,
            ITypeBinding typeBinding, ITypeBinding typeBindingConcrete, IMethodBinding methodBinding,
            Object[] arguments) throws Exception {
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.user.client.Command")) {
            return null;
        }
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.text.shared.Renderer")) {
            return null;
        }
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.cell.client.Cell")) {
            return getCellFake(context, typeBinding, methodBinding, arguments);
        }
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.view.client.TreeViewModel")) {
            return getTreeViewModelFake(context, typeBinding, methodBinding, arguments);
        }
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.user.cellview.client.Column")) {
            return getColumnFake(context, typeBinding);
        }
        if (AstNodeUtils.isSuccessorOf(typeBinding, "com.google.gwt.user.cellview.client.Header")) {
            return AstEvaluationEngine.createAnonymousInstance(context, methodBinding, arguments);
        }
        return AstEvaluationEngine.UNKNOWN;
    }

    /**
     * Support evaluating <code>com.google.gwt.cell.client.Cell</code> instances.
     * 
     * @return instance with overridden "renderer" method.
     */
    private Object getCellFake(EvaluationContext context, ITypeBinding typeBinding, IMethodBinding methodBinding,
            Object[] arguments) throws Exception {
        final String renderSignature = MessageFormat.format("render({0},{1},{2})",
                "com.google.gwt.cell.client.Cell.Context", "java.lang.Object",
                "com.google.gwt.safehtml.shared.SafeHtmlBuilder");
        return AstEvaluationEngine.createAnonymousInstance(context, methodBinding, arguments,
                new DefaultMethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                            throws Throwable {
                        String signature = ReflectionUtils.getMethodSignature(method);
                        if (renderSignature.equals(signature)) {
                            Object safeHtmlBuilder = args[2];
                            String toAppend = "<rendered Cell value>";
                            ReflectionUtils.invokeMethod(safeHtmlBuilder, "appendEscaped(java.lang.String)",
                                    toAppend);
                            return null;
                        }
                        return super.intercept(obj, method, args, proxy);
                    }
                });
    }

    /**
     * Support evaluating <code>com.google.gwt.view.client.TreeViewModel</code> instances.
     * 
     * @return Fake <code>com.google.gwt.view.client.TreeViewModel</code> instance.
     */
    private Object getTreeViewModelFake(EvaluationContext context, ITypeBinding typeBinding,
            IMethodBinding methodBinding, Object[] arguments) throws Exception {
        ClassLoader classLoader = context.getClassLoader();
        final Object nodeInfoFake = TreeViewModelSupport.getNodeInfoFake(classLoader);
        return AstEvaluationEngine.createAnonymousInstance(context, methodBinding, arguments,
                new DefaultMethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                            throws Throwable {
                        // intercept com.google.gwt.view.client.TreeViewModel.getNodeInfo(T)
                        if ("getNodeInfo".equals(method.getName())) {
                            Class<?>[] parameterTypes = method.getParameterTypes();
                            if (parameterTypes.length == 1) {
                                return nodeInfoFake;
                            }
                        }
                        // intercept com.google.gwt.view.client.TreeViewModel.isLeaf(Object)
                        if ("isLeaf".equals(method.getName())) {
                            Class<?>[] parameterTypes = method.getParameterTypes();
                            if (parameterTypes.length == 1 && "java.lang.Object"
                                    .equals(ReflectionUtils.getFullyQualifiedName(parameterTypes[0], false))) {
                                if (args[0] == nodeInfoFake) {
                                    return Boolean.TRUE;
                                } else {
                                    return Boolean.FALSE;
                                }
                            }
                        }
                        return super.intercept(obj, method, args, proxy);
                    }
                });
    }

    /**
     * Support for evaluating <code>com.google.gwt.view.client.Column</code> instances.
     * 
     * @return the instance of <code>TextColumn</code>.
     */
    private Object getColumnFake(EvaluationContext context, ITypeBinding typeBinding) throws Exception {
        String columnText;
        {
            String columnValueTypeName = getColumnValueTypeName(typeBinding);
            String shortTypeName = CodeUtils.getShortClass(columnValueTypeName);
            columnText = "<" + StringUtils.substring(shortTypeName, 0, 20) + ">";
        }
        // create TextColumn
        ClassLoader classLoader = context.getClassLoader();
        return createTextColumn(classLoader, columnText);
    }

    /**
     * @return the instance of <code>TextColumn</code> for displaying static text;
     */
    public static Object createTextColumn(ClassLoader classLoader, final String columnText)
            throws ClassNotFoundException {
        Class<?> classTextColumn = classLoader.loadClass("com.google.gwt.user.cellview.client.TextColumn");
        // prepare Enhancer
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classTextColumn);
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if (ReflectionUtils.getMethodSignature(method).equals("getValue(java.lang.Object)")) {
                    return columnText;
                }
                return proxy.invokeSuper(obj, args);
            }
        });
        // create instance
        return enhancer.create();
    }

    /**
     * @return the name of <code>getValue()</code> return type.
     */
    private String getColumnValueTypeName(ITypeBinding columnTypeBinding) {
        ITypeBinding cellArgumentTypeBinding = AstNodeUtils.getTypeBindingArgument(columnTypeBinding,
                "com.google.gwt.user.cellview.client.Column", 1);
        return AstNodeUtils.getFullyQualifiedName(cellArgumentTypeBinding, false);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Invocation
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public Object evaluate(EvaluationContext context, MethodInvocation invocation, IMethodBinding methodBinding,
            Class<?> clazz, Method method, Object[] argumentValues) {
        // Panel.add(Widget) throws exception, so make it fatal
        if (ReflectionUtils.getMethodSignature(method).equals("add(com.google.gwt.user.client.ui.Widget)")) {
            if (method.getDeclaringClass().getName().equals("com.google.gwt.user.client.ui.Panel")) {
                FatalDesignerException e = new FatalDesignerException(IExceptionConstants.PANEL_ADD_INVOCATION,
                        invocation.toString());
                e.setSourcePosition(invocation.getStartPosition());
                throw e;
            }
        }
        return AstEvaluationEngine.UNKNOWN;
    }

    @Override
    public Object evaluate(EvaluationContext context, ClassInstanceCreation expression, ITypeBinding typeBinding,
            Class<?> clazz, Constructor<?> actualConstructor, Object[] arguments) throws Exception {
        if (isWidget(clazz)) {
            return evaluateGWT(context, expression, clazz, actualConstructor, arguments);
        }
        return AstEvaluationEngine.UNKNOWN;
    }

    private Object evaluateGWT(EvaluationContext context, ClassInstanceCreation expression, Class<?> clazz,
            Constructor<?> actualConstructor, Object[] arguments) throws Exception {
        PlaceholderUtils.clear(expression);
        // ignore Google Map
        if (clazz.getName().equals("com.google.gwt.maps.client.MapWidget")) {
            PlaceholderUtils.markPlaceholder(expression);
            return createPlaceholder(clazz);
        }
        // fix arguments
        fixArguments(clazz, actualConstructor, arguments);
        // ValueLabel++
        if (clazz.getName().equals("com.google.gwt.user.client.ui.ValueLabel")) {
            return createValueLabel(actualConstructor, arguments);
        }
        if (ReflectionUtils.isSuccessorOf(clazz, "com.google.gwt.user.client.ui.DateLabel")) {
            return createDateLabel(expression, actualConstructor, arguments);
        }
        if (ReflectionUtils.isSuccessorOf(clazz, "com.google.gwt.user.client.ui.NumberLabel")) {
            return createNumberLabel(expression, actualConstructor, arguments);
        }
        // try actual constructor
        try {
            return actualConstructor.newInstance(arguments);
        } catch (Throwable e) {
            context.addException(expression, e);
            PlaceholderUtils.addException(expression, e);
        }
        // some exception happened, try default constructor (if actual was not default)
        try {
            Constructor<?> defaultConstructor = ReflectionUtils.getConstructorBySignature(clazz, "<init>()");
            if (defaultConstructor != null && !ReflectionUtils.equals(actualConstructor, defaultConstructor)) {
                return defaultConstructor.newInstance();
            }
        } catch (Throwable e) {
            context.addException(expression, e);
            PlaceholderUtils.addException(expression, e);
        }
        // still no success, use placeholder
        PlaceholderUtils.markPlaceholder(expression);
        return createPlaceholder(clazz);
    }

    /**
     * Tweaks arguments to fix know problem cases.
     */
    private void fixArguments(Class<?> clazz, Constructor<?> constructor, Object[] arguments) throws Exception {
        if (clazz.getName().equals("com.google.gwt.user.client.ui.Tree")) {
            String signature = ReflectionUtils.getConstructorSignature(constructor);
            if (signature.equals("<init>(com.google.gwt.user.client.ui.TreeImages)")) {
                if (arguments[0] == null) {
                    arguments[0] = ScriptUtils.evaluate(clazz.getClassLoader(),
                            CodeUtils.getSource("import com.google.gwt.core.client.GWT;",
                                    "import com.google.gwt.user.client.ui.*;", "return GWT.create(TreeImages);"));
                }
            }
        }
        // prevent "null" as "cell" in CellList
        if (clazz.getName().equals("com.google.gwt.user.cellview.client.CellList")) {
            String signature = ReflectionUtils.getConstructorSignature(constructor);
            if (signature.equals("<init>(com.google.gwt.cell.client.Cell)")) {
                if (arguments[0] == null) {
                    ClassLoader classLoader = clazz.getClassLoader();
                    Class<?> classTextCell = classLoader.loadClass("com.google.gwt.cell.client.TextCell");
                    arguments[0] = classTextCell.newInstance();
                }
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // ValueLabel
    //
    ////////////////////////////////////////////////////////////////////////////
    private static Object createValueLabel(Constructor<?> actualConstructor, Object[] arguments) throws Exception {
        Object valueLabel = actualConstructor.newInstance(arguments);
        // set text 
        {
            String rendererName;
            if (arguments[0] == null) {
                rendererName = "null";
            } else {
                rendererName = CodeUtils.getShortClass(arguments[0].getClass().getName());
            }
            String text = "ValueLabel(" + rendererName + ")";
            setValueLabelText(valueLabel, text);
        }
        // done
        return valueLabel;
    }

    private static Object createDateLabel(ClassInstanceCreation expression, Constructor<?> actualConstructor,
            Object[] arguments) throws Exception {
        Object valueLabel = actualConstructor.newInstance(arguments);
        setValueLabelText(valueLabel, "12/31/2010");
        return valueLabel;
    }

    private static Object createNumberLabel(ClassInstanceCreation expression, Constructor<?> actualConstructor,
            Object[] arguments) throws Exception {
        Object valueLabel = actualConstructor.newInstance(arguments);
        // prepare text to show
        String text;
        {
            ITypeBinding creationBinding = AstNodeUtils.getTypeBinding(expression);
            ITypeBinding typeBinding = AstNodeUtils.getTypeBindingArgument(creationBinding,
                    "com.google.gwt.user.client.ui.NumberLabel", 0);
            String typeName = AstNodeUtils.getFullyQualifiedName(typeBinding, false);
            text = MessageFormat.format("NumberLabel<{0}>", CodeUtils.getShortClass(typeName));
        }
        // set text
        setValueLabelText(valueLabel, text);
        return valueLabel;
    }

    /**
     * Sets text for given <code>ValueLabel</code> widget.
     */
    public static void setValueLabelText(Object valueLabel, String text) throws Exception {
        Object directionalTextHelper = ReflectionUtils.getFieldObject(valueLabel, "directionalTextHelper");
        ReflectionUtils.invokeMethod(directionalTextHelper, "setTextOrHtml(java.lang.String,boolean)", text, false);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Exception
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public Throwable rewriteException(Throwable e) {
        if (isMailExampleException(e)) {
            String userStackTrace = AstEvaluationEngine.getUserStackTrace(e);
            return new DesignerException(IExceptionConstants.MAIL_SAMPLE_GET, e, userStackTrace);
        }
        return null;
    }

    /**
     * @return <code>true</code> if given {@link Throwable} is caused by GWT Mail example.
     */
    private static boolean isMailExampleException(Throwable e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        StackTraceElement element = stackTrace[0];
        return "com.google.gwt.sample.mail.client.MailList".equals(element.getClassName())
                && "selectRow".equals(element.getMethodName());
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Implementation
    //
    ////////////////////////////////////////////////////////////////////////////
    private static boolean isWidget(Class<?> clazz) {
        return ReflectionUtils.isSuccessorOf(clazz, "com.google.gwt.user.client.ui.Widget");
    }

    private static boolean isJavaScriptObject_rewrittenInterface(Class<?> clazz) {
        return clazz.isInterface() && ReflectionUtils.isSuccessorOf(clazz, JSO_NAME);
    }

    /**
     * @return the <code>Widget</code> to use as placeholder instead of real component that can not be
     *         created because of some exception.
     */
    private static Object createPlaceholder(Class<?> clazz) throws Exception {
        String message = MessageFormat.format(
                "Exception during creation of: {0}. See \"Open error log\" for details.",
                CodeUtils.getShortClass(clazz.getName()));
        // script
        String script;
        {
            AstEditor editor = EditorState.getActiveJavaInfo().getEditor();
            ComponentDescription description = ComponentDescriptionHelper.getDescription(editor, clazz);
            script = description.getParameter("placeholderScript");
        }
        // variables
        Map<String, Object> variables = Maps.newTreeMap();
        variables.put("clazz", clazz);
        variables.put("message", message);
        // execute
        ClassLoader classLoader = clazz.getClassLoader();
        return ScriptUtils.evaluate(classLoader, script, variables);
    }
}