/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/ejb/gen/SessionGenerator.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.ejb.gen;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.LocalBean;
import javax.ejb.Schedule;
import javax.ejb.Schedules;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;

import com.caucho.config.ConfigException;
import com.caucho.config.gen.AspectBeanFactory;
import com.caucho.config.gen.AspectGenerator;
import com.caucho.config.gen.BeanGenerator;
import com.caucho.config.reflect.AnnotatedTypeUtil;
import com.caucho.inject.Module;
import com.caucho.java.JavaWriter;
import com.caucho.util.L10N;

/**
 * Generates the skeleton for a session bean.
 */
@Module
abstract public class SessionGenerator<X> extends BeanGenerator<X> {
  private static final L10N L = new L10N(SessionGenerator.class);
  
  private boolean _hasNoInterfaceView;

  private ArrayList<AnnotatedType<? super X>> _localApi;
  private AnnotatedType<X> _localBean;
  
  private ArrayList<AnnotatedType<? super X>> _remoteApi;
  
  private ArrayList<AnnotatedMethod<? super X>> _annotatedMethods
    = new ArrayList<AnnotatedMethod<? super X>>();

  protected String _contextClassName = "dummy";

  private final NonBusinessAspectBeanFactory<X> _nonBusinessAspectBeanFactory;

  private final ArrayList<AspectGenerator<X>> _businessMethods 
    = new ArrayList<AspectGenerator<X>>();

  public SessionGenerator(String ejbName, 
                          AnnotatedType<X> beanType,
                          ArrayList<AnnotatedType<? super X>> localApi,
                          AnnotatedType<X> localBean,
                          ArrayList<AnnotatedType<? super X>> remoteApi, 
                          String beanTypeName)
  {
    super(toFullClassName(ejbName, beanType.getJavaClass().getName(), beanTypeName),
          beanType);

    _contextClassName = "dummy";

    _localApi = new ArrayList<AnnotatedType<? super X>>(localApi);
    _localBean = localBean;

    _remoteApi = new ArrayList<AnnotatedType<? super X>>(remoteApi);

    _nonBusinessAspectBeanFactory 
      = new NonBusinessAspectBeanFactory<X>(getBeanType());
 }

  public static String toFullClassName(String ejbName, String className,
                                       String beanType)
  {
    StringBuilder sb = new StringBuilder();

    sb.append(className);
    
    sb.append("__");
    
    if (! className.endsWith(ejbName)) {
      for (int i = 0; i < ejbName.length(); i++) {
        char ch = ejbName.charAt(i);

        if (ch == '/')
          sb.append('_');
        else if (Character.isJavaIdentifierPart(ch))
          sb.append(ch);
        else
          sb.append('_');
      }
    }
    
    sb.append(beanType);
    sb.append("Proxy");

    return sb.toString();
  }

  public boolean isStateless() 
  {
    return false;
  }

  public boolean hasNoInterfaceView()
  {
    // return _hasNoInterfaceView;
    return getLocalBean() != null;
  }
  
  /**
   * Returns the local API list.
   */
  public ArrayList<AnnotatedType<? super X>> getLocalApi()
  {
    return _localApi;
  }
  
  public AnnotatedType<X> getLocalBean()
  {
    return _localBean;
  }

  /**
   * Returns the remote API list.
   */
  public ArrayList<AnnotatedType<? super X>> getRemoteApi()
  {
    return _remoteApi;
  }
  
  /**
   * Returns the merged annotated methods
   */
  protected ArrayList<AnnotatedMethod<? super X>> getAnnotatedMethods()
  {
    return _annotatedMethods;
  }

  /**
   * Returns the introspected methods
   */
  @Override
  public ArrayList<AspectGenerator<X>> getMethods()
  {
    return _businessMethods;
  }

  /**
   * Introspects the bean.
   */
  @Override
  public void introspect()
  {
    super.introspect();
    
    if (getBeanType().isAnnotationPresent(LocalBean.class) 
        && ! getBeanType().getJavaClass().isInterface())
      _hasNoInterfaceView = true;
        
    if (_localApi.size() == 0 && _remoteApi.size() == 0)
      _hasNoInterfaceView = true;

    if (_hasNoInterfaceView) {
      AnnotatedType<? super X> localDefault = introspectLocalDefault();
      
      if (localDefault.getJavaClass().isInterface())
        _localApi.add(localDefault); 
      else {
        // we still want to introspect the methods, but don't add it as
        // a local api because it will be treated as an interface later
        introspectType(localDefault);
      }
    }
    
    for (AnnotatedType<? super X> type : _localApi) {
      introspectType(type);
    }
    
    for (AnnotatedType<? super X> type : _remoteApi) {
      introspectType(type);
    }
    
    introspectImpl();
    
    // this comes after the other introspection classes because all it
    // does is catch private timer methods and generate aspect wrappers
    // that wouldn't normally be generated if the method didn't have
    // timer behavior associated.
    if (isTimerSupported())
      introspectTimerMethods();
  }
  
  private void introspectType(AnnotatedType<? super X> type)
  {
    for (AnnotatedMethod<? super X> method : type.getMethods()) {
      introspectMethod(method);
    }
  }
  
  private void introspectMethod(AnnotatedMethod<? super X> method)
  {
    AnnotatedMethod<? super X> oldMethod 
      = findMethod(_annotatedMethods, method);
    
    if (oldMethod != null) {
      // XXX: merge annotations
      return;
    }
    
    AnnotatedMethod<? super X> baseMethod
      = findMethod(getBeanType().getMethods(), method);

    if (baseMethod == null)
      throw new IllegalStateException(L.l("{0} does not have a matching base method in {1}",
                                          method,
                                          getBeanType()));
    // XXX: merge annotations
    _annotatedMethods.add(baseMethod);
  }

  
  /**
   * Introspects the APIs methods, producing a business method for
   * each.
   */
  private void introspectImpl()
  {
    for (AnnotatedMethod<? super X> method : getAnnotatedMethods()) {
      // ejb/5015
      introspectMethodImpl(method);
    }
    
    for (AnnotatedMethod<? super X> method : getBeanType().getMethods()) {
      Method javaMethod = method.getJavaMember();
      
      if (method.isAnnotationPresent(PostConstruct.class)
          && javaMethod.getParameterTypes().length == 0) {
        // ejb/1060 only one post construct
        addPostConstructMethod(method);
      }
      else if (method.isAnnotationPresent(PreDestroy.class)
               && javaMethod.getParameterTypes().length == 0) {
        addPreDestroyMethod(method);
      }
    }
  }

  private void introspectMethodImpl(AnnotatedMethod<? super X> apiMethod)
  {
    Method javaMethod = apiMethod.getJavaMember();

    if (isBusinessMethod(javaMethod)) {
      addBusinessMethod(apiMethod);
    }
    else {
      if (javaMethod.getName().startsWith("ejb")) {
        throw new ConfigException(L.l("{0}: '{1}' must not start with 'ejb'.  The EJB spec reserves all methods starting with ejb.",
                                      javaMethod.getDeclaringClass(),
                                      javaMethod.getName()));
      }
    
      int modifiers = javaMethod.getModifiers();

      if (! Modifier.isPublic(modifiers)
          && ! Modifier.isPrivate(modifiers)
          && ! Modifier.isStatic(modifiers)
          && ! javaMethod.isSynthetic()
          && ! javaMethod.isBridge()) {
        addNonBusinessMethod(apiMethod);
      }
    }
  }
  
  private void introspectTimerMethods()
  {
    for (AnnotatedMethod<? super X> method : getAnnotatedMethods()) {
      introspectTimerMethod(method);
    }    
  }
  
  private void introspectTimerMethod(AnnotatedMethod<? super X> apiMethod)
  {
    Method javaMethod = apiMethod.getJavaMember();
      
    int modifiers = javaMethod.getModifiers();

    if (! isBusinessMethod(javaMethod) 
        && ! Modifier.isPublic(modifiers)
        && (javaMethod.isAnnotationPresent(Schedule.class)
            || javaMethod.isAnnotationPresent(Schedules.class))) {
      addScheduledMethod(apiMethod);
    }
  }
  
  protected void addBusinessMethod(AnnotatedMethod<? super X> method)
  {
    AspectGenerator<X> bizMethod = getAspectBeanFactory().create(method);
      
    if (bizMethod != null && ! _businessMethods.contains(bizMethod)) {
      _businessMethods.add(bizMethod);
    }
  }
  
  protected void addPostConstructMethod(AnnotatedMethod<? super X> method)
  {
    if (getLifecycleAspectFactory() == null)
      return;
    
    AspectGenerator<X> initMethod = getLifecycleAspectFactory().create(method);
      
    // ejb/5015
    if (initMethod != null) {
      _businessMethods.add(initMethod);
    }
    
    /*
    if (initMethod != null && ! _businessMethods.contains(initMethod)) {
      _businessMethods.add(initMethod);
    }
    */
  }
  
  protected void addPreDestroyMethod(AnnotatedMethod<? super X> method)
  {
    if (getLifecycleAspectFactory() == null)
      return;
    
    AspectGenerator<X> initMethod = getLifecycleAspectFactory().create(method);
      
    // ejb/5015
    /*
    if (initMethod != null && ! _businessMethods.contains(initMethod)) {
      _businessMethods.add(initMethod);
    }
    */
    if (initMethod != null) {
      _businessMethods.add(initMethod);
    }
  }

  protected void addNonBusinessMethod(AnnotatedMethod<? super X> method)
  {
    AspectGenerator<X> nonBizMethod 
      = _nonBusinessAspectBeanFactory.create(method);
      
    // XXX seems weird to add this to the _businessMethods, but the generation
    // is correct.
    if (nonBizMethod != null)
      _businessMethods.add(nonBizMethod);
  } 

  protected void addScheduledMethod(AnnotatedMethod<? super X> method)
  {
    AspectGenerator<X> bizMethod = 
      getScheduledAspectBeanFactory().create(method);
      
    if (bizMethod != null)
      _businessMethods.add(bizMethod);
  }
  
  private AnnotatedMethod<? super X> 
  findMethod(Collection<AnnotatedMethod<? super X>> methodList,
             AnnotatedMethod<? super X> method)
  {
    for (AnnotatedMethod<? super X> oldMethod : methodList) {
      if (AnnotatedTypeUtil.isMatch(oldMethod, method)) {
        return oldMethod;
      }
    }
    
    return null;
  }

  protected AnnotatedType<? super X> introspectLocalDefault()
  {
    return getBeanType();
  }
  
  protected void generateContentImpl(JavaWriter out,
                                     HashMap<String,Object> map)
    throws IOException
  {
    generateBeanPrologue(out, map);

    generateBusinessMethods(out, map);
    
    generateEpilogue(out, map);
    generateInject(out, map);
    generateDelegate(out, map);
    generatePostConstruct(out, map);
    generateDestroy(out, map);
  }

  @Override
  protected void generatePostConstruct(JavaWriter out, 
                                       HashMap<String,Object> map)
     throws IOException
  {
    generatePostConstructImpl(out, map);
  }
 
  protected AspectBeanFactory<X> getScheduledAspectBeanFactory()
  {
    throw new UnsupportedOperationException();
  }
  
  abstract protected boolean isTimerSupported();
  abstract protected AspectBeanFactory<X> getAspectBeanFactory();
  
  protected AspectBeanFactory<X> getLifecycleAspectFactory()
  {
    return null;
  }
  
  // abstract protected void generateBody(JavaWriter out) throws IOException;
  
  public static boolean isBusinessMethod(Method method)
  {
    if (method.getDeclaringClass().equals(Object.class))
      return false;
    if (method.getDeclaringClass().getName().startsWith("javax.ejb."))
      return false;
    if (method.getName().startsWith("ejb")) {
    }
    
    int modifiers = method.getModifiers();

    if (! Modifier.isPublic(modifiers))
      return false;
    
    if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers))
      return false;
    
    return true;
  }
}
