Package com.google.gdt.eclipse.designer.uibinder.parser

Source Code of com.google.gdt.eclipse.designer.uibinder.parser.UiBinderParser

/*******************************************************************************
* 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.parser;

import com.google.common.collect.Maps;
import com.google.gdt.eclipse.designer.uibinder.IExceptionConstants;
import com.google.gdt.eclipse.designer.uibinder.model.util.EventHandlersSupport;
import com.google.gdt.eclipse.designer.uibinder.model.util.NameSupport;
import com.google.gdt.eclipse.designer.uibinder.model.util.StylePropertySupport;
import com.google.gdt.eclipse.designer.uibinder.model.util.UiBinderStaticFieldSupport;
import com.google.gdt.eclipse.designer.uibinder.model.util.UiChildSupport;
import com.google.gdt.eclipse.designer.uibinder.model.util.UiConstructorSupport;
import com.google.gdt.eclipse.designer.uibinder.model.widgets.IsWidgetInfo;
import com.google.gdt.eclipse.designer.util.Utils;

import org.eclipse.wb.core.model.broadcast.ObjectEventListener;
import org.eclipse.wb.core.model.broadcast.ObjectInfoTreeComplete;
import org.eclipse.wb.internal.core.model.util.ScriptUtils;
import org.eclipse.wb.internal.core.utils.exception.DesignerException;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
import org.eclipse.wb.internal.core.utils.xml.DocumentElement;
import org.eclipse.wb.internal.core.utils.xml.DocumentModelVisitor;
import org.eclipse.wb.internal.core.xml.model.XmlObjectInfo;
import org.eclipse.wb.internal.core.xml.model.creation.ElementCreationSupport;
import org.eclipse.wb.internal.core.xml.model.description.ComponentDescription;
import org.eclipse.wb.internal.core.xml.model.description.ComponentDescriptionHelper;
import org.eclipse.wb.internal.core.xml.model.utils.GlobalStateXml;
import org.eclipse.wb.internal.core.xml.model.utils.XmlObjectUtils;

import org.eclipse.jdt.core.IJavaProject;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
* Parser for GWT UiBinder.
*
* @author scheglov_ke
* @coverage GWT.UiBinder.parser
*/
public final class UiBinderParser {
  private final UiBinderContext m_context;
  private final Map<String, DocumentElement> m_pathToElementMap = Maps.newLinkedHashMap();
  private final Map<String, XmlObjectInfo> m_pathToModelMap = Maps.newLinkedHashMap();
  private XmlObjectInfo m_rootModel;

  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  public UiBinderParser(UiBinderContext context) throws Exception {
    m_context = context;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Parse
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Parse <code>*.ui.xml</code> files and return root {@link XmlObjectInfo}.
   */
  public XmlObjectInfo parse() throws Exception {
    // validate GWT version
    {
      IJavaProject javaProject = m_context.getJavaProject();
      // has UiBinder support at all
      if (!hasUiBinderSupport(javaProject)) {
        throw new DesignerException(IExceptionConstants.WRONG_VERSION);
      }
      // has support for @UiField(provided) and @UiFactory
      if (!hasUiFieldUiFactorySupport(javaProject)) {
        String formSource = m_context.getFormType().getSource();
        if (formSource.contains("@UiField(provided")) {
          throw new DesignerException(IExceptionConstants.UI_FIELD_FACTORY_FEATURE);
        }
        if (formSource.contains("@UiFactory")) {
          throw new DesignerException(IExceptionConstants.UI_FIELD_FACTORY_FEATURE);
        }
      }
    }
    // prepare for parsing
    GlobalStateXml.setEditorContext(m_context);
    m_context.initialize();
    // parse
    m_context.runDesignTime(new RunnableEx() {
      public void run() throws Exception {
        try {
          parse0();
        } catch (Throwable e) {
          m_context.getBroadcastSupport().getListener(ObjectEventListener.class).dispose();
          ReflectionUtils.propagate(e);
        }
      }
    });
    return m_rootModel;
  }

  /**
   * Implementation of {@link #parse()}.
   */
  private void parse0() throws Exception {
    m_context.setParsing(true);
    m_context.notifyAboutToParse();
    fillMap_pathToElement();
    // load Binder
    Object createdBinder;
    {
      ClassLoader classLoader = m_context.getClassLoader();
      String binderClassName = m_context.getBinderClassName();
      Class<?> binderClass = classLoader.loadClass(binderClassName);
      Class<?> classGWT = classLoader.loadClass("com.google.gwt.core.client.GWT");
      createdBinder =
          ReflectionUtils.invokeMethod(classGWT, "create(java.lang.Class)", binderClass);
      createModels(createdBinder);
    }
    // render Widget(s)
    ReflectionUtils.invokeMethod(createdBinder, "createAndBindUi(java.lang.Object)", (Object) null);
    buildHierarchy();
    // done
    m_context.setParsing(false);
    new UiConstructorSupport(m_context);
    new UiChildSupport(m_context);
    NameSupport.decoratePresentationWithName(m_rootModel);
    XmlObjectUtils.callRootProcessors(m_rootModel);
    XmlObjectUtils.registerTagResolvers(m_rootModel);
    new UiBinderStaticFieldSupport(m_rootModel);
    NameSupport.removeName_onDelete(m_rootModel);
    NameSupport.ensureFieldProvided_onCreate(m_rootModel);
    new EventHandlersSupport(m_rootModel);
    new StylePropertySupport(m_context);
    GlobalStateXml.activate(m_rootModel);
    m_rootModel.getBroadcast(ObjectInfoTreeComplete.class).invoke();
    m_rootModel.refresh_dispose();
  }

  /**
   * Handle object before setting attributes, so get default property values.
   */
  private void createModels(Object binder) throws Exception {
    ClassLoader classLoader = binder.getClass().getClassLoader();
    String handlerClassName = binder.getClass().getName() + "$DTObjectHandler";
    Class<?> handlerClass = classLoader.loadClass(handlerClassName);
    Object handler =
        Proxy.newProxyInstance(classLoader, new Class[]{handlerClass}, new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("handle")) {
              String path = (String) args[0];
              Object object = args[1];
              createModel(path, object);
            }
            if (method.getName().equals("provideFactory")) {
              Class<?> factoryType = (Class<?>) args[0];
              String methodName = (String) args[1];
              Object[] factoryArgs = (Object[]) args[2];
              return createProvidedFactory(m_context, factoryType, methodName, factoryArgs);
            }
            if (method.getName().equals("provideField")) {
              Class<?> fieldType = (Class<?>) args[0];
              String fieldName = (String) args[1];
              return createProvidedField(m_context, fieldType, fieldName);
            }
            return null;
          }
        });
    ReflectionUtils.setField(binder, "dtObjectHandler", handler);
  }

  private void createModel(String path, Object object) throws Exception {
    DocumentElement xmlElement = m_pathToElementMap.get(path);
    XmlObjectInfo objectInfo =
        XmlObjectUtils.createObject(
            m_context,
            object.getClass(),
            new ElementCreationSupport(xmlElement));
    GlobalStateXml.activate(objectInfo);
    objectInfo.setObject(object);
    m_pathToModelMap.put(path, objectInfo);
  }

  private void buildHierarchy() throws Exception {
    for (Map.Entry<String, XmlObjectInfo> entry : m_pathToModelMap.entrySet()) {
      String path = entry.getKey();
      XmlObjectInfo object = entry.getValue();
      if (object instanceof IsWidgetInfo) {
        object = ((IsWidgetInfo) object).getWrapped();
      }
      XmlObjectInfo parent = findParent(path);
      if (parent != null) {
        parent.addChild(object);
      } else {
        m_rootModel = object;
      }
    }
  }

  /**
   * @return the parent for given "child" path, may be <code>null</code> if root.
   */
  private XmlObjectInfo findParent(String childPath) {
    String parentPath = StringUtils.substringBeforeLast(childPath, "/");
    for (Map.Entry<String, XmlObjectInfo> entry : m_pathToModelMap.entrySet()) {
      String path = entry.getKey();
      XmlObjectInfo parent = entry.getValue();
      if (path.equals(parentPath)) {
        return parent;
      }
    }
    if (childPath.contains("/")) {
      return findParent(parentPath);
    }
    return null;
  }

  /**
   * Visits all {@link DocumentElement}s and remembers all of them with path.
   */
  private void fillMap_pathToElement() {
    m_context.getRootElement().accept(new DocumentModelVisitor() {
      @Override
      public void endVisit(DocumentElement element) {
        m_pathToElementMap.put(getPath(element), element);
      }
    });
  }

  /**
   * @return the path of our {@link DocumentElement}.
   */
  public static String getPath(DocumentElement element) {
    DocumentElement parent = element.getParent();
    if (parent == null) {
      return "0";
    } else {
      int index = parent.indexOf(element);
      return getPath(parent) + "/" + index;
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Utils
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return the instance of given {@link Class} created using factory method.
   */
  static Object createProvidedFactory(UiBinderContext context,
      Class<?> factoryType,
      String methodName,
      Object[] args) throws Exception {
    try {
      return createObjectInstance(context, methodName, factoryType, args);
    } catch (Throwable e) {
      throw new DesignerException(IExceptionConstants.UI_FACTORY_EXCEPTION,
          e,
          factoryType.getName(),
          methodName);
    }
  }

  /**
   * @return the instance of given {@link Class}.
   */
  static Object createProvidedField(UiBinderContext context, Class<?> fieldType, String fieldName)
      throws Exception {
    try {
      return createObjectInstance(context, fieldName, fieldType, ArrayUtils.EMPTY_OBJECT_ARRAY);
    } catch (Throwable e) {
      throw new DesignerException(IExceptionConstants.UI_FIELD_EXCEPTION,
          e,
          fieldType.getName(),
          fieldName);
    }
  }

  /**
   * @return the instance of given {@link Class}, created using the most specific method.
   */
  private static Object createObjectInstance(UiBinderContext context,
      String objectName,
      Class<?> clazz,
      Object[] args) throws Exception {
    // try CreateObjectInstance broadcast
    {
      Object result[] = {null};
      context.getBroadcastSupport().getListener(CreateObjectInstance.class).invoke(
          objectName,
          clazz,
          args,
          result);
      if (result[0] != null) {
        return result[0];
      }
    }
    // try "UiBinder.createInstance" script
    {
      ComponentDescription description = ComponentDescriptionHelper.getDescription(context, clazz);
      // prepare script
      String script;
      {
        String scriptObject[] = {null};
        context.getBroadcastSupport().getListener(CreateObjectScript.class).invoke(
            objectName,
            clazz,
            args,
            scriptObject);
        script = scriptObject[0];
      }
      if (script == null) {
        script = description.getParameter("UiBinder.createInstance");
      }
      // try to use script
      if (script != null) {
        ClassLoader classLoader = context.getClassLoader();
        Map<String, Object> variables = Maps.newTreeMap();
        variables.put("wbpClassLoader", UiBinderParser.class.getClassLoader());
        variables.put("classLoader", classLoader);
        variables.put("componentClass", clazz);
        variables.put("objectName", objectName);
        variables.put("modelClass", description.getModelClass());
        variables.put("modelClassLoader", description.getModelClass().getClassLoader());
        variables.put("args", args);
        Object result = ScriptUtils.evaluate(classLoader, script, variables);
        return result;
      }
    }
    // default constructor
    {
      Constructor<?> constructor = ReflectionUtils.getConstructorBySignature(clazz, "<init>()");
      if (constructor != null) {
        return constructor.newInstance();
      }
    }
    // shortest constructor
    {
      Constructor<?> constructor = ReflectionUtils.getShortestConstructor(clazz);
      Class<?>[] parameterTypes = constructor.getParameterTypes();
      Object[] argumentValues = new Object[parameterTypes.length];
      for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        argumentValues[i] = getDefaultValue(parameterType);
      }
      return constructor.newInstance(argumentValues);
    }
  }

  /**
   * @return the argument value for parameter of given type.
   */
  private static Object getDefaultValue(Class<?> parameterType) throws Exception {
    // try INSTANCE field
    {
      Field instanceField = ReflectionUtils.getFieldByName(parameterType, "INSTANCE");
      if (instanceField != null
          && ReflectionUtils.isStatic(instanceField)
          && parameterType.isAssignableFrom(instanceField.getType())) {
        return instanceField.get(null);
      }
    }
    // use default value
    return ReflectionUtils.getDefaultValue(parameterType);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Versions
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return <code>true</code> if given {@link IJavaProject} uses at least GWT 2.1, which contains
   *         design-time tweaks for using UiBinder in GWT Designer.
   */
  public static boolean hasUiBinderSupport(IJavaProject javaProject) {
    return Utils.getVersion(javaProject).isHigherOrSame(Utils.GWT_2_1);
  }

  /**
   * @return <code>true</code> if given {@link IJavaProject} uses at least GWT 2.1.1, which contains
   *         design-time tweaks for using UiBinder's <code>@UiField(provided)</code> and
   *         <code>@UiFactory</code> in GWT Designer.
   */
  public static boolean hasUiFieldUiFactorySupport(IJavaProject javaProject) {
    return Utils.getVersion(javaProject).isHigherOrSame(Utils.GWT_2_1_1);
  }
}
TOP

Related Classes of com.google.gdt.eclipse.designer.uibinder.parser.UiBinderParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.