Package com.carrotgarden.osgi.anno.scr.make

Source Code of com.carrotgarden.osgi.anno.scr.make.Builder

/**
* Copyright (C) 2010-2013 Andrei Pozolotin <Andrei.Pozolotin@gmail.com>
*
* All rights reserved. Licensed under the OSI BSD License.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package com.carrotgarden.osgi.anno.scr.make;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Property;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.carrotgarden.osgi.anno.scr.bean.AggregatorBean;
import com.carrotgarden.osgi.anno.scr.bean.ComponentBean;
import com.carrotgarden.osgi.anno.scr.bean.PropertyBean;
import com.carrotgarden.osgi.anno.scr.bean.PropertyFileBean;
import com.carrotgarden.osgi.anno.scr.bean.ProvideBean;
import com.carrotgarden.osgi.anno.scr.bean.ReferenceBean;
import com.carrotgarden.osgi.anno.scr.bean.ServiceBean;
import com.carrotgarden.osgi.anno.scr.conv.PropertyType;
import com.carrotgarden.osgi.anno.scr.util.Util;
import com.carrotgarden.osgi.anno.scr.util.UtilAsm;
import com.carrotgarden.osgi.anno.scr.util.UtilJdk;

/**
* Xstream bean builder.
*/
public class Builder {

  @SuppressWarnings("unused")
  private static final Logger log = LoggerFactory.getLogger(Builder.class);

  /**
   * Names of interfaces that should be excluded from
   * {@link Component#service()}
   * <p>
   * <provide interface="..."/>
   */
  private final Set<String> unwantedServiceSet;

  public Builder(final Set<String> unwantedServiceSet) {
    if (unwantedServiceSet == null) {
      this.unwantedServiceSet = new HashSet<String>();
    } else {
      this.unwantedServiceSet = unwantedServiceSet;
    }

  }

  /**
   * Build {@link ComponentBean}.
   */
  private void applyComponent(final ComponentBean component,
      final Class<?> type, final ClassNode classNode) throws Exception {

    final AnnotationNode annoNode = UtilAsm.componentAnno(classNode);

    if (annoNode == null) {
      return;
    }

    //

    final String name = UtilAsm.asString(annoNode, "name");
    if (name != null) {
      component.name = name;
    } else {
      component.name = type.getName();
    }

    final Boolean enabled = UtilAsm.asBoolean(annoNode, "enabled");
    if (enabled != null) {
      component.enabled = enabled;
    }

    final String factory = UtilAsm.asString(annoNode, "factory");
    if (factory != null) {
      component.factory = factory;
    }

    final Boolean immediate = UtilAsm.asBoolean(annoNode, "immediate");
    if (immediate != null) {
      component.immediate = immediate;
    }

    final ClassLoader loader = type.getClassLoader();

    final ConfigurationPolicy policy = UtilAsm.asEnum(loader, annoNode,
        "configurationPolicy");
    if (policy != null) {
      component.configPolicy = policy;
    }

    //

    component.implementation.klaz = type.getName();

    final Boolean servicefactory = UtilAsm.asBoolean(annoNode,
        "servicefactory");
    if (servicefactory != null) {
      component.service.servicefactory = servicefactory;
    }

    //

    applyPropertyKeyValue(component, annoNode, type);

    applyPropertyFileEntry(component, annoNode, type);

    //

    applyServiceDeclared(component, annoNode, type);

  }

  /**
   * Collect component life cycle annotations to {@link ComponentBean}.
   */
  private void applyLifecycle(final ComponentBean component,
      final Class<?> type, final ClassNode classNode) {

    int countActivate = 0;
    int countDeactivate = 0;
    int countModified = 0;

    @SuppressWarnings("unchecked")
    final List<MethodNode> mothodList = classNode.methods;

    for (final MethodNode methodNode : mothodList) {

      final String methodName = methodNode.name;

      final List<AnnotationNode> annoList = UtilAsm.combine(methodNode);

      if (Util.isListNone(annoList)) {
        continue;
      }

      for (final AnnotationNode annoNode : annoList) {

        final String annoDesc = annoNode.desc;

        if (UtilAsm.isActivateDesc(annoDesc)) {
          component.activate = methodName;
          countActivate++;
        }

        if (UtilAsm.isDeactivateDesc(annoDesc)) {
          component.deactivate = methodName;
          countDeactivate++;
        }

        if (UtilAsm.isModifiedDesc(annoDesc)) {
          component.modified = methodName;
          countModified++;
        }

      }

    }

    if (countActivate > 1) {
      throw new IllegalStateException("duplicate @Activate in class : "
          + type);
    }

    if (countDeactivate > 1) {
      throw new IllegalStateException("duplicate @Deactivate in class : "
          + type);
    }

    if (countModified > 1) {
      throw new IllegalStateException("duplicate @Modified in class : "
          + type);
    }

  }

  /**
   * Collect static final {@link Property} fields.
   */
  private void applyPropertyEmbedded(final ComponentBean component,
      final Class<?> type, final ClassNode node) throws Exception {

    @SuppressWarnings("unchecked")
    final List<FieldNode> fieldList = node.fields;

    if (Util.isListNone(fieldList)) {
      return;
    }

    for (final FieldNode fieldNode : fieldList) {

      final AnnotationNode annoNode = UtilAsm.propertyAnno(fieldNode);

      if (annoNode == null) {
        continue;
      }

      final String annoName = UtilAsm.asString(annoNode, "name");

      final String fieldName = fieldNode.name;

      final Field field = type.getDeclaredField(fieldName);

      field.setAccessible(true);

      final int modifiers = field.getModifiers();

      if (!Modifier.isStatic(modifiers)) {
        throw new IllegalArgumentException(
            "property field must be static : " + type + " / "
                + fieldName);
      }

      if (!Modifier.isFinal(modifiers)) {
        throw new IllegalArgumentException(
            "property field must be final : " + type + " / "
                + fieldName);
      }

      if (!PropertyType.isValidType(field.getType())) {
        throw new IllegalArgumentException(
            "property field type must be one of : ["
                + PropertyType.getList() + "] " + type + " / "
                + fieldName + " / " + field.getType());
      }

      //

      final String name = Util.isValidText(annoName) ? annoName
          : fieldName;

      final Object value;
      try {
        value = field.get(null);
      } catch (final Exception e) {
        throw new IllegalArgumentException(
            "property annotated value is invalid : " + type + " / "
                + fieldName, e);
      }

      final PropertyBean bean = new PropertyBean();

      bean.name = name;
      bean.type = PropertyType.from(value.getClass()).value;
      bean.value = "" + value;

      /** override if any */
      component.propertySet.remove(bean);
      component.propertySet.add(bean);

    }

  }

  /**
   * Collect {@link Component#properties()} entries.
   */
  private void applyPropertyFileEntry(final ComponentBean bean,
      final AnnotationNode annoNode, final Class<?> type) {

    final List<String> entryList = UtilAsm.asStringList(annoNode,
        "properties");

    if (Util.isListNone(entryList)) {
      return;
    }

    final String annoDesc = annoNode.desc;

    for (final String entry : entryList) {

      if (!Util.isValidText(entry)) {
        throw new IllegalArgumentException(
            "property file entry must not be empty : " + type
                + " / " + annoDesc);
      }

      final PropertyFileBean propBean = new PropertyFileBean();

      propBean.entry = entry;

      /** no duplicates */
      bean.propertyFileSet.remove(propBean);
      bean.propertyFileSet.add(propBean);

    }

  }

  /**
   * Collect {@link Component#property()} entries.
   */
  private void applyPropertyKeyValue(final ComponentBean bean,
      final AnnotationNode annoNode, final Class<?> klaz) {

    final List<String> entryList = UtilAsm.asStringList(annoNode,
        "property");

    if (Util.isListNone(entryList)) {
      return;
    }

    final String annoDesc = annoNode.desc;

    for (final String entry : entryList) {

      if (!Util.isValidText(entry)) {
        throw new IllegalStateException("property must not be empty : "
            + klaz + " / " + annoDesc);
      }

      if (!entry.contains("=")) {
        throw new IllegalStateException("property must contain '=' : "
            + klaz + " / " + annoDesc);
      }

      final int indexEquals = entry.indexOf("=");

      final String nameType = entry.substring(0, indexEquals);

      if (nameType.length() == 0) {
        throw new IllegalStateException(
            "property name must not be empty : " + klaz + " / "
                + annoDesc);
      }

      final String name;
      final String type;

      if (nameType.contains(":")) {

        final int indexColon = nameType.indexOf(":");

        name = nameType.substring(0, indexColon);
        type = PropertyType.from(nameType.substring(indexColon + 1)).value;

        if (name.length() == 0) {
          throw new IllegalStateException(
              "property name must not be empty : " + klaz + " / "
                  + annoDesc);
        }

      } else {
        name = nameType;
        type = PropertyType.STRING.value;
      }

      final String value = entry.substring(indexEquals + 1);

      final PropertyBean propBean = new PropertyBean();

      propBean.name = name;
      propBean.type = type;
      propBean.value = value;

      /** no duplicates */
      bean.propertySet.remove(propBean);
      bean.propertySet.add(propBean);

    }

  }

  /**
   * Collect bind methods from {@link Reference} annotations.
   */
  private void applyReference(final ComponentBean component,
      final Class<?> type, final ClassNode classNode) throws Exception {

    @SuppressWarnings("unchecked")
    final List<MethodNode> methodList = classNode.methods;

    if (Util.isListNone(methodList)) {
      return;
    }

    for (final MethodNode methodNode : methodList) {

      final AnnotationNode annoNode = UtilAsm.referenceAnno(methodNode);

      if (annoNode == null) {
        continue;
      }

      final ClassLoader loader = type.getClassLoader();

      if (!UtilAsm.isValidBindParam(loader, methodNode)) {
        throw new IllegalStateException(
            "invalid parameters for reference : " + methodNode.desc);
      }

      final ReferenceBean bean = makeReference(type, methodNode, annoNode);

      final Set<ReferenceBean> referenceSet = component.referenceSet;

      if (referenceSet.contains(bean)) {
        throw new IllegalStateException("duplicate reference : "
            + methodNode.desc);
      }

      referenceSet.add(bean);

    }

  }

  /**
   * Override collected service interfaces with explicit
   * {@link Component#service()} statement, if present.
   *
   * @throws Exception
   */
  private void applyServiceDeclared(final ComponentBean component,
      final AnnotationNode annoNode, final Class<?> type)
      throws Exception {

    final ClassLoader loader = type.getClassLoader();

    final List<Class<?>> serviceList = UtilAsm.asClassList(loader,
        annoNode, "service");

    if (Util.isListNone(serviceList)) {
      return;
    }

    final Set<ProvideBean> provideSet = component.service.provideSet;

    /** Discard previously collected interfaces. */
    provideSet.clear();

    for (final Class<?> service : serviceList) {

      if (!service.isAssignableFrom(type)) {
        throw new IllegalArgumentException(
            "annotated service must also be implemented : "
                + service + " / " + type);
      }

      final ProvideBean bean = new ProvideBean();

      bean.type = service.getName();

      provideSet.add(bean);

    }

  }

  /**
   * Collect all implied service interfaces.
   */
  private void applyServiceInferred(final ComponentBean component,
      final Class<?> type) {

    final Class<?>[] ifaceArray = type.getInterfaces();

    if (ifaceArray.length == 0) {
      return;
    }

    final ServiceBean service = component.service;

    for (final Class<?> iface : ifaceArray) {

      final ProvideBean bean = new ProvideBean();
      bean.type = iface.getName();

      /** no duplicates */
      if (service.provideSet.contains(bean)) {
        continue;
      }

      service.provideSet.add(bean);

    }

  }

  /**
   * cleanup and discard unwanted service interfaces
   */
  private void finalizeProvidedServices(final ComponentBean bean) {

    if (bean.service.provideSet.isEmpty()) {
      bean.service = null;
      return;
    }

    if (unwantedServiceSet.isEmpty()) {
      return;
    }

    final Set<ProvideBean> provideSet = new TreeSet<ProvideBean>();

    for (final ProvideBean provide : bean.service.provideSet) {
      if (unwantedServiceSet.contains(provide.type)) {
        continue; // ignore
      } else {
        provideSet.add(provide);
      }
    }

    if (provideSet.isEmpty()) {
      bean.service = null;
    } else {
      bean.service.provideSet = provideSet;
    }

  }

  public AggregatorBean makeAggregator(final List<Class<?>> klazList)
      throws Exception {

    final AggregatorBean aggregator = new AggregatorBean();

    for (final Class<?> klaz : klazList) {
      aggregator.componentList.add(makeComponent(klaz));
    }

    return aggregator;

  }

  /**
   * Super class annotations are overridden by derived classes in the type
   * hierarchy.
   */
  private ComponentBean makeComponent(final Class<?> klaz) throws Exception {

    final ComponentBean bean = new ComponentBean();

    final List<Class<?>> typeList = UtilJdk.inheritanceList(klaz);

    if (Util.isListNone(typeList)) {
      return bean;
    }

    for (final Class<?> type : typeList) {

      final ClassNode node = UtilAsm.classNode(type);

      applyServiceInferred(bean, type);

      applyPropertyEmbedded(bean, type, node);

      applyReference(bean, type, node);

      applyLifecycle(bean, type, node);

      // keep last
      applyComponent(bean, type, node);

    }

    finalizeProvidedServices(bean);

    return bean;

  }

  private ReferenceBean makeReference(final Class<?> type,
      final MethodNode bindMethod, final AnnotationNode annoNode)
      throws Exception {

    final ReferenceBean reference = new ReferenceBean();

    final String bindName = bindMethod.name;

    final String bindType = UtilAsm.firstParamType(bindMethod)
        .getClassName();

    //

    reference.type = bindType;

    final String target = UtilAsm.asString(annoNode, "target");
    if (target != null) {
      reference.target = target;
    }

    /**
     * Name must be unique in a component.
     * <p>
     * Use (interface type)/(target filter) combination.
     */
    final String name = UtilAsm.asString(annoNode, "name");
    if (name != null) {
      reference.name = name;
    } else {
      final String nameType = bindType;
      final String nameTarget = Util.isValidText(target) ? target : "*";
      final String nameDefault = nameType + "/" + nameTarget;
      reference.name = nameDefault;
    }

    final ClassLoader loader = type.getClassLoader();

    final ReferenceCardinality cardinality = UtilAsm.asEnum(loader,
        annoNode, "cardinality");
    if (cardinality != null) {
      reference.cardinality = cardinality;
    }

    final ReferencePolicy policy = UtilAsm.asEnum(loader, annoNode,
        "policy");
    if (policy != null) {
      reference.policy = policy;
    }

    reference.bind = bindName;

    reference.unbind = Util.unbindName(bindName);

    //

    UtilJdk.assertBindUnbindMatch(//
        type, bindType, reference.bind, reference.unbind);

    return reference;

  }

}
TOP

Related Classes of com.carrotgarden.osgi.anno.scr.make.Builder

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.