Standard Libraries

import default
import semmle.code.java.frameworks.spring.SpringXMLElement
import semmle.code.java.frameworks.spring.SpringBeanRefType
import semmle.code.java.frameworks.spring.SpringProperty
import semmle.code.java.frameworks.spring.SpringConstructorArg
import semmle.code.java.frameworks.spring.SpringLookupMethod
import semmle.code.java.frameworks.spring.SpringReplacedMethod
//////////////////////////////////////////////////////////////////////////////////////////
//reference: http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-child-bean-definitions
/** This class represents the <bean> elements in a Spring XML file.*/
class SpringBean extends SpringXMLElement {
  SpringBean() {
    this.getName() = "bean"
  }

  /** Returns the human-readable output for the bean*/
  string toString() {
    result = this.getBeanIdentifier()
  }

  /** is true if it is a top-level bean definition (i.e. not inside another element 
    * (except <beans>)) */
  predicate isTopLevel() {
    this.getParent().getName() = "beans"
  }
  
  /** is true if the bean has an id attribute*/
  predicate hasBeanId() {
    this.hasAttribute("id")
  }
  /** returns the value of the id attribute*/
  string getBeanId() {
    result = this.getAttribute("id").getValue()
  }

  /** is true if the bean has a name attribute*/
  predicate hasBeanName() {
    this.hasAttribute("name")
  }
  /** returns the value of the name attribute*/
  string getBeanName() {
    result = this.getAttribute("name").getValue()
  }

  /** is true if the bean has a name, id or class attribute*/
  predicate hasBeanIdentifier() {
    this.hasBeanName() or
    this.hasClassName() or
    this.hasBeanId()
  }

  /** returns the bean id or name, whichever is present, giving priority to id*/
  //NOTE: The current library does not support aliasing as yet, the bean lookup method
  //prioritizes ids, followed by the name
  string getBeanIdentifier() {
    if this.hasBeanId()
    then result = this.getBeanId()
    else (if this.hasBeanName()
          then result = this.getBeanName()
          else result = this.getClassName())
  }

  /** is true if the bean is abstract*/
  predicate isAbstract() {
    exists (XMLAttribute a | 
      a = this.getAttribute("abstract") and 
      a.getValue() = "true"
    )
  }

  /** Gets the raw value of the autowire attribute*/
  string getAutowireRaw() {
    result = this.getAttributeValueWithDefault("autowire")
  }

  /** Returns the autowire value for the bean, taking any default values from the 
    * enclosing <beans> element*/
  //REF: 3.6 always from current, ignores parent
  string getAutowire() {
    if this.getAutowireRaw() != "default" 
    then result = this.getAutowireRaw()
    else result = this.getSpringBeanFile().getDefaultAutowire()
  }

  /** Returs the value for the autowire-candidate attribute*/
  string getAutowireCandidate() {
    result = this.getAttributeValueWithDefault("autowire-candidate")
  }

  /** True if the bean has a class attribute*/
  predicate hasClassNameRaw() {
    this.hasAttribute("class")
  }
  /** Returns the value of the bean's class attribute, if it has one*/
  string getClassNameRaw() {
    result = this.getAttribute("class").getValue()
  }
  /** true if the bean has a class name, taking parent inheritance into account*/
  //REF: 3.6
  predicate hasClassName() {
    this.hasClassNameRaw() or
    this.getBeanParent().hasClassName()
  }
  /** returns the name of the bean's class, taking parent inheritance into account*/
  string getClassName() {
    if this.hasClassNameRaw() 
    then result = this.getClassNameRaw()
    else result = this.getBeanParent().getClassName()
  }

  /** returns the Java class referred to by the bean's class name*/
  RefType getClass() {
    result.getQualifiedName() = this.getClassName()
  }

  /** returns the value of the dependency-check attribute, if the bean has one*/
  string getDependencyCheckRaw() {
    result = this.getAttributeValueWithDefault("dependency-check")
  }

  /** returns the dependency-check value for the bean, taking any default values declared
    * in the enclosing <beans> element*/
  //3.6 always from current, ignores parent
  string getDependencyCheck() {
    if this.getDependencyCheckRaw() != "default"
    then result = this.getDependencyCheckRaw()
    else result = this.getSpringBeanFile().getDefaultDependencyCheck() 
  }
  
  /** returns the value of the depends-on attribute*/
  string getDependsOnString() {
    result = this.getAttributeValue("depends-on")
  }

  /** true if the bean has a destroy-method attribute*/
  predicate hasDestroyMethodNameRaw() {
    this.hasAttribute("destroy-method")
  }
  /** returns the value of the bean's destroy-method attribute*/
  string getDestroyMethodNameRaw() {
    result = this.getAttributeValue("destroy-method")
  }

  /** true if the bean has a destroy-method name, taking bean inheritance and <beans> 
    * defaults into account*/
  predicate hasDestroyMethodName() {
    this.hasDestroyMethodNameRaw() or
    this.getBeanParent().hasDestroyMethodName() or
    this.getSpringBeanFile().hasDefaultDestroyMethod()
  }
  //NOTE: The spring docs are a bit unclear about the effect of defaults on inheritance,
  //and this is an implementation based on the best interpretation of the documents.
  //As currently implemented, if no destroy-methods are declared except in the defaults,
  //the bean will inherit the default destroy-method of the nearest ancestor (including
  //itself) with a default defined
  /** returns the destroy-method name of the bean, taking bean inheritance and <beans> 
    * defaults into account*/
  string getDestroyMethodName() {
    if this.hasDestroyMethodNameRaw()
    then result = this.getAttributeValue("destroy-method")
    else (
      if this.getSpringBeanFile().hasDefaultDestroyMethod()
      then result = this.getSpringBeanFile().getDefaultDestroyMethod()
      else result = this.getBeanParent().getDestroyMethodName()
    )
  }
  /** gets the java method that corresponds to the bean's destroy-method*/
  Method getDestroyMethod() {
    exists (RefType superType |
      hasMethod(this.getClass(), result, superType) and
      result.getName() = this.getDestroyMethodName() and
      result.getNumberOfParameters() = 0
    )
  }

  /** true if the bean has a factory-bean attribute*/
  predicate hasFactoryBeanNameRaw() {
    this.hasAttribute("factory-bean")
  }
  /** gets the value of the factory-bean attribute*/
  string getFactoryBeanNameRaw() {
    result = this.getAttributeValue("factory-bean")
  }
  //3.6
  /** gets the name of the bean's factory-bean, taking bean inheritance into account*/
  string getFactoryBeanName() {
    if this.hasFactoryBeanNameRaw()
    then result = this.getFactoryBeanNameRaw()
    else result = this.getBeanParent().getFactoryBeanName()
  }

  /** true if the bean as a factory-method attribute*/
  predicate hasFactoryMethodNameRaw() {
    this.hasAttribute("factory-method")
  }
  /** returns the value of the factory-method attribute*/
  string getFactoryMethodNameRaw() {
    result = this.getAttributeValue("factory-method")
  }
  /** gets the name of the bean's factory-method, taking bean inheritance into account*/
  string getFactoryMethodName() {
    if this.hasFactoryMethodNameRaw()
    then result = this.getFactoryMethodNameRaw()
    else result = this.getBeanParent().getFactoryMethodName()
  }

  /** true if the bean has an init-method attribute*/
  predicate hasInitMethodNameRaw() {
    this.hasAttribute("init-method")
  }
  /** returns the value of the bean's init-method attribute*/
  string getInitMethodNameRaw() {
    result = this.getAttributeValue("init-method")
  }
  /** true if the bean has an init-method name, taking bean inheritance and <beans> 
    * defaults into account*/
  predicate hasInitMethodName() {
    this.hasInitMethodNameRaw() or
    this.getBeanParent().hasInitMethodName() or
    this.getSpringBeanFile().hasDefaultInitMethod()
  }
  //NOTE: The spring docs are a bit unclear about the effect of defaults on inheritance,
  //and this is an implementation based on the best interpretation of the documents.
  //As currently implemented, if no destroy method name is specified (except in defaults),
  //the default of the nearest ancestor (including itself) with a default defined
  //is inherited by the bean
  /** returns the init-method name of the bean, taking bean inheritance and <beans> 
    * defaults into account*/
  string getInitMethodName() {
    if this.hasInitMethodNameRaw()
    then result = this.getInitMethodNameRaw()
    else (
      if this.getSpringBeanFile().hasDefaultInitMethod()
      then result = this.getSpringBeanFile().getDefaultInitMethod()
      else result = this.getBeanParent().getInitMethodName()
    )
  }

  /** returns the java method that the init-method corresponds to*/
  Method getInitMethod() {
    exists (RefType superType |
      hasMethod(this.getClass(), result, superType) and
      result.getName() = this.getInitMethodName() and
      result.getNumberOfParameters() = 0
    )
  }

  /** returns the name of the bean's parent bean*/
  string getBeanParentName() {
    result = this.getAttributeValue("parent")
  }
  /** true if the bean has the parent attribute*/
  predicate hasBeanParentName() {
    this.hasAttribute("parent")
  }
  /** returns the SpringBean parent of this bean*/
  SpringBean getBeanParent() {
    result.getBeanIdentifier() = this.getBeanParentName()
  }
  /** true if this bean has a parent bean*/
  predicate hasBeanParent() {
    exists(SpringBean b | b = this.getBeanParent())
  }
  predicate hasBeanAncestor(SpringBean ancestor) {
    ancestor = this.getBeanParent() or
    this.getBeanParent().hasBeanAncestor(ancestor)
  }

  /** returns the value of the bean's lazy-init attribute*/
  string getLazyInitRaw() {
    result = this.getAttributeValueWithDefault("lazy-init")
  }
  //3.6 ignore parent beans
  /** is true if the bean is to be lazily initialized. Takes <beans> defaults into 
    * account*/
  predicate isLazyInit() {
    if this.getLazyInitRaw() != "default" 
    then this.getAttributeValue("lazy-init") = "true"
    else this.getSpringBeanFile().isDefaultLazyInit()
  }
  
  /** true if the bean has been declared to be a primary bean for autowiring*/
  predicate isPrimary() {
    exists(XMLAttribute a | a = this.getAttribute("primary") and a.getValue() = "true")
  }

  //scope. Default is singleton (from xsd spec)
  //3.6 ignore parent beans
  /** returns the scope of the bean*/
  string getScope() {
    if this.hasAttribute("scope")
    then result = this.getAttributeValue("scope")
    else result = "singleton"
  }

  /** true if this bean is similar to another bean. Currently just matches on the bean
    * identifier*/
  predicate isSimilar(SpringXMLElement other) {
    this.getBeanIdentifier() = ((SpringBean) other).getBeanIdentifier()
  }

  /** returns a <property> element declared in this bean (not inherited from parent beans)
    */
  SpringProperty getADeclaredProperty() {
    result = this.getASpringChild()
  }
  /** returns <property> elements inherited from parent beans*/
  SpringProperty getAnInheritedProperty() {
    (not exists (SpringProperty thisProperty | 
      thisProperty = this.getADeclaredProperty() and
      result.getPropertyName() = thisProperty.getPropertyName())
    ) and  (  
      result = this.getBeanParent().getADeclaredProperty() or 
      result = this.getBeanParent().getAnInheritedProperty()
    )
  }
  /** returns the <property> elements that apply to this bean (including those inherited 
    * from the parent bean)*/
  SpringProperty getAProperty() {
    result = this.getADeclaredProperty() or
    result = this.getAnInheritedProperty()
  }

  /** returns a <constructor-arg> element declared in this bean*/
  SpringConstructorArg getADeclaredConstructorArg() {
    result = this.getASpringChild()
  }
  /** returns a <constructor-arg> element inherited from the parent bean*/
  SpringConstructorArg getAnInheritedConstructorArg() {
    (not exists(SpringConstructorArg thisArg | 
      thisArg = this.getADeclaredConstructorArg() and
      thisArg.conflictsWithArg(result))
    ) and (
      result = this.getBeanParent().getADeclaredConstructorArg() or 
      result = this.getBeanParent().getAnInheritedConstructorArg()
    )
  }
  /** returns a <constructor-arg> element that applies to this bean (including those 
    * inherited from the parent bean)*/
  SpringConstructorArg getAConstructorArg() {
    result = this.getADeclaredConstructorArg() or 
    result = this.getAnInheritedConstructorArg()
  }

  /** returns a <lookup-method> element declared in this bean*/
  SpringLookupMethod getADeclaredLookupMethod() {
    result = this.getASpringChild()
  }
  //NOTE: The spring docs are a bit unclear about the behavior of inherited
  //lookup-methods. The implementation below assumes that 
  //lookup-methods are overriden if they have the same name.
  /** returns a <lookup-method> element inherited from the parent bean*/
  SpringLookupMethod getAnInheritedLookupMethod() {
    (not exists(SpringLookupMethod thisMethod |
      thisMethod = this.getADeclaredLookupMethod() and
      thisMethod.getMethodName() = result.getMethodName()
      )
    ) and  (
        result = this.getBeanParent().getADeclaredLookupMethod() or 
        result = this.getBeanParent().getAnInheritedLookupMethod()
    )
  }
  /** returns a <lookup-method> element that applies to this bean (including those 
    * inherited from the parent bean)*/
  SpringLookupMethod getALookupMethod() {
    result = this.getADeclaredLookupMethod() or
    result = this.getAnInheritedLookupMethod()
  }

  /** returns a <replaced-method> element declared in this bean*/
  SpringReplacedMethod getADeclaredReplacedMethod() {
    result = this.getASpringChild()
  }
  //NOTE: The spring docs are a bit unclear about the behavior of inherited
  //replaced-methods. The implementation below assumes that 
  //replaced-methods are overriden if they have the same name
  //NOTE: The current implementation does not yet take argtype child members into account
  /** returns a <replaced-method> element inherited from the parent bean*/
  SpringReplacedMethod getAnInheritedReplacedMethod() {
    (not exists (SpringReplacedMethod thisMethod |
      thisMethod = this.getADeclaredReplacedMethod() and
      thisMethod.getMethodName() = result.getMethodName()
      )
    ) and (
      result = this.getBeanParent().getADeclaredReplacedMethod() or 
      result = this.getBeanParent().getAnInheritedReplacedMethod()
    )
  }
  /** returns a <replaced-method> element that applies to this bean (including those 
    * inherited from the parent bean)*/
  SpringLookupMethod getAReplacedMethod() {
    result = this.getADeclaredReplacedMethod() or
    result = this.getAnInheritedReplacedMethod()
	}
	
}