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

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

/*******************************************************************************
* 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.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gdt.eclipse.designer.GwtToolkitDescription;
import com.google.gdt.eclipse.designer.model.widgets.support.GwtState;
import com.google.gdt.eclipse.designer.parser.IClassLoaderValidator;
import com.google.gdt.eclipse.designer.uibinder.IExceptionConstants;
import com.google.gdt.eclipse.designer.util.ModuleDescription;
import com.google.gdt.eclipse.designer.util.Utils;

import org.eclipse.wb.internal.core.model.description.resource.IDescriptionVersionsProvider;
import org.eclipse.wb.internal.core.model.description.resource.IDescriptionVersionsProviderFactory;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.util.ScriptUtils;
import org.eclipse.wb.internal.core.utils.IOUtils2;
import org.eclipse.wb.internal.core.utils.ast.AstEditor;
import org.eclipse.wb.internal.core.utils.exception.DesignerException;
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.external.ExternalFactoriesHelper;
import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils;
import org.eclipse.wb.internal.core.utils.reflect.CompositeClassLoader;
import org.eclipse.wb.internal.core.utils.xml.DocumentElement;
import org.eclipse.wb.internal.core.xml.model.EditorContext;
import org.eclipse.wb.internal.core.xml.model.ILiveEditorContext;
import org.eclipse.wb.internal.core.xml.model.XmlObjectInfo;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;

import org.apache.commons.lang.StringUtils;

import java.beans.Beans;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* {@link EditorContext} for GWT UiBinder.
*
* @author scheglov_ke
* @coverage GWT.UiBinder.parser
*/
public class UiBinderContext extends EditorContext {
  private String m_binderClassName;
  private String m_binderResourceName;
  private String m_formClassName;
  private IType m_formType;
  private IFile m_formFile;
  private AstEditor m_formEditor;
  private long m_formFileModification;
  private ModuleDescription m_module;
  private GwtState m_state;

  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  public UiBinderContext(IFile file, IDocument document) throws Exception {
    super(GwtToolkitDescription.INSTANCE, file, document);
    prepareBinderNames();
    configureDescriptionVersionsProviders();
    addVersions(ImmutableMap.of("isUiBinder", "true"));
  }

  public UiBinderContext(GwtState state, ClassLoader classLoader, IFile file, IDocument document)
      throws Exception {
    this(file, document);
    m_state = state;
    m_classLoader = classLoader;
    updateFromGWTState();
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Access
  //
  ////////////////////////////////////////////////////////////////////////////
  public String getBinderClassName() {
    return m_binderClassName;
  }

  /**
   * @return the {@link IType} of class with "Binder".
   */
  public IType getFormType() {
    return m_formType;
  }

  /**
   * @return the {@link AstEditor} for Java source.
   */
  public AstEditor getFormEditor() throws Exception {
    long currentModificationStamp = m_formFile.getModificationStamp();
    if (m_formEditor == null || currentModificationStamp != m_formFileModification) {
      m_formEditor = new AstEditor(m_formType.getCompilationUnit());
      m_formFileModification = currentModificationStamp;
    }
    return m_formEditor;
  }

  /**
   * Saves and clears {@link AstEditor} for Java source.
   */
  public void saveFormEditor() throws Exception {
    if (m_formEditor != null) {
      m_formEditor.saveChanges(true);
      m_formEditor = null;
    }
  }

  /**
   * @return the {@link GwtState} of this editor.
   */
  public GwtState getState() {
    return m_state;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Notification
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Notifies {@link AboutToParseProcessor} that this {@link UiBinderContext} is about to parse.
   */
  public void notifyAboutToParse() throws Exception {
    List<AboutToParseProcessor> processors =
        ExternalFactoriesHelper.getElementsInstances(
            AboutToParseProcessor.class,
            "com.google.gdt.eclipse.designer.UiBinder.aboutToParse",
            "processor");
    for (AboutToParseProcessor processor : processors) {
      processor.process(this);
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Utils
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Ensures that UiBinder is configured for design time.
   */
  public void runDesignTime(RunnableEx runnable) throws Exception {
    String isKey = "gwt.UiBinder.isDesignTime " + m_binderClassName.replace('$', '.');
    String resKey = "gwt.UiBinder.designTime " + m_binderResourceName;
    boolean old_designTime = Beans.isDesignTime();
    try {
      Beans.setDesignTime(true);
      // mark "Binder" as design time
      System.setProperty(isKey, "true");
      // put current document content into System, to make it available to UiBinderGenerator
      {
        String content = getContent();
        content = removeWbpNameAttributes(content);
        System.setProperty(resKey, content);
      }
      // do run
      runnable.run();
    } finally {
      m_state.getDevModeBridge().invalidateRebind(m_binderClassName);
      Beans.setDesignTime(old_designTime);
      System.clearProperty(isKey);
      System.clearProperty(resKey);
      getBroadcastSupport().getListener(AfterRunDesignTime.class).invoke();
    }
  }

  /**
   * In tests we use "wbp:name" attribute to access widgets by such "internal" names, but UiBinder
   * does not like when it sees unknown attributes, so we should remove them.
   */
  private static String removeWbpNameAttributes(String content) {
    Matcher matcher = Pattern.compile("wbp:name=\"[^\"]*\"").matcher(content);
    // process each match
    int last = 0;
    StringBuffer sb = new StringBuffer();
    while (matcher.find()) {
      int start = matcher.start();
      int end = matcher.end();
      // not matched part
      sb.append(content.substring(last, start));
      last = end;
      // replace matched part with spaces
      for (int i = start; i < end; i++) {
        sb.append(' ');
      }
    }
    // append tail
    sb.append(content.substring(last));
    return sb.toString();
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Attributes
  //
  ////////////////////////////////////////////////////////////////////////////
  private final Map<String, Object> m_attributeValues = Maps.newHashMap();

  /**
   * Registers values for attributes, during rendering.
   */
  public void setAttributeValues(Map<String, Object> attributes) {
    m_attributeValues.clear();
    m_attributeValues.putAll(attributes);
  }

  /**
   * Registers value for attribute.
   */
  public void setAttributeValue(DocumentElement element, String name, Object value) {
    String key = UiBinderParser.getPath(element) + " " + name;
    m_attributeValues.put(key, value);
  }

  /**
   * @return all attributes of the element with the given path.
   */
  public Map<String, Object> getAttributeValues(String path) {
    Map<String, Object> attribute = Maps.newHashMap();
    String pathPrefix = path + " ";
    for (Entry<String, Object> entry : m_attributeValues.entrySet()) {
      if (entry.getKey().startsWith(pathPrefix)) {
        attribute.put(StringUtils.substringAfter(entry.getKey(), pathPrefix), entry.getValue());
      }
    }
    return attribute;
  }

  /**
   * @return the attribute value, remembered earlier during rendering. Value <code>null</code> is
   *         just value, not flag that there are no value. If no value for attribute, then
   *         {@link Property#UNKNOWN_VALUE} will be returned.
   */
  public Object getAttributeValue(DocumentElement element, String name) {
    String key = UiBinderParser.getPath(element) + " " + name;
    if (m_attributeValues.containsKey(key)) {
      return m_attributeValues.get(key);
    }
    return Property.UNKNOWN_VALUE;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // ClassLoader
  //
  ////////////////////////////////////////////////////////////////////////////
  @Override
  protected void createClassLoader() throws Exception {
    if (m_classLoader == null) {
      m_module = Utils.getSingleModule(m_file);
      super.createClassLoader();
      executeClassLoaderInitializationScripts();
    }
  }

  /**
   * Allows users to perform {@link ClassLoader} initialization actions, to prepare environment.
   */
  private void executeClassLoaderInitializationScripts() {
    IResource resource = m_file;
    while (true) {
      if (resource.getParent() instanceof IFolder) {
        IFolder folder = (IFolder) resource.getParent();
        resource = folder;
        // use script in current folder
        final IFile scriptFile = folder.getFile("ClassLoaderInitializer.gwtd.mvel");
        if (scriptFile.exists()) {
          ExecutionUtils.runLog(new RunnableEx() {
            public void run() throws Exception {
              String script = IOUtils2.readString(scriptFile);
              ScriptUtils.evaluate(m_classLoader, script);
            }
          });
        }
      } else {
        break;
      }
    }
  }

  @Override
  protected void addParentClassLoaders(CompositeClassLoader parentClassLoader) throws Exception {
    super.addParentClassLoaders(parentClassLoader);
    // add ClassLoader to use only for loading resources
    {
      ClassLoader resourcesClassLoader = m_module.getClassLoader();
      parentClassLoader.add(resourcesClassLoader, ImmutableList.<String>of(), null);
    }
  }

  @Override
  protected ClassLoader createProjectClassLoader(CompositeClassLoader parentClassLoader)
      throws Exception {
    createGWTState(parentClassLoader);
    {
      // process ClassLoader validators
      List<IClassLoaderValidator> validators =
          ExternalFactoriesHelper.getElementsInstances(
              IClassLoaderValidator.class,
              "com.google.gdt.eclipse.designer.classLoaderValidators",
              "validator");
      for (IClassLoaderValidator validator : validators) {
        validator.validate(m_javaProject, m_state);
      }
    }
    return m_state.getClassLoader();
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Utils
  //
  ////////////////////////////////////////////////////////////////////////////
  private void prepareBinderNames() throws Exception {
    // template
    IPath templatePath = m_file.getFullPath();
    String templatePathString = templatePath.toPortableString();
    // package
    IPackageFragment uiPackage;
    {
      if (!(m_file.getParent() instanceof IFolder)) {
        throw new DesignerException(IExceptionConstants.NO_FORM_PACKAGE, templatePathString);
      }
      // prepare package
      IFolder uiFolder = (IFolder) m_file.getParent();
      IJavaElement uiElement = JavaCore.create(uiFolder);
      if (!(uiElement instanceof IPackageFragment)) {
        throw new DesignerException(IExceptionConstants.NO_FORM_PACKAGE, templatePathString);
      }
      uiPackage = (IPackageFragment) uiElement;
      // has client package
      if (!Utils.isModuleSourcePackage(uiPackage)) {
        throw new DesignerException(IExceptionConstants.NOT_CLIENT_PACKAGE, templatePathString);
      }
    }
    // binder resource
    m_binderResourceName = uiPackage.getElementName().replace('.', '/') + "/" + m_file.getName();
    // try current package
    {
      String formName = StringUtils.removeEnd(m_file.getName(), ".ui.xml");
      m_formClassName = uiPackage.getElementName() + "." + formName;
      m_formType = m_javaProject.findType(m_formClassName);
      if (m_formType != null) {
        m_formFile = (IFile) m_formType.getCompilationUnit().getUnderlyingResource();
        prepareBinderClass();
        if (m_binderClassName != null) {
          return;
        }
      }
    }
    // try @UiTemplate
    IType uiTemplateType = m_javaProject.findType("com.google.gwt.uibinder.client.UiTemplate");
    List<IJavaElement> references = CodeUtils.searchReferences(uiTemplateType);
    for (IJavaElement reference : references) {
      if (reference instanceof IAnnotation) {
        IAnnotation annotation = (IAnnotation) reference;
        IMemberValuePair[] valuePairs = annotation.getMemberValuePairs();
        if (valuePairs.length == 1 && valuePairs[0].getValue() instanceof String) {
          String templateName = (String) valuePairs[0].getValue();
          // prepare ICompilationUnit
          ICompilationUnit compilationUnit =
              (ICompilationUnit) annotation.getAncestor(IJavaElement.COMPILATION_UNIT);
          // prepare qualified template name
          templateName = StringUtils.removeEnd(templateName, ".ui.xml");
          if (templateName.contains(".")) {
            templateName = templateName.replace('.', '/');
          } else {
            String packageName = compilationUnit.getPackageDeclarations()[0].getElementName();
            templateName = packageName.replace('.', '/') + "/" + templateName;
          }
          templateName += ".ui.xml";
          // if found, initialize "form" element
          if (m_binderResourceName.equals(templateName)) {
            m_formType = (IType) annotation.getParent().getParent();
            m_formClassName = m_formType.getFullyQualifiedName();
            m_formFile = (IFile) m_formType.getCompilationUnit().getUnderlyingResource();
            prepareBinderClass();
            if (m_binderClassName != null) {
              return;
            }
          }
        }
      }
    }
    // no Java form
    throw new DesignerException(IExceptionConstants.NO_FORM_TYPE, m_binderResourceName);
  }

  /**
   * Attempts for put into {@link #m_binderClassName} the "UiBinder" inner {@link IType} from
   * {@link #m_formType}.
   */
  private void prepareBinderClass() throws Exception {
    for (IType innerType : m_formType.getTypes()) {
      if (CodeUtils.isSuccessorOf(innerType, "com.google.gwt.uibinder.client.UiBinder")) {
        IType binderType = innerType;
        assertWidgetBased(binderType);
        m_binderClassName = m_formClassName + "$" + innerType.getElementName();
      }
    }
  }

  /**
   * Asserts that given <code>binderType</code> generates <code>Widget</code> when rendered.
   */
  private void assertWidgetBased(IType binderType) throws Exception {
    String[] superSignatures = binderType.getSuperInterfaceTypeSignatures();
    for (String superSignature : superSignatures) {
      int binderIndex = superSignature.indexOf("UiBinder<");
      if (binderIndex != -1) {
        int objectTypeBegin = binderIndex + "UiBinder<".length();
        int objectTypeEnd = superSignature.indexOf(";", binderIndex);
        String objectTypeSignature = superSignature.substring(objectTypeBegin, objectTypeEnd + 1);
        String objectTypeName = CodeUtils.getResolvedTypeName(m_formType, objectTypeSignature);
        if (objectTypeName != null) {
          IType objectType = m_javaProject.findType(objectTypeName);
          if (!CodeUtils.isSuccessorOf(objectType, "com.google.gwt.user.client.ui.Widget")) {
            throw new DesignerException(IExceptionConstants.ONLY_WIDGET_BASED,
                m_formClassName,
                objectTypeName);
          }
        }
      }
    }
  }

  private void createGWTState(CompositeClassLoader parentClassLoader) throws Exception {
    if (m_sharedUse && m_shared_GWTState != null) {
      m_state = m_shared_GWTState;
    } else {
      // initialize GWTState
      m_state = new GwtState(parentClassLoader, m_module);
      m_state.initialize();
      // remember shared state
      if (m_sharedUse) {
        m_state.setShared(true);
        m_shared_GWTState = m_state;
      }
    }
    updateFromGWTState();
  }

  /**
   * Updates this {@link UiBinderContext} from {@link #m_state}.
   */
  private void updateFromGWTState() {
    addVersions(ImmutableMap.of("gwt_isStrictMode", m_state.isStrictMode()));
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Sharing GWTState
  //
  ////////////////////////////////////////////////////////////////////////////
  private static boolean m_sharedUse = false;
  private static GwtState m_shared_GWTState;

  /**
   * Specifies if next parsing should use shared {@link GwtState}.
   */
  public static void setUseSharedGWTState(boolean use) {
    m_sharedUse = use;
  }

  /**
   * Disposes existing shared {@link GwtState}.
   */
  public static void disposeSharedGWTState() {
    if (m_shared_GWTState != null) {
      m_shared_GWTState.setShared(false);
      m_shared_GWTState.dispose();
      m_shared_GWTState = null;
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Live support
  //
  ////////////////////////////////////////////////////////////////////////////
  @Override
  public ILiveEditorContext getLiveContext() {
    return m_liveEditorContext;
  }

  private final ILiveEditorContext m_liveEditorContext = new ILiveEditorContext() {
    public XmlObjectInfo parse(String[] sourceLines) throws Exception {
      // prepare document
      String source = StringUtils.join(sourceLines, "\n");
      IDocument document = new Document(source);
      // prepare context
      UiBinderContext context = new UiBinderContext(m_state, m_classLoader, m_file, document);
      context.setLiveComponent(true);
      // do parse
      UiBinderParser parser = new UiBinderParser(context);
      return parser.parse();
    }

    public void dispose() throws Exception {
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  //
  // IDescriptionVersionsProvider
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Installs {@link IDescriptionVersionsProvider}'s.
   */
  private void configureDescriptionVersionsProviders() throws Exception {
    List<IDescriptionVersionsProviderFactory> factories =
        ExternalFactoriesHelper.getElementsInstances(
            IDescriptionVersionsProviderFactory.class,
            "org.eclipse.wb.core.descriptionVersionsProviderFactories",
            "factory");
    for (IDescriptionVersionsProviderFactory factory : factories) {
      // versions
      addVersions(factory.getVersions(m_javaProject, m_classLoader));
      // version providers
      {
        IDescriptionVersionsProvider provider = factory.getProvider(m_javaProject, m_classLoader);
        if (provider != null) {
          addDescriptionVersionsProvider(provider);
        }
      }
    }
  }
}
TOP

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

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.