Package org.jboss.errai.databinding.rebind

Source Code of org.jboss.errai.databinding.rebind.DataBindingUtil

/*
* Copyright 2013 JBoss, by Red Hat, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.jboss.errai.databinding.rebind;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;

import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.Variable;
import org.jboss.errai.codegen.exception.GenerationException;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.util.PrivateAccessType;
import org.jboss.errai.codegen.util.PrivateAccessUtil;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.common.metadata.RebindUtils;
import org.jboss.errai.config.rebind.EnvUtil;
import org.jboss.errai.config.util.ClassScanner;
import org.jboss.errai.databinding.client.api.Bindable;
import org.jboss.errai.databinding.client.api.DataBinder;
import org.jboss.errai.ioc.rebind.ioc.injector.InjectUtil;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectableInstance;
import org.jboss.errai.ui.shared.api.annotations.AutoBound;
import org.jboss.errai.ui.shared.api.annotations.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gwt.core.ext.GeneratorContext;

/**
* Utility to retrieve a data binder reference. The reference is either to an
* injected {@link AutoBound} data binder or to a generated data binder for an
* injected {@link Model}.
*
* @author Christian Sadilek <csadilek@redhat.com>
* @author Mike Brock
*/
public class DataBindingUtil {
  private static final Logger log = LoggerFactory.getLogger(DataBindingUtil.class);
  public static final String TRANSIENT_BINDER_VALUE = "DataModelBinder";
  public static final String BINDER_MODEL_TYPE_VALUE = "DataBinderModelType";

  public static final Annotation[] MODEL_QUALIFICATION = new Annotation[] {
    new Model() {
      @Override
      public Class<? extends Annotation> annotationType() {
        return Model.class;
      }
    }
  };
 
  private DataBindingUtil() {}

  /**
   * Represents a reference to an injected or generated data binder.
   */
  public static class DataBinderRef {
    private final MetaClass dataModelType;
    private final Statement valueAccessor;

    public DataBinderRef(final MetaClass dataModelType, final Statement valueAccessor) {
      this.dataModelType = dataModelType;
      this.valueAccessor = valueAccessor;
    }

    public MetaClass getDataModelType() {
      return dataModelType;
    }

    public Statement getValueAccessor() {
      return valueAccessor;
    }
  }

  /**
   * Tries to find a data binder reference for either an injected {@link Model}
   * or an injected {@link AutoBound} data binder.
   *
   * @param inst
   *          the injectable instance
   *
   * @return the data binder reference or null if not found.
   */
  public static DataBinderRef lookupDataBinderRef(final InjectableInstance<?> inst) {
    DataBinderRef ref = lookupBinderForModel(inst);
    if (ref == null) {
      ref = lookupAutoBoundBinder(inst);
    }
    return ref;
  }

  /**
   * Tries to find a data binder reference for an injected {@link Model}.
   *
   * @param inst
   *          the injectable instance
   *
   * @return the data binder reference or null if not found.
   */
  private static DataBinderRef lookupBinderForModel(final InjectableInstance<?> inst) {
    Statement dataBinderRef;
    MetaClass dataModelType;

    InjectUtil.BeanMetric beanMetric = InjectUtil.getFilteredBeanMetric(inst.getInjectionContext(), inst.getInjector()
            .getInjectedType(), Model.class);

    if (!beanMetric.getAllInjectors().isEmpty()) {
      final Collection<Object> allInjectors = beanMetric.getAllInjectors();
      if (allInjectors.size() > 1) {
        throw new GenerationException("Multiple @Models injected in " + inst.getEnclosingType());
      }
      else if (allInjectors.size() == 1) {
        final Object injectorElement = allInjectors.iterator().next();

        if (injectorElement instanceof MetaConstructor || injectorElement instanceof MetaMethod) {
          final MetaParameter mp = beanMetric.getConsolidatedMetaParameters().iterator().next();

          dataModelType = mp.getType();
          assertTypeIsBindable(dataModelType);
          dataBinderRef = inst.getTransientValue(TRANSIENT_BINDER_VALUE, DataBinder.class);
          inst.ensureMemberExposed();
        }
        else {
          final MetaField field = (MetaField) allInjectors.iterator().next();

          dataModelType = field.getType();
          assertTypeIsBindable(dataModelType);

          dataBinderRef = inst.getTransientValue(TRANSIENT_BINDER_VALUE, DataBinder.class);
          inst.getInjectionContext().addExposedField(field, PrivateAccessType.Both);
        }
        return new DataBinderRef(dataModelType, dataBinderRef);
      }
    }
    else {
      List<MetaField> modelFields = inst.getInjector().getInjectedType().getFieldsAnnotatedWith(Model.class);
      if (!modelFields.isEmpty()) {
        throw new GenerationException("Found one or more fields annotated with @Model but missing @Inject "
                + modelFields.toString());
      }

      List<MetaParameter> modelParameters = inst.getInjector().getInjectedType()
              .getParametersAnnotatedWith(Model.class);
      if (!modelParameters.isEmpty()) {
        throw new GenerationException(
                "Found one or more constructor or method parameters annotated with @Model but missing @Inject "
                        + modelParameters.toString());
      }
    }

    return null;
  }

  /**
   * Tries to find a reference for an injected {@link AutoBound} data binder.
   *
   * @return the data binder reference or null if not found.
   */
  private static DataBinderRef lookupAutoBoundBinder(final InjectableInstance<?> inst) {
    Statement dataBinderRef = null;
    MetaClass dataModelType = null;

    InjectUtil.BeanMetric beanMetric = InjectUtil.getFilteredBeanMetric(inst.getInjectionContext(), inst.getInjector()
            .getInjectedType(), AutoBound.class);

    final Collection<Object> allInjectors = beanMetric.getAllInjectors();
    if (allInjectors.size() > 1) {
      throw new GenerationException("Multiple @AutoBound data binders injected in " + inst.getEnclosingType());
    }
    else if (allInjectors.size() == 1) {
      final Object injectorElement = allInjectors.iterator().next();

      if (injectorElement instanceof MetaConstructor || injectorElement instanceof MetaMethod) {
        final MetaParameter mp = beanMetric.getConsolidatedMetaParameters().iterator().next();

        assertTypeIsDataBinder(mp.getType());
        dataModelType = (MetaClass) mp.getType().getParameterizedType().getTypeParameters()[0];
        dataBinderRef = inst.getInjectionContext().getInlineBeanReference(mp);
        inst.ensureMemberExposed();
      }
      else {
        final MetaField field = (MetaField) allInjectors.iterator().next();

        assertTypeIsDataBinder(field.getType());
        dataModelType = (MetaClass) field.getType().getParameterizedType().getTypeParameters()[0];
        dataBinderRef = Stmt.invokeStatic(inst.getInjectionContext().getProcessingContext().getBootstrapClass(),
                PrivateAccessUtil.getPrivateFieldInjectorName(field),
                Variable.get(inst.getInjector().getInstanceVarName()));
        inst.getInjectionContext().addExposedField(field, PrivateAccessType.Both);
      }
    }
    else {
      final MetaClass declaringClass = inst.getInjector().getInjectedType();
      for (final MetaField field : declaringClass.getFields()) {
        if (field.isAnnotationPresent(AutoBound.class)) {
          assertTypeIsDataBinder(field.getType());
          dataModelType = (MetaClass) field.getType().getParameterizedType().getTypeParameters()[0];
          dataBinderRef = Stmt.invokeStatic(inst.getInjectionContext().getProcessingContext().getBootstrapClass(),
                  PrivateAccessUtil.getPrivateFieldInjectorName(field),
                  Variable.get(inst.getInjector().getInstanceVarName()));
          inst.getInjectionContext().addExposedField(field, PrivateAccessType.Both);
          break;
        }
      }
    }

    return (dataBinderRef != null) ? new DataBinderRef(dataModelType, dataBinderRef) : null;
  }

  /**
   * Ensures the provided type is a {@link DataBinder} and throws a
   * {@link GenerationException} in case it's not.
   *
   * @param type
   *          the type to check
   */
  private static void assertTypeIsDataBinder(MetaClass type) {
    final MetaClass databinderMetaClass = MetaClassFactory.get(DataBinder.class);

    if (!databinderMetaClass.isAssignableFrom(type)) {
      throw new GenerationException("Type of @AutoBound element must be " + DataBinder.class.getName() + " but is: "
              + type.getFullyQualifiedName());
    }
  }

  /**
   * Ensured the provided type is bindable and throws a
   * {@link GenerationException} in case it's not.
   *
   * @param type
   *          the type to check
   */
  private static void assertTypeIsBindable(MetaClass type) {
    if (!type.isAnnotationPresent(Bindable.class) && !getConfiguredBindableTypes().contains(type)) {
      throw new GenerationException(type.getName() + " must be a @Bindable type when used as @Model");
    }
  }

  /**
   * Checks if the provided type is bindable.
   *
   * @param type
   *          the type to check
   *
   * @return true if the provide type is bindable, otherwise false.
   */
  public static boolean isBindableType(MetaClass type) {
    return (type.isAnnotationPresent(Bindable.class) || getConfiguredBindableTypes().contains(type));
  }

  /**
   * Returns all bindable types on the classpath.
   *
   * @param context
   *          the current generator context
   *
   * @return a set of meta classes representing the all bindable types (both
   *         annotated and configured in ErraiApp.properties).
   */
  public static Set<MetaClass> getAllBindableTypes(final GeneratorContext context) {
    Collection<MetaClass> annotatedBindableTypes = ClassScanner.getTypesAnnotatedWith(Bindable.class,
            RebindUtils.findTranslatablePackages(context), context);

    Set<MetaClass> bindableTypes = new HashSet<MetaClass>(annotatedBindableTypes);
    bindableTypes.addAll(DataBindingUtil.getConfiguredBindableTypes());
    return bindableTypes;
  }

  private static Set<MetaClass> configuredBindableTypes = null;

  /**
   * Reads bindable types from all ErraiApp.properties files on the classpath.
   *
   * @return a set of meta classes representing the configured bindable types.
   */
  public static Set<MetaClass> getConfiguredBindableTypes() {
    if (configuredBindableTypes != null) {
      return configuredBindableTypes;
    }
   
    Set<MetaClass> bindableTypes = new HashSet<MetaClass>();
    final Collection<URL> erraiAppProperties = EnvUtil.getErraiAppProperties();
    for (URL url : erraiAppProperties) {
      InputStream inputStream = null;
      try {
        log.debug("Checking " + url.getFile() + " for bindable types...");
        inputStream = url.openStream();

        final ResourceBundle props = new PropertyResourceBundle(inputStream);
        for (final String key : props.keySet()) {
          if (key.equals("errai.ui.bindableTypes")) {
            for (final String s : props.getString(key).split(" ")) {
              try {
                bindableTypes.add(MetaClassFactory.get(s.trim()));
              } catch (Exception e) {
                throw new RuntimeException("Could not find class defined in ErraiApp.properties as bindable type: " + s);
              }
            }
            break;
          }
        }
      } catch (IOException e) {
        throw new RuntimeException("Error reading ErraiApp.properties", e);
      } finally {
        if (inputStream != null) {
          try {
            inputStream.close();
          } catch (IOException e) {
            log.warn("Failed to close input stream", e);
          }
        }
      }
    }
   
    configuredBindableTypes = bindableTypes;
    return configuredBindableTypes;
  }
}
TOP

Related Classes of org.jboss.errai.databinding.rebind.DataBindingUtil

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.