Package com.google.gdt.eclipse.designer.uibinder.model.util

Source Code of com.google.gdt.eclipse.designer.uibinder.model.util.StylePropertySupport

/*******************************************************************************
* 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.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gdt.eclipse.designer.model.property.css.ContextDescription;
import com.google.gdt.eclipse.designer.model.property.css.FileContextDescription;
import com.google.gdt.eclipse.designer.model.property.css.StylePropertyEditor;
import com.google.gdt.eclipse.designer.model.property.css.StylePropertyEditorListener;
import com.google.gdt.eclipse.designer.uibinder.parser.AfterRunDesignTime;
import com.google.gdt.eclipse.designer.uibinder.parser.UiBinderContext;

import org.eclipse.wb.core.model.ObjectInfo;
import org.eclipse.wb.core.model.broadcast.EditorActivatedListener;
import org.eclipse.wb.core.model.broadcast.EditorActivatedRequest;
import org.eclipse.wb.core.model.broadcast.ObjectEventListener;
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.utils.jdt.core.JavaDocUtils;
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.DocumentTextNode;
import org.eclipse.wb.internal.core.xml.model.EditorContextCommitListener;
import org.eclipse.wb.internal.core.xml.model.utils.NamespacesHelper;
import org.eclipse.wb.internal.css.model.CssRuleNode;
import org.eclipse.wb.internal.css.parser.CssEditContext;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

/**
* Support for UiBinder template specific in {@link StylePropertyEditor}.
*
* @author scheglov_ke
* @coverage GWT.UiBinder.model
*/
public final class StylePropertySupport {
  private final UiBinderContext m_context;
  private final List<ContextDescription> m_contextDescriptions = Lists.newArrayList();

  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  public StylePropertySupport(UiBinderContext context) throws Exception {
    m_context = context;
    createExternalContextDescriptions();
    createLocalContextDescription();
    // add context descriptions
    m_context.getBroadcastSupport().addListener(null, new StylePropertyEditorListener() {
      @Override
      public void addContextDescriptions(ObjectInfo object, List<ContextDescription> contexts)
          throws Exception {
        syncExternalContextDescriptions();
        for (ContextDescription context : m_contextDescriptions) {
          contexts.add(0, context);
        }
      }
    });
    // reload after render
    m_context.getBroadcastSupport().addListener(null, new AfterRunDesignTime() {
      public void invoke() throws Exception {
        reloadExternalClientBundles();
      }
    });
    // refresh on external CSS file change
    m_context.getBroadcastSupport().addListener(null, new EditorActivatedListener() {
      public void invoke(EditorActivatedRequest request) throws Exception {
        for (ContextDescription contextDescription : m_contextDescriptions) {
          if (contextDescription.isStale()) {
            request.requestRefresh();
          }
        }
      }
    });
    // dispose on hierarchy dispose
    m_context.getBroadcastSupport().addListener(null, new ObjectEventListener() {
      @Override
      public void dispose() throws Exception {
        for (ContextDescription context : m_contextDescriptions) {
          context.dispose();
        }
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // <ui:with field='resources' type='fully.qualified.Resources'/>
  //
  ////////////////////////////////////////////////////////////////////////////
  private static class WithObject {
    String field;
    IType withType;
    Class<?> withClass;
  }

  private final List<WithObject> m_withObjects = Lists.newArrayList();

  /**
   * @return the {@link ContextDescription}s for "<ui:with>" elements.
   */
  private void createExternalContextDescriptions() throws Exception {
    createWithObjects();
    for (final WithObject withObject : m_withObjects) {
      ExecutionUtils.runIgnore(new RunnableEx() {
        public void run() throws Exception {
          createExternalContextDescriptions(withObject);
        }
      });
    }
  }

  /**
   * May be adds single {@link UiBinderExternalContextDescription}.
   */
  private void createExternalContextDescriptions(WithObject withObject) throws Exception {
    String field = withObject.field;
    IType withType = withObject.withType;
    Class<?> withClass = withObject.withClass;
    Map<Class<?>, IFile> styleToFile = getExternalPossibleCssFiles(withType);
    // check every CSS method in "withClass"
    for (Method styleMethod : withClass.getMethods()) {
      Class<?> styleClass = styleMethod.getReturnType();
      if (isCssResource(styleClass)) {
        IFile file = styleToFile.get(styleClass);
        if (file != null) {
          // prepare selectors accessible via this "styleClass"
          Set<String> styleSelectors = Sets.newHashSet();
          for (Method singleStyleMethod : styleClass.getMethods()) {
            styleSelectors.add("." + singleStyleMethod.getName());
          }
          // add ContextDescription
          m_contextDescriptions.add(new UiBinderExternalContextDescription(field,
              styleMethod.getName(),
              styleSelectors,
              file));
        }
      }
    }
  }

  /**
   * Checks if {@link UiBinderExternalContextDescription} is out of sync with its {@link IFile} and
   * then makes it sync.
   */
  private void syncExternalContextDescriptions() throws Exception {
    for (ListIterator<ContextDescription> I = m_contextDescriptions.listIterator(); I.hasNext();) {
      ContextDescription context = I.next();
      if (context instanceof UiBinderExternalContextDescription) {
        if (context.isStale()) {
          ContextDescription syncCopy =
              ((UiBinderExternalContextDescription) context).createSyncCopy();
          context.dispose();
          I.set(syncCopy);
        }
      }
    }
  }

  /**
   * Parses all "<ui:with>" elements and fills {@link #m_withObjects}.
   */
  private void createWithObjects() throws Exception {
    for (final DocumentElement withElement : getExternalStyleElements()) {
      ExecutionUtils.runIgnore(new RunnableEx() {
        public void run() throws Exception {
          String field = withElement.getAttribute("field");
          String withTypeName = withElement.getAttribute("type");
          IType withType = m_context.getJavaProject().findType(withTypeName);
          Class<?> withClass = m_context.getClassLoader().loadClass(withTypeName);
          {
            WithObject withObject = new WithObject();
            withObject.field = field;
            withObject.withType = withType;
            withObject.withClass = withClass;
            m_withObjects.add(withObject);
          }
        }
      });
    }
  }

  /**
   * Tries to find and reload all used <code>ClientBundle</code>s.
   */
  private void reloadExternalClientBundles() throws Exception {
    for (final WithObject withObject : m_withObjects) {
      ExecutionUtils.runIgnore(new RunnableEx() {
        public void run() throws Exception {
          Class<?> withClass = withObject.withClass;
          for (Field fieldRef : withClass.getDeclaredFields()) {
            Class<?> bundleClass = fieldRef.getType();
            if (ReflectionUtils.isStatic(fieldRef) && isClientBundle(bundleClass)) {
              reloadExternalClientBundleField(withObject, fieldRef);
            }
          }
        }
      });
    }
  }

  /**
   * Attempts to reload <code>ClientBundle</code> {@link Field} according to the specified JavaDoc
   * annotation.
   */
  private void reloadExternalClientBundleField(WithObject withObject, Field fieldRef)
      throws Exception {
    fieldRef.setAccessible(true);
    Class<?> bundleClass = fieldRef.getType();
    String bundleClassName = bundleClass.getName();
    // prepare JavaDoc lines
    List<String> javaDocLines;
    {
      String fieldName = fieldRef.getName();
      IField fieldModel = withObject.withType.getField(fieldName);
      javaDocLines = JavaDocUtils.getJavaDocLines(fieldModel, false);
    }
    // check GWT specific JavaDoc marker
    for (String javaDocLine : javaDocLines) {
      javaDocLine = javaDocLine.trim();
      // set "null" to force lazy re-creation
      if (javaDocLine.equals("@gwtd.reload.null")) {
        m_context.getState().getDevModeBridge().invalidateRebind(bundleClassName);
        fieldRef.set(null, null);
      }
      // re-create ClientBundle of same type
      if (javaDocLine.equals("@gwtd.reload.create")) {
        m_context.getState().getDevModeBridge().invalidateRebind(bundleClassName);
        Class<?> classGWT = m_context.getClassLoader().loadClass("com.google.gwt.core.client.GWT");
        Object newBundle =
            ReflectionUtils.invokeMethod(classGWT, "create(java.lang.Class)", bundleClass);
        // set new ClientBundle instance
        fieldRef.set(null, newBundle);
        // inject CssResource objects
        Method[] styleMethods = newBundle.getClass().getMethods();
        for (Method styleMethod : styleMethods) {
          if (styleMethod.getParameterTypes().length == 0
              && isCssResource(styleMethod.getReturnType())) {
            Object cssResource = styleMethod.invoke(newBundle);
            ReflectionUtils.invokeMethod(cssResource, "ensureInjected()");
          }
        }
      }
    }
  }

  /**
   * @return the {@link Map} of <code>ClientBundle</code> classes into resource {@link IFile}s.
   */
  private Map<Class<?>, IFile> getExternalPossibleCssFiles(final IType withType) throws Exception {
    final Map<Class<?>, IFile> styleToFile = Maps.newHashMap();
    // check every inner type
    for (final IType clientBundleType : withType.getTypes()) {
      if (isClientBundle(clientBundleType)) {
        ExecutionUtils.runIgnore(new RunnableEx() {
          public void run() throws Exception {
            addExternalPossibleCssFiles(styleToFile, withType, clientBundleType);
          }
        });
      }
    }
    return styleToFile;
  }

  /**
   * Adds CSS {@link IFile}s for {@link CssResource} methods of given {@link ClientBundle} type.
   */
  private void addExternalPossibleCssFiles(Map<Class<?>, IFile> styleToFile,
      IType withType,
      IType clientBundleType) throws Exception {
    IFolder withFolder = (IFolder) withType.getPackageFragment().getUnderlyingResource();
    String clientBundleName = clientBundleType.getFullyQualifiedName();
    Class<?> clientBundleClass = m_context.getClassLoader().loadClass(clientBundleName);
    // check every method
    for (Method styleMethod : clientBundleClass.getMethods()) {
      Class<?> styleClass = styleMethod.getReturnType();
      String fileName = getClientBundleSource(styleMethod);
      // we need CssResource return type, and existing @Source annotation
      if (isCssResource(styleClass) && fileName != null) {
        IFile file = withFolder.getFile(new Path(fileName));
        if (file.exists()) {
          styleToFile.put(styleClass, file);
        }
      }
    }
  }

  /**
   * @return the @Source path to resource.
   */
  @SuppressWarnings("unchecked")
  private String getClientBundleSource(AnnotatedElement obj) throws Exception {
    Class<Annotation> sourceClass =
        (Class<Annotation>) m_context.getClassLoader().loadClass(
            "com.google.gwt.resources.client.ClientBundle$Source");
    Annotation annotation = obj.getAnnotation(sourceClass);
    if (annotation != null) {
      return ((String[]) ReflectionUtils.invokeMethod(annotation, "value()"))[0];
    } else {
      return null;
    }
  }

  private static boolean isCssResource(Class<?> clazz) {
    return ReflectionUtils.isSuccessorOf(clazz, "com.google.gwt.resources.client.CssResource");
  }

  private static boolean isClientBundle(Class<?> clazz) throws JavaModelException {
    return ReflectionUtils.isSuccessorOf(clazz, "com.google.gwt.resources.client.ClientBundle");
  }

  private static boolean isClientBundle(IType type) throws JavaModelException {
    return CodeUtils.isSuccessorOf(type, "com.google.gwt.resources.client.ClientBundle");
  }

  /**
   * @return the {@link DocumentElement}s with "ui:with" tag.
   */
  private List<DocumentElement> getExternalStyleElements() {
    List<DocumentElement> elements = Lists.newArrayList();
    DocumentElement rootElement = m_context.getRootElement();
    String uiName = NamespacesHelper.getName(rootElement, "urn:ui:com.google.gwt.uibinder");
    for (DocumentElement withElement : rootElement.getChildren()) {
      if (withElement.getTag().equals(uiName + ":with")) {
        String field = withElement.getAttribute("field");
        String typeName = withElement.getAttribute("type");
        if (field != null && typeName != null) {
          elements.add(withElement);
        }
      }
    }
    return elements;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // UiBinderExternalContextDescription
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * {@link ContextDescription} for context from "<ui:style>" element.
   */
  private static class UiBinderExternalContextDescription extends FileContextDescription {
    private final String m_fieldName;
    private final String m_resourceName;
    private final Set<String> m_styleSelectors;

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public UiBinderExternalContextDescription(String fieldName,
        String resourceName,
        Set<String> styleSelectors,
        IFile file) throws Exception {
      super(file);
      m_fieldName = fieldName;
      m_resourceName = resourceName;
      m_styleSelectors = styleSelectors;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Special access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the {@link UiBinderExternalContextDescription} which is in sync with CSS file.
     */
    public UiBinderExternalContextDescription createSyncCopy() throws Exception {
      return new UiBinderExternalContextDescription(m_fieldName,
          m_resourceName,
          m_styleSelectors,
          getFile());
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Access
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public String getStyleName(CssRuleNode rule) {
      String selector = rule.getSelector().getValue();
      if (selector.startsWith(".")) {
        return MessageFormat.format(
            "'{'{0}.{1}.{2}'}'",
            m_fieldName,
            m_resourceName,
            selector.substring(1));
      }
      return null;
    }

    @Override
    public List<CssRuleNode> getRules() {
      List<CssRuleNode> rules = Lists.newArrayList();
      List<CssRuleNode> allRules = super.getRules();
      for (CssRuleNode rule : allRules) {
        String selector = rule.getSelector().getValue();
        if (m_styleSelectors.contains(selector)) {
          rules.add(rule);
        }
      }
      return rules;
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // <ui:style> implementation
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Creates {@link ContextDescription} for "<ui:style>" element.
   */
  private void createLocalContextDescription() throws Exception {
    DocumentElement styleElement = getLocalStyleElement();
    if (styleElement != null) {
      DocumentTextNode textNode = styleElement.getTextNode();
      String cssSource = textNode != null ? textNode.getRawText() : "";
      IDocument cssDocument = new Document(cssSource);
      CssEditContext cssContext = new CssEditContext(cssDocument);
      ContextDescription contextDescription =
          new UiBinderLocalContextDescription(m_context, styleElement, cssContext);
      m_contextDescriptions.add(contextDescription);
    }
  }

  /**
   * @return the "<ui:style>" element, may be <code>null</code>.
   */
  private DocumentElement getLocalStyleElement() {
    DocumentElement rootElement = m_context.getRootElement();
    String uiName = NamespacesHelper.getName(rootElement, "urn:ui:com.google.gwt.uibinder");
    return rootElement.getChild(uiName + ":style", true);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // UiBinderLocalContextDescription
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * {@link ContextDescription} for context from "<ui:style>" element.
   */
  private static class UiBinderLocalContextDescription extends ContextDescription {
    private final UiBinderContext m_uiContext;
    private final DocumentElement m_styleElement;

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public UiBinderLocalContextDescription(UiBinderContext uiContext,
        DocumentElement styleElement,
        CssEditContext cssContext) {
      super(cssContext);
      m_uiContext = uiContext;
      m_styleElement = styleElement;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Access
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public String getStyleName(CssRuleNode rule) {
      String selector = rule.getSelector().getValue();
      if (selector.startsWith(".")) {
        return "{style." + selector.substring(1) + "}";
      }
      return null;
    }

    @Override
    public void commit() throws Exception {
      getCommitListener().aboutToCommit();
      try {
        DocumentTextNode textNode = m_styleElement.getTextNode();
        String cssSource = getContext().getText();
        textNode.setText(cssSource);
      } finally {
        getCommitListener().doneCommit();
      }
    }

    private EditorContextCommitListener getCommitListener() {
      return m_uiContext.getBroadcastSupport().getListener(EditorContextCommitListener.class);
    }
  }
}
TOP

Related Classes of com.google.gdt.eclipse.designer.uibinder.model.util.StylePropertySupport

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.