Java tutorial
/******************************************************************************* * 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); } }