/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package com.sun.jdo.api.persistence.enhancer.util;

import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.io.File;
import java.io.IOException;
import java.io.FilenameFilter;


/**
 * ClassPath provides class file lookup according to a classpath 
 * specification.
 */

public class ClassPath {
  /* The entire class path specification */
  private String theClassPathSpec;

  /* Linked list of class path elements */
  private ClassPathElement theClassPath;

  /**
   * Construct a class path from the input String argument.
   * The path is expected to be in the form appropriate for the current
   * execution environment.
   */
  public ClassPath(String path) {
    theClassPathSpec = path;
    parsePath();
  }

  /**
   * locate a class file given a fully qualified class name
   */
  public ClassFileSource findClass(String className) {
    return findClass(className, theClassPath);
  }

  /**
   * locate a class file given a fully qualified class name
   * starting at the specified class path element
   */
  static ClassFileSource findClass(String className, ClassPathElement path) {
    for (ClassPathElement e = path; e != null; e = e.next()) {
      ClassFileSource source = e.sourceOf(className);
      if (source != null) {
	source.setSourceElement(e);
	return source;
      }
    }

    return null;
  }

  /**
   * Return a file name which might reasonably identify a file containing
   * the specified class.  The name is a "./" relative path.
   */
  public static String fileNameOf(String className, char separator) {
    StringBuffer path = new StringBuffer();
    StringTokenizer parser = new StringTokenizer(className, "./", false);//NOI18N
    for (boolean first = true; parser.hasMoreElements(); first = false) {
      if (!first)
	path.append(separator);
      path.append(parser.nextToken());
    }
    path.append(".class");//NOI18N
    return path.toString();
  }


  /**
   * Return a file name which might reasonably identify a file containing
   * the specified class.  The name is a "./" relative path.
   */
  public static String fileNameOf(String className) {
    return fileNameOf(className, File.separatorChar);
  }


  /**
   * Return a file name which might reasonably identify a file containing
   * the specified class in a zip file.
   */
  public static String zipFileNameOf(String className) {
    return fileNameOf(className, '/');
  }


  /**
   * Return the vm class name which corresponds to the input file name.
   * The file name is expected to be a "./" relative path.
   * Returns null if the file name doesn't end in ".class"
   */
  public static String classNameOf(String fileName) {
    int fnlen = fileName.length();
    if (fnlen > 6 && fileName.regionMatches(true, fnlen - 6, ".class", 0, 6)) {//NOI18N
      /* the file name ends with .class */
      fileName = fileName.substring(0, fileName.length()-6);
      StringBuffer className = new StringBuffer();
      StringTokenizer parser = new StringTokenizer(fileName, "\\/", false);//NOI18N
      for (boolean first = true; parser.hasMoreElements(); first = false) {
	if (!first)
	  className.append('/');
	className.append(parser.nextToken());
      }
      return className.toString();
    }
    return null;
  }

  /**
   * Remove any class path elements which match directory
   */
  public boolean remove(File directory) {
    boolean matched = false;
    ClassPathElement firstElement = theClassPath;
    ClassPathElement prevElement = null;
    for (ClassPathElement cpe = firstElement; cpe != null; cpe = cpe.next()) {
      if (cpe.matches(directory)) {
	matched = true;
	if (prevElement == null)
	  firstElement = cpe.next();
	else
	  prevElement.setNext(cpe.next());
      } else {
	prevElement = cpe;
      }
    }
    theClassPath = firstElement;
    return matched;
  }

  /**
   * Append a directory to the classpath.
   */
  public void append(File directory) {
    append(ClassPathElement.create(directory.getPath()));
  }

  /**
   * Append a class path element to the classpath.
   */
  public void append(ClassPathElement anElement) {
    if (theClassPath == null)
      theClassPath = anElement;
    else
      theClassPath.append(anElement);
  }

  /**
   * Return an enumeration of all of the class files in the specified 
   * package in this class path.
   * @param packageName specifies the VM format package name 
   *    to which class files must belong.
   * @returns an Enumeration of the VM format class names which
   *    can be found.  Note that the Enumeration value is of type String
   *    and duplicate entries may be returned as the result of finding
   *    a class through more than one class path element.  Note also
   *    that the class name returned might not correspond the the
   *    name of the class in the file.
   */
  public Enumeration classesInPackage(String packageName) {
    return new ClassPackageEnumeration(this, packageName);
  }

  /* package local accessors */
  ClassPathElement getPathElements() {
    return theClassPath;
  }

  /* private accessors */

  private void parsePath() {
    StringTokenizer parser = 
      new StringTokenizer(theClassPathSpec,
			  java.io.File.pathSeparator,
			  false /* dont return delimiters */
			  );
    
    ClassPathElement lastElement = null;
    while (parser.hasMoreElements()) {
      ClassPathElement anElement = ClassPathElement.create(parser.nextToken());

      if (lastElement == null)
	theClassPath = anElement;
      else
	lastElement.append(anElement);

      lastElement = anElement;
    }
  }

}

/**
 * An enumeration class which returns the names of the classes which
 * can be found in a class path
 */

class ClassPackageEnumeration implements Enumeration {
  /* The next class path element to look for matches in once
     the current enumeration is complete */
  private ClassPathElement nextClassPathElement; 

  /* The package name */
  private String thePackageName;

  /* The enumeration of matching class names in the current class path
     element */
  private Enumeration currentElementEnumeration;

  /**
   * Construct a ClassPackageEnumeration.
   * @param classPath The class path in which to search for classes.
   * @param packageName The VM name of the package in which to search.
   */
  ClassPackageEnumeration(ClassPath classPath, String packageName) {
    nextClassPathElement = classPath.getPathElements();
    thePackageName = packageName;
  }

  public boolean hasMoreElements() {
    while ((currentElementEnumeration == null ||
	    !currentElementEnumeration.hasMoreElements()) &&
	   nextClassPathElement != null) {
      currentElementEnumeration = 
	nextClassPathElement.classesInPackage(thePackageName);
      nextClassPathElement = nextClassPathElement.next();
    }

    return (currentElementEnumeration != null &&
	    currentElementEnumeration.hasMoreElements());
  }

  public Object nextElement() {
    if (hasMoreElements())
      return currentElementEnumeration.nextElement();

    throw new NoSuchElementException();
  }
}

