content: /**
   * Copyright (c) 2013, Andre Steingress
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
   * following conditions are met:
   *
   * 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
   * disclaimer.
   * 2.) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
   * disclaimer in the documentation and/or other materials provided with the distribution.
   * 3.) Neither the name of Andre Steingress nor the names of its contributors may be used to endorse or
   * promote products derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
   * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  package org.gcontracts.grails;
  
  import org.codehaus.groovy.ast.*;
  import org.codehaus.groovy.ast.stmt.BlockStatement;
  import org.gcontracts.common.base.BaseLifecycle;
  import org.gcontracts.common.spi.ProcessingContextInformation;
  import org.gcontracts.generation.CandidateChecks;
  import org.gcontracts.generation.ClassInvariantGenerator;
  import org.gcontracts.util.AnnotationUtils;
  import org.objectweb.asm.Opcodes;
  
  /**
   * Implements a {@link org.gcontracts.common.spi.Lifecycle} in order to intercept GContracts
   * assertion generation mechanism and adapt it to specific requirements of the Spring
   * application container.<p/>
   *
   * @author ast
   */
  public class SpringContractsLifecycle extends BaseLifecycle {
  
      private static final String INITIALIZINGBEAN_INTERFACE = "org.springframework.beans.factory.InitializingBean";
      private static final String POSTCONSTRUCT_ANNOTATION = "javax.annotation.PostConstruct";
  
      private static final String POSTCONSTRUCT_METHOD_NAME = "$gcontracts_postConstruct";
      private static final String SPRING_STEREOTYPE_PACKAGE = "org.springframework.stereotype";
  
      private static final String IS_SPRING_STEREOTYPE = "isSpringStereotype";
  
      @Override
      public void beforeProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
          if (!CandidateChecks.isContractsCandidate(classNode)) return;
  
          boolean isSpringStereotype = AnnotationUtils.hasAnnotationOfType(classNode, SPRING_STEREOTYPE_PACKAGE);
  
          processingContextInformation.put(IS_SPRING_STEREOTYPE, isSpringStereotype);
          processingContextInformation.setConstructorAssertionsEnabled(!isSpringStereotype);
      }
  
      @Override
      public void afterProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
          if (!CandidateChecks.isContractsCandidate(classNode)) return;
          if (!(Boolean) processingContextInformation.get(IS_SPRING_STEREOTYPE)) return;
          if (processingContextInformation.contract().hasDefaultClassInvariant()) return;
  
          createPostConstructMethodForSpringBeans(processingContextInformation, classNode);
      }
  
      private void createPostConstructMethodForSpringBeans(ProcessingContextInformation pci, ClassNode type) {
          if (isAnyPostConstructionCallbackAvailable(type)) return;
  
          // add a synthetic post-construction method
          final MethodNode postConstructionMethodNode =
                  type.addMethod(POSTCONSTRUCT_METHOD_NAME, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
  
          final Class<?> postConstructAnnotationClass;
          try {
              postConstructAnnotationClass = SpringContractsLifecycle.class.getClassLoader().loadClass(POSTCONSTRUCT_ANNOTATION);
  
              final ClassNode postConstructAnnotationClassNode = ClassHelper.makeWithoutCaching(postConstructAnnotationClass);
              postConstructionMethodNode.addAnnotation(new AnnotationNode(postConstructAnnotationClassNode));
  
              final ClassInvariantGenerator classInvariantGenerator = new ClassInvariantGenerator(pci.readerSource());
              classInvariantGenerator.addInvariantAssertionStatement(type, postConstructionMethodNode);
  
          } catch (ClassNotFoundException e) {
              pci.addError("Annotation " + POSTCONSTRUCT_ANNOTATION + " could not be found in classpath!", type);
          }
      }
  
      private boolean isAnyPostConstructionCallbackAvailable(ClassNode type)  {
  
          boolean foundAnyPostConstructionCallback = false;
  
          // 1: the bean implements InitializingBean
          for (ClassNode interfaceClassNode : type.getAllInterfaces())  {
              if (interfaceClassNode.getName().equals(INITIALIZINGBEAN_INTERFACE))  {
                  foundAnyPostConstructionCallback = true;
                  break;
              }
          }
  
          // 2: method annotated with @PostConstruct (JEE5)
          for (MethodNode methodNode : type.getAllDeclaredMethods())  {
              if (AnnotationUtils.hasAnnotationOfType(methodNode, POSTCONSTRUCT_ANNOTATION))  {
                  foundAnyPostConstructionCallback = true;
                  break;
              }
          }
  
          return foundAnyPostConstructionCallback;
      }
  }
  