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.uibinder.model.util; import com.google.common.collect.ImmutableList; import com.google.gdt.eclipse.designer.uibinder.parser.UiBinderContext; import org.eclipse.wb.internal.core.DesignerPlugin; import org.eclipse.wb.internal.core.model.property.Property; import org.eclipse.wb.internal.core.model.property.event.EventsPropertyUtils; import org.eclipse.wb.internal.core.model.util.ObjectInfoAction; 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.BodyDeclarationTarget; import org.eclipse.wb.internal.core.utils.ast.DomGenerics; import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils; import org.eclipse.wb.internal.core.utils.execution.RunnableEx; import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils; import org.eclipse.wb.internal.core.xml.editor.DesignContextMenuProvider; import org.eclipse.wb.internal.core.xml.model.XmlObjectInfo; import org.eclipse.wb.internal.core.xml.model.property.event.AbstractListenerProperty; import org.eclipse.core.resources.IFile; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.texteditor.ITextEditor; import org.apache.commons.lang.StringUtils; import java.util.List; /** * {@link Property} for single UiBinder event. * * @author scheglov_ke * @coverage GWT.UiBinder.model */ public final class EventHandlerProperty extends AbstractListenerProperty { private final EventHandlerDescription m_handler; //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// public EventHandlerProperty(XmlObjectInfo object, EventHandlerDescription handler) { super(object, handler.getMethodName(), EventHandlerPropertyEditor.INSTANCE); m_handler = handler; } //////////////////////////////////////////////////////////////////////////// // // Property // //////////////////////////////////////////////////////////////////////////// @Override public boolean isModified() throws Exception { return getMethodDeclaration(false) != null; } @Override public void setValue(Object value) throws Exception { if (value == UNKNOWN_VALUE && isModified()) { if (MessageDialog.openConfirm(DesignerPlugin.getShell(), "Confirm", "Do you really want delete handle '" + m_handler.getMethodName() + "'?")) { ExecutionUtils.run(m_object, new RunnableEx() { public void run() throws Exception { removeListener(); } }); } } } //////////////////////////////////////////////////////////////////////////// // // Access // //////////////////////////////////////////////////////////////////////////// @Override protected void removeListener() throws Exception { prepareAST(); try { MethodDeclaration method = getMethodDeclaration0(); m_editor.removeBodyDeclaration(method); } finally { clearAST(); } } //////////////////////////////////////////////////////////////////////////// // // Context menu // //////////////////////////////////////////////////////////////////////////// @Override protected void addListenerActions(IMenuManager manager, IMenuManager implementMenuManager) throws Exception { IAction[] actions = createListenerMethodActions(); // append existing stub action if (actions[0] != null) { manager.appendToGroup(DesignContextMenuProvider.GROUP_EVENTS, actions[0]); } // append existing or new method action implementMenuManager.add(actions[0] != null ? actions[0] : actions[1]); } /** * For given {@link ListenerMethodProperty} creates two {@link Action}'s: * * [0] - for existing stub method, may be <code>null</code>;<br> * [1] - for creating new stub method. */ private IAction[] createListenerMethodActions() throws Exception { String name = m_handler.getMethodName(); IAction[] actions = new IAction[2]; // try to find existing stub method { MethodDeclaration method = getMethodDeclaration(false); if (method != null) { actions[0] = new ObjectInfoAction(m_object) { @Override protected void runEx() throws Exception { openListener(); } }; actions[0].setText(name + " -> " + method.getName().getIdentifier()); actions[0].setImageDescriptor(EventsPropertyUtils.LISTENER_METHOD_IMAGE_DESCRIPTOR); } } // in any case prepare action for creating new stub method { actions[1] = new ObjectInfoAction(m_object) { @Override protected void runEx() throws Exception { openListener(); } }; actions[1].setText(name); actions[1].setImageDescriptor(EventsPropertyUtils.LISTENER_METHOD_IMAGE_DESCRIPTOR); } // return actions; } //////////////////////////////////////////////////////////////////////////// // // Handler // //////////////////////////////////////////////////////////////////////////// @Override protected void openListener() throws Exception { final MethodDeclaration method = getMethodDeclaration(true); if (method != null) { ExecutionUtils.runAsync(new RunnableEx() { public void run() throws Exception { openMethod_inEditor(method); } }); } } //////////////////////////////////////////////////////////////////////////// // // AST // //////////////////////////////////////////////////////////////////////////// private IFile m_javaFile; private AstEditor m_editor; private TypeDeclaration m_typeDeclaration; /** * Prepares {@link #m_editor} for {@link #m_javaFile}. */ private void prepareAST() throws Exception { m_editor = ((UiBinderContext) m_object.getContext()).getFormEditor(); m_javaFile = (IFile) m_editor.getModelUnit().getUnderlyingResource(); m_typeDeclaration = m_editor.getPrimaryType(); } /** * Clears {@link #m_editor} after finishing AST operations. */ private void clearAST() { m_editor = null; m_typeDeclaration = null; } //////////////////////////////////////////////////////////////////////////// // // Implementation // //////////////////////////////////////////////////////////////////////////// MethodDeclaration getMethodDeclaration(boolean addNew) throws Exception { if (canHaveHandler()) { prepareAST(); try { MethodDeclaration method = getMethodDeclaration0(); if (method != null || !addNew) { return method; } } finally { clearAST(); } } // new is not requested if (!addNew) { return null; } // ensure method prepareAST(); try { return createMethodDeclaration0(); } finally { clearAST(); ExecutionUtils.refresh(m_object); } } /** * This method is used for optimization - parsing AST is fairly costly operation, so we try to * check quickly at first and do exact check only if needed. Alternative approach is remembering * {@link AstEditor} somewhere in context and track Java file changes. But for now we use simplest * solution. * * @return <code>true</code> if it is possible that there is handler method, or <code>false</code> * if there is definitely no such handler. */ private boolean canHaveHandler() throws Exception { String name = NameSupport.getName(m_object); if (name == null) { return false; } // prepare Java source String formSource; { UiBinderContext context = (UiBinderContext) m_object.getContext(); formSource = getTypeSource(context.getFormType()); } // widget name if (!formSource.contains("\"" + name + "\"")) { return false; } // handler type name { String typeName = m_handler.getEventTypeName(); String shortTypeName = CodeUtils.getShortClass(typeName); if (!formSource.contains(shortTypeName)) { return false; } } // may be there is handler return true; } /** * @return the source of {@link IType}. In theory first call of {@link IType#getSource()} should * be successful, however when we change {@link IType} opened in Java editor there is some * delay between change and time when source will be visible again (reconciling?). */ private static String getTypeSource(IType type) throws Exception { String source = null; for (int i = 0; i < 5000; i++) { source = type.getSource(); if (source != null) { break; } ExecutionUtils.waitEventLoop(5); } return source; } /** * @return the existing or new {@link MethodDeclaration} for this event. */ private MethodDeclaration getMethodDeclaration0() throws Exception { String name = NameSupport.getName(m_object); // try to find existing for (MethodDeclaration methodDeclaration : m_typeDeclaration.getMethods()) { if (isEventHandler(methodDeclaration) && isObjectHandler(methodDeclaration, name)) { return methodDeclaration; } } // no method return null; } /** * @return <code>true</code> if {@link MethodDeclaration} has required event type parameter. */ private boolean isEventHandler(MethodDeclaration methodDeclaration) { List<SingleVariableDeclaration> parameters = DomGenerics.parameters(methodDeclaration); if (parameters.size() == 1) { SingleVariableDeclaration parameter = parameters.get(0); String parameterTypeName = AstNodeUtils.getFullyQualifiedName(parameter, false); return m_handler.getEventTypeName().equals(parameterTypeName); } return false; } /** * @return <code>true</code> if {@link MethodDeclaration} has "@UiHandler" annotation with * required name. */ static boolean isObjectHandler(MethodDeclaration methodDeclaration, String name) { SingleMemberAnnotation handlerAnnotation = getHandlerAnnotation(methodDeclaration); if (handlerAnnotation != null && handlerAnnotation.getValue() instanceof StringLiteral) { StringLiteral handlerLiteral = (StringLiteral) handlerAnnotation.getValue(); String handlerName = handlerLiteral.getLiteralValue(); return name.equals(handlerName); } return false; } /** * @return the existing or new {@link MethodDeclaration} for this event. */ private MethodDeclaration createMethodDeclaration0() throws Exception { String name = NameSupport.ensureName(m_object); String eventTypeName = m_handler.getEventTypeName(); // prepare name of method String methodName; { String eventName = StringUtils.removeStart(m_handler.getMethodName(), "on"); String baseName = "on" + StringUtils.capitalize(name) + eventName; methodName = m_editor.getUniqueMethodName(baseName); } // add method String uiHandlerAnnotation = "@com.google.gwt.uibinder.client.UiHandler(\"" + name + "\")"; String header = "void " + methodName + "(" + eventTypeName + " event)"; MethodDeclaration method = m_editor.addMethodDeclaration(ImmutableList.<String>of(uiHandlerAnnotation), header, ImmutableList.<String>of(), new BodyDeclarationTarget(m_typeDeclaration, false)); // done return method; } /** * @return the "@UiHandler" {@link SingleMemberAnnotation}, may be <code>null</code>. */ private static SingleMemberAnnotation getHandlerAnnotation(MethodDeclaration methodDeclaration) { for (IExtendedModifier modifier : DomGenerics.modifiers(methodDeclaration)) { if (modifier instanceof SingleMemberAnnotation) { SingleMemberAnnotation annotation = (SingleMemberAnnotation) modifier; if (AstNodeUtils.isSuccessorOf(annotation, "com.google.gwt.uibinder.client.UiHandler")) { return annotation; } } } return null; } /** * Opens source of given Java {@link IFile} at position that corresponds {@link MethodDeclaration} * . */ private void openMethod_inEditor(MethodDeclaration method) throws Exception { IEditorPart javaEditor = IDE.openEditor(DesignerPlugin.getActivePage(), m_javaFile); if (javaEditor instanceof ITextEditor) { ((ITextEditor) javaEditor).selectAndReveal(method.getStartPosition(), 0); } } }