/*
 * Copyright (c) 1998-2006 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 SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.loader;

import java.io.*;

import java.util.*;

import java.util.logging.*;

import java.util.jar.Manifest;
import java.util.jar.Attributes;

import java.util.regex.Pattern;

import java.net.URL;

import java.security.CodeSource;

import java.security.cert.Certificate;

import com.caucho.util.CauchoSystem;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;

import com.caucho.vfs.Path;
import com.caucho.vfs.WriteStream;
import com.caucho.vfs.JarPath;
import com.caucho.vfs.Depend;

import com.caucho.log.Log;

import com.caucho.config.ConfigException;

import com.caucho.make.Dependency;
import com.caucho.make.DependencyContainer;

/**
 * JarEntry.
 */
class JarEntry {
  private static final L10N L = new L10N(JarEntry.class);
  private static final Logger log = Log.open(JarEntry.class);

  private Manifest _manifest;
  private JarPath _jarPath;
  private ArrayList<ClassPackage> _packages = new ArrayList<ClassPackage>();

  /**
   * Creates a JarEntry.
   */
  JarEntry(JarPath jarPath)
  {
    _jarPath = jarPath;

    readManifest();
  }

  /**
   * Reads the jar's manifest.
   */
  private void readManifest()
  {
    try {
      _manifest = _jarPath.getManifest();
      if (_manifest == null)
        return;

      Attributes attr = _manifest.getMainAttributes();
      if (attr != null)
        addManifestPackage("", attr);

      Map entries = _manifest.getEntries();
      
      Iterator iter = entries.keySet().iterator();
      while (iter.hasNext()) {
        String pkg = (String) iter.next();
        
        attr = _manifest.getAttributes(pkg);
        if (attr == null)
          continue;

        addManifestPackage(pkg, attr);
      }
    } catch (IOException e) {
      log.log(Level.WARNING, e.toString(), e);
    }
  }

  /**
   * Adds package information from the manifest.
   */
  private void addManifestPackage(String name, Attributes attr)
  {
    // only add packages
    if (! name.endsWith("/") && ! name.equals(""))
      return;

    String specTitle = attr.getValue("Specification-Title");
    String specVersion = attr.getValue("Specification-Version");
    String specVendor = attr.getValue("Specification-Vendor");
    String implTitle = attr.getValue("Implementation-Title");
    String implVersion = attr.getValue("Implementation-Version");
    String implVendor = attr.getValue("Implementation-Vendor");

    // If all none, then it isn't a package entry
    if (specTitle == null && specVersion == null && specVendor != null &&
        implTitle == null && implVersion == null && implVendor != null)
      return;

    ClassPackage pkg = new ClassPackage(name);
    pkg.setSpecificationTitle(specTitle);
    pkg.setSpecificationVersion(specVersion);
    pkg.setSpecificationVendor(specVendor);
    pkg.setImplementationTitle(implTitle);
    pkg.setImplementationVersion(implVersion);
    pkg.setImplementationVendor(implVendor);

    _packages.add(pkg);
  }

  /**
   * Validates the jar.
   */
  public void validate()
    throws ConfigException
  {
    if (_manifest != null)
      validateManifest(_jarPath.getContainer().getURL(), _manifest);
  }

  /**
   * Validates the manifest.
   */
  public static void validateManifest(String manifestName, Manifest manifest)
    throws ConfigException
  {
    Attributes attr = manifest.getMainAttributes();
    if (attr == null)
      return;

    String extList = attr.getValue("Extension-List");
    if (extList == null)
      return;

    Pattern pattern = Pattern.compile("[, \t]+");
    String []split = pattern.split(extList);

    for (int i = 0; i < split.length; i++) {
      String ext = split[i];

      String name = attr.getValue(ext + "-Extension-Name");
      if (name == null)
	continue;

      Package pkg = Package.getPackage(name);

      if (pkg == null) {
	log.warning(L.l("package {0} is missing.  {1} requires package {0}.",
			name, manifestName));
	continue;
      }

      String version = attr.getValue(ext + "-Specification-Version");

      if (version == null)
	continue;

      if (pkg.getSpecificationVersion() == null ||
	  pkg.getSpecificationVersion().equals("")) {
	log.warning(L.l("installed {0} is not compatible with version `{1}'.  {2} requires version {1}.",
		     name, version, manifestName));
      }
      else if (! pkg.isCompatibleWith(version)) {
	log.warning(L.l("installed {0} is not compatible with version `{1}'.  {2} requires version {1}.",
		     name, version, manifestName));
      }
    }
  }

  public ClassPackage getPackage(String name)
  {
    ClassPackage bestPackage = null;
    int bestLength = -1;

    for (int i = 0; i < _packages.size(); i++) {
      ClassPackage pkg = _packages.get(i);

      String prefix = pkg.getPrefix();

      if (name.startsWith(prefix) && bestLength < prefix.length()) {
        bestPackage = pkg;
        bestLength = prefix.length();
      }
    }

    return bestPackage;
  }

  public JarPath getJarPath()
  {
    return _jarPath;
  }

  /**
   * Returns the code source.
   */
  public CodeSource getCodeSource(String path)
  {
    try {
      Path jarPath = _jarPath.lookup(path);
      
      Certificate []certificates = jarPath.getCertificates();
	
      return new CodeSource(new URL(jarPath.getURL()), certificates);
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);

      return null;
    }
  }

  /**
   * Tests for equality.
   */
  public boolean equals(Object o)
  {
    if (! (o instanceof JarEntry))
      return false;

    JarEntry entry = (JarEntry) o;

    return _jarPath.equals(entry._jarPath);
  }
}
