package com.marinilli.b2.c6.bank;

import java.util.*;
import java.net.*;
import java.io.*;
import java.util.zip.*;

/**
 * Chapter 6 - The Custom Classloader
 *
 * @author Mauro Marinilli
 * @version 1.0
 */

public class BankClientLoader extends ClassLoader {
  private ApplicationHelper helper;

  /**
   * Constructor
   */
  public BankClientLoader(ApplicationHelper h) {
    helper = h;
  }

  /**
   * Gets a resource from the currently available resources
   * the order is the following: <br>
   *  - cached files <br>
   *  - super.classloader's resources <br>
   *  - server resources <br>
   */
  public URL getResource(String name) {
    URL resource = null;
    try {
      if (helper.isCached(name))
        return helper.getCachedFile(name).toURL();

      resource = super.getResource(name);
      // check the obtained resource
      try {
        System.out.println("BCL- resource="+resource.getContent());
        resource.getContent();
      } catch (Exception e) {
        // the url is empty or null
        System.out.println("BankClientLoader- getResource( "+
                            name+" ) EMPTY: look for it on the server");
        resource = helper.getRemoteFile(name, false).toURL();
      }
    } catch (IOException e) {
      System.out.println("BankClientLoader- getResource( "+name+" ): "+e);
    }
    return resource;
  }

  /**
   * see superclasss
   */
  public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    InputStream is = null;
    if (url != null) {
      try {
        is = url.openStream();
      } catch (IOException e) {
        System.out.println("BankClientLoader- getResourceAsStream( "+name+" ): "+e);
      }
    }
    return is;
  }

  /**
   * see superclasss
   */
  public Class loadClass(String name) throws ClassNotFoundException {
    // This is the actions sequence:
    //
    // 1. check if the class is in local cache,
    //    if it is the case, load the class in cache
    //
    // 2. if it's not in local cache, try with the super classloader
    //
    // 3. if it is not in cache and not loaded by the ancestor classloader
    //    then ask it directly to the remote server
    //
    // 4. if not found otherwise, quit throwing an exception

    Class neededClass = null;

    // 1. check if it is cached
    if (helper.isCached(helper.getCacheClassFileName(name)+".class")) {
      System.out.println("BankClientLoader- class: "+name+" loaded from local cache");
      neededClass = loadClass(helper.getCacheClassFileName(name), getClass(name));
    }

    // 2. if not found, see if super-classloader has class
    if (neededClass == null) {
      try {
        neededClass = super.findSystemClass(name);
        if (neededClass != null) {
          System.out.println("BankClientLoader- class: "+name+" loaded from super classloader");
          return neededClass;
        }
      } catch (ClassNotFoundException e) {
        // ignore this error
//        System.out.println("BankClientLoader- class not found in super classloader, name="+name);
      }
    }

    // 3. if still not found, look for it on the server, using a standard GET
    if (neededClass==null) {
      try {
        File f =  helper.getRemoteFile(name, true);

        if (f.exists()) {
          neededClass = loadClass(helper.getCacheClassFileName(name), getClass(name));
        }
        else
        System.out.println("BankClientLoader- - class: "+name+" not found on server.");
      } catch (Exception ex) {
        System.out.println("BankClientLoader- loadClass() while GET: "+ex);
      }
    }

    // 4. finally, throw an exception
    if (neededClass == null) {
      System.out.println("BankClassLoader- couldn't find class: "+ name +" anywhere.");
      throw new ClassNotFoundException("BankClassLoader- couldn't find class: "+ name);
    }
    return neededClass;
  }

  /**
   * see superclasss
   */
  public Class loadClass(String className, byte[] b){
    return defineClass(className, b, 0, b.length );
  }

  /**
   * This method returns a cached class
   * @return a cached class as a byte array
   */
  protected byte[] getClass(String className){
    // flatten up package information
    String fileName = helper.getCacheClassFileName(className);
    byte[] b = null;
    int size = 0;
    try {
      File f = helper.getCachedFile(fileName+".class");
      size = (int)f.length();
      b = new byte[size];
      DataInputStream dis =
          new DataInputStream(f.toURL().openConnection().getInputStream());
      dis.readFully(b);
    } catch (Exception e){
      System.out.println("BankClientLoader- getClass("+className+"): "+e);
    }
    return b;
  }

}