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

import java.net.URL;
import java.util.ArrayList;

import com.caucho.config.ConfigException;
import com.caucho.inject.Module;
import com.caucho.loader.EnvironmentApply;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.util.L10N;

/**
 * A jar artifact in the repository
 */
@Module
public class ArtifactManager
{
  private static final L10N L = new L10N(ArtifactManager.class);

  private EnvironmentClassLoader _loader;

  private ArrayList<ArtifactDependency> _dependencyList
    = new ArrayList<ArtifactDependency>();

  private ArrayList<ArtifactDependency> _pendingList
    = new ArrayList<ArtifactDependency>();

  private ArrayList<Artifact> _artifactList
    = new ArrayList<Artifact>();

  private ArrayList<Entry> _entryList
    = new ArrayList<Entry>();

  private ArrayList<ArtifactClassLoader> _loaderList
    = new ArrayList<ArtifactClassLoader>();

  public ArtifactManager(EnvironmentClassLoader loader)
  {
    _loader = loader;
  }

  public void addDependency(ArtifactDependency dependency)
  {
    ArtifactRepository repository = ArtifactRepository.getCurrent();

    if (repository == null) {
      throw new ConfigException(L.l("Artifact dependency org='{0}', name='{1}' is not valid because no artifact repositories have been defined",
                                    dependency.getOrg(),
                                    dependency.getName()));
    }

    ArrayList<Artifact> artifactList = repository.resolve(dependency);

    if (artifactList == null || artifactList.size() == 0) {
      ArtifactDependency plainDependency
        = new ArtifactDependency(dependency.getOrg(),
                                 null,
                                 dependency.getName(),
                                 null);

      artifactList = repository.resolve(plainDependency);
      
      throw new ConfigException(L.l("Artifact dependency '{0}', org='{1}', version={2} does not match any jars in the repository.  Available artifacts:{3}",
                                    dependency.getName(),
                                    dependency.getOrg(),
                                    dependency.getVersion().toDebugString(),
                                    toArtifactList(artifactList)));
    }

    _dependencyList.add(dependency);
    
    _pendingList.add(dependency);
  }

  private String toArtifactList(ArrayList<Artifact> artifactList)
  {
    StringBuilder sb = new StringBuilder();

    for (Artifact artifact : artifactList) {
      sb.append("\n  ").append(artifact.getName());
      sb.append(", org=").append(artifact.getOrg());

      if (artifact.getVersion() != null)
        sb.append(", version=").append(artifact.getVersion().toDebugString());
    }

    return sb.toString();
  }

  public void start()
  {
    resolve();
  }

  public Class findImportClass(String name)
  {
    resolve();

    for (int i = 0; i < _entryList.size(); i++) {
      Entry entry = _entryList.get(i);

      try {
        Class cl = entry.getLoader().findClassImpl(name);

        if (cl != null)
          return cl;
      } catch (ClassNotFoundException e) {
      }
    }
    
    return null;
  }

  public URL getImportResource(String name)
  {
    resolve();
    
    return null;
  }

  public void buildImportClassPath(ArrayList<String> cp)
  {
    resolve();

    for (Entry entry : _entryList) {
      entry.getLoader().buildClassPathImpl(cp);
    }
  }

  public void applyVisibleModules(EnvironmentApply apply)
  {
    resolve();
    
    for (Entry entry : _entryList) {
      apply.apply(entry.getLoader());
    }
  }

  private void resolve()
  {
    synchronized (this) {
      if (_pendingList.size() == 0)
        return;
      
      ArrayList<ArtifactDependency> pendingList
        = new ArrayList<ArtifactDependency>(_pendingList);

      ArtifactRepository repository = ArtifactRepository.getCurrent();

      ArrayList<Artifact> newArtifactList = new ArrayList<Artifact>();
      
      for (ArtifactDependency depend : pendingList) {
        ArrayList<Artifact> artifacts
          = repository.resolve(depend, _dependencyList);

        Artifact artifact = artifacts.get(0);

        _artifactList.add(artifact);
        newArtifactList.add(artifact);
      }

      for (Artifact artifact : newArtifactList) {
        ArrayList<Artifact> createList = new ArrayList<Artifact>();

        ArtifactClassLoader loader
          = createLoader(_loader.getParent(), artifact,
                         _artifactList, createList);

        _entryList.add(new Entry(artifact, loader));
      }
    }

    // XXX: timing
    for (Entry entry : _entryList) {
      entry.getLoader().start();
    }
  }

  private ArtifactClassLoader createLoader(ClassLoader parent,
                                           Artifact artifact,
                                           ArrayList<Artifact> artifactList,
                                           ArrayList<Artifact> createList)
  {
    if (createList.contains(artifact)) {
      throw new ConfigException(L.l("Dependency loop detected at {0} while creating artifact {1}",
                                    artifact,
                                    createList.get(0)));
    }

    createList.add(artifact);
    
    ArtifactRepository repository = ArtifactRepository.getCurrent();
    
    ArrayList<ArtifactClassLoader> importList
      = new ArrayList<ArtifactClassLoader>();

    ArrayList<Artifact> subArtifactList
      = new ArrayList<Artifact>();

    ArrayList<ArtifactDependency> dependencies
      = getArtifactDependencies(artifact);
      
    for (ArtifactDependency depend : dependencies) {
      ArrayList<Artifact> artifacts = repository.resolve(depend, dependencies);

      if (artifacts.size() == 0) {
        throw new ConfigException(L.l("Dependency org={0}, name={1} does not have any matching artifacts",
                                      depend.getOrg(),
                                      depend.getName()));
      }
      
      Artifact subArtifact = findArtifact(artifacts.get(0),
                                          artifactList);

      subArtifactList.add(subArtifact);
    }

    for (Artifact subArtifact : subArtifactList) {
      importList.add(createLoader(parent, subArtifact,
                                  subArtifactList, createList));
    }
    
    createList.remove(artifact);

    
    ArtifactClassLoader loader = findLoader(artifact);

    if (loader == null) {
      loader = new ArtifactClassLoader(_loader.getParent(),
                                       artifact,
                                       importList);

      _loaderList.add(loader);
    }

    return loader;
  }

  private ArrayList<ArtifactDependency>
    getArtifactDependencies(Artifact artifact)
  {
    ArrayList<ArtifactDependency> dependencies
      = new ArrayList<ArtifactDependency>();

    ArtifactDependency parentDep = artifact.getParent();

    if (parentDep != null) {
      ArtifactRepository repository = ArtifactRepository.getCurrent();
      
      ArrayList<Artifact> parentList = repository.resolve(parentDep);

      if (parentList == null || parentList.size() == 0)
        throw new ConfigException(L.l("maven parent {0} cannot be resolved",
                                      parentDep));

      Artifact parent = parentList.get(0);

      dependencies.addAll(getArtifactDependencies(parent));
    }

    for (ArtifactDependency dep : artifact.getDependencies()) {
      dependencies.add(dep);
    }

    return dependencies;
  }

  private Artifact findArtifact(Artifact newArtifact,
                                ArrayList<Artifact> artifactList)
  {
    for (Artifact artifact : artifactList) {
      if (newArtifact.isSameArtifact(artifact))
        return artifact;
    }

    return newArtifact;
  }
  
  private ArtifactClassLoader findLoader(Artifact artifact)
  {
    for (ArtifactClassLoader loader : _loaderList) {
      if (loader.getArtifact().equals(artifact))
        return loader;
    }

    return null;
  }
  
  @Override
  public String toString()
  {
    return (getClass().getSimpleName() + "[" + _loader + "]");
  }

  static class Entry {
    private Artifact _artifact;
    private ArtifactClassLoader _loader;

    Entry(Artifact artifact,
          ArtifactClassLoader loader)
    {
      _artifact = artifact;
      _loader = loader;
    }

    ArtifactClassLoader getLoader()
    {
      return _loader;
    }
  }
}
