/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/loader/ClassEntry.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.loader;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.CodeSource;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.inject.Module;
import com.caucho.make.DependencyContainer;
import com.caucho.util.Alarm;
import com.caucho.util.ByteBuffer;
import com.caucho.util.JniTroubleshoot;
import com.caucho.util.L10N;
import com.caucho.util.QDate;
import com.caucho.vfs.Depend;
import com.caucho.vfs.Dependency;
import com.caucho.vfs.JarPath;
import com.caucho.vfs.Path;
import com.caucho.vfs.PersistentDependency;
import com.caucho.vfs.ReadStream;

/**
 * Describes a cached loaded class entry.
 */
@Module
public class ClassEntry implements Dependency {
  private static final L10N L = new L10N(ClassEntry.class);
  private static final Logger log
    = Logger.getLogger(ClassEntry.class.getName());
  
  private static boolean _hasJNIReload;
  private static boolean _hasAnnotations;
  private static final JniTroubleshoot _jniTroubleshoot;

  private DynamicClassLoader _loader;
  private String _name;
    
  private Path _classPath;

  private PersistentDependency _depend;

  private boolean _classIsModified;
    
  private Path _sourcePath;
  private long _sourceLastModified;
  private long _sourceLength;

  private ClassPackage _classPackage;

  private CodeSource _codeSource;

  private WeakReference<Class<?>> _clRef;

  /**
   * Create a loaded class entry
   *
   * @param name the classname
   * @param sourcePath path to the source Java file
   * @param classPath path to the compiled class file
   */
  public ClassEntry(DynamicClassLoader loader,
                    String name, Path sourcePath,
                    Path classPath,
                    CodeSource codeSource)
  {
    _loader = loader;
    _name = name;
    
    _classPath = classPath;

    setDependPath(classPath);

    if (sourcePath != null && ! sourcePath.equals(classPath)) {
      _sourcePath = sourcePath;
      
      _sourceLastModified = sourcePath.getLastModified();
      _sourceLength = sourcePath.getLength();
    }

    _codeSource = codeSource;
  }

  /**
   * Create a loaded class entry
   *
   * @param name the classname
   * @param sourcePath path to the source Java file
   * @param classPath path to the compiled class file
   */
  public ClassEntry(Loader loader,
                    String name, Path sourcePath,
                    Path classPath)
  {
    this(loader.getClassLoader(), name, sourcePath, classPath,
         loader.getCodeSource(classPath));
  }

  public String getName()
  {
    return _name;
  }

  /**
   * returns the class loader.
   */
  public DynamicClassLoader getClassLoader()
  {
    return _loader;
  }

  public CodeSource getCodeSource()
  {
    return _codeSource;
  }

  public Path getSourcePath()
  {
    return _sourcePath;
  }

  /**
   * Sets the depend path.
   */
  protected void setDependPath(Path dependPath)
  {
    if (dependPath instanceof JarPath)
      _depend = ((JarPath) dependPath).getDepend();
    else
      _depend = new Depend(dependPath);
  }

  /**
   * Adds the dependencies, returning true if it's adding itself.
   */
  protected boolean addDependencies(DependencyContainer container)
  {
    if (_classPath instanceof JarPath) {
      container.add(_depend);
      return false;
    }
    else if (_hasJNIReload) {
      container.add(this);
      return true;
    }
    else if (_sourcePath == null) {
      container.add(_depend);
      return false;
    }
    else {
      container.add(this);
      return true;
    }
  }
  
  public void setSourceLength(long length)
  {
    _sourceLength = length;
  }
  
  public void setSourceLastModified(long lastModified)
  {
    _sourceLastModified = lastModified;
  }
  
  public ClassPackage getClassPackage()
  {
    return _classPackage;
  }

  public void setClassPackage(ClassPackage pkg)
  {
    _classPackage = pkg;
  }

  /**
   * Returns true if the source file has been modified.
   */
  @Override
  public boolean isModified()
  {
    if (_depend.isModified()) {
      if (log.isLoggable(Level.FINE))
        log.fine("class modified: " + _depend);

      return reloadIsModified();
    }
    else if (_sourcePath == null) {
      return false;
    }  
    else if (_sourcePath.getLastModified() != _sourceLastModified) {
      if (log.isLoggable(Level.FINE))
        log.fine("source modified time: " + _sourcePath +
                 " old:" + QDate.formatLocal(_sourceLastModified) +
                 " new:" + QDate.formatLocal(_sourcePath.getLastModified()));

      if (! compileIsModified()) {
        return false;
      }
      
      boolean isModified = reloadIsModified();

      return isModified;
    }
    else if (_sourcePath.getLength() != _sourceLength) {
      if (log.isLoggable(Level.FINE))
        log.fine("source modified length: " + _sourcePath +
                 " old:" + _sourceLength +
                 " new:" + _sourcePath.getLength());

      if (! compileIsModified())
        return false;

      return reloadIsModified();
    }
    else {
      return false;
    }
  }

  /**
   * Returns true if the source file has been modified.
   */
  public boolean logModified(Logger log)
  {
    if (_depend.logModified(log)) {
      return true;
    }
    else if (_sourcePath == null)
      return false;
      
    else if (_sourcePath.getLastModified() != _sourceLastModified) {
      log.info("source modified time: " + _sourcePath +
               " old:" + QDate.formatLocal(_sourceLastModified) +
               " new:" + QDate.formatLocal(_sourcePath.getLastModified()));

      return true;
    }
    else if (_sourcePath.getLength() != _sourceLength) {
      log.info("source modified length: " + _sourcePath +
               " old:" + _sourceLength +
               " new:" + _sourcePath.getLength());

      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Returns true if the compile doesn't avoid the dependency.
   */
  public boolean compileIsModified()
  {
    return true;
  }

  /**
   * Returns true if the reload doesn't avoid the dependency.
   */
  public boolean reloadIsModified()
  {
    if (_classIsModified) {
      return true;
    }

    if (! _hasJNIReload || ! _classPath.canRead()) {
      return true;
    }
    
    try {
      long length = _classPath.getLength();

      Class<?> cl = _clRef != null ? _clRef.get() : null;

      if (cl == null) {
        return false;
      }

      if (cl.isAnnotationPresent(RequireReload.class))
        return true;

      ReadStream is = _classPath.openRead();

      byte []bytecode = new byte[(int) length];

      try {
        is.readAll(bytecode, 0, bytecode.length);
      } finally {
        is.close();
      }

      int result = reloadNative(cl, bytecode, 0, bytecode.length);

      if (result != 0) {
        _classIsModified = true;
        return true;
      }

      setDependPath(_classPath);

      if (_sourcePath != null) {
        _sourceLastModified = _sourcePath.getLastModified();
        _sourceLength = _sourcePath.getLength();
      }

      log.info("Reloading " + cl.getName());

      return false;
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);
      _classIsModified = true;

      return true;
    }
  }

  /**
   * Returns the path to the class file.
   */
  public Path getClassPath()
  {
    return _classPath;
  }

  public Class<?> getEntryClass()
  {
    return _clRef != null ? _clRef.get() : null;
  }

  public void setEntryClass(Class<?> cl)
  {
    _clRef = new WeakReference<Class<?>>(cl);
  }

  /**
   * preload actions.
   */
  public void preLoad()
    throws ClassNotFoundException
  {
  }

  /**
   * Loads the contents of the class file into the buffer.
   */
  public void load(ByteBuffer buffer)
    throws IOException
  {
    synchronized (this) {
      Path classPath = getClassPath();
    
      buffer.clear();

      int retry = 3;
      for (int i = 0; i < retry; i++) {
        long length = -1;
        
        ReadStream is = classPath.openRead();

        try {
          length = classPath.getLength();
          long lastModified = classPath.getLastModified();

          if (length < 0)
            throw new IOException("class loading failed because class file '" + classPath + "' does not have a positive length.  Possibly the file has been overwritten");

          buffer.setLength((int) length);

          int results = is.readAll(buffer.getBuffer(), 0, (int) length);

          if (results == length
              && length == classPath.getLength()
              && lastModified == classPath.getLastModified()) {
            return;
          }

          log.warning(L.l("{0}: class file length mismatch expected={1} received={2}.  The class file may have been modified concurrently.",
                this, length, results));
          
        } finally {
          is.close();
        }
      }
    }
  }

  /**
   * post-load actions.
   */
  public boolean postLoad()
  {
    return false;
  }

  public static boolean isReloadEnabled()
  {
    return _hasJNIReload;
  }

  public String toString()
  {
    if (_sourcePath == null)
      return getClass().getSimpleName() + "[" + _classPath + "]";
    else
      return getClass().getSimpleName() +  "[" + _classPath + ", src=" + _sourcePath + "]";
  }

  public static native boolean canReloadNative();

  public static native int reloadNative(Class<?> cl,
                                        byte []bytes, int offset, int length);

  class ReloadThread implements Runnable {
    private volatile boolean _isDone;

    public boolean isDone()
    {
      return _isDone;
    }
    
    public void run()
    {
    }
  }
  
  static {
    JniTroubleshoot jniTroubleshoot = null;

    try {
      System.loadLibrary("resin_os");
      _hasJNIReload = canReloadNative();

      if (Alarm.isTest()) {
        // skip logging for test
      }
      else if (_hasJNIReload)
        log.config("In-place class redefinition (HotSwap) is available.");
      else
        log.config("In-place class redefinition (HotSwap) is not available.  In-place class reloading during development requires a compatible JDK and -Xdebug.");

      jniTroubleshoot = new JniTroubleshoot(ClassEntry.class, "resin_os");
    } 
    catch (Throwable e) {
      jniTroubleshoot = new JniTroubleshoot(ClassEntry.class, "resin_os", e);
    }

    _jniTroubleshoot = jniTroubleshoot;

    _jniTroubleshoot.log();
  }
}
