/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.deployers.vfs.plugins.structure.dir;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.structure.ContextInfo;
import org.jboss.deployers.vfs.plugins.structure.AbstractVFSStructureDeployer;
import org.jboss.deployers.vfs.spi.structure.StructureContext;
import org.jboss.util.collection.CollectionsFactory;
import org.jboss.virtual.VirtualFile;
import org.jboss.virtual.VirtualFileFilter;

/**
 * Similar to jar or directory structure,
 * being able to handle sub-dirs, but a bit more strict then dir structure.
 * Only listed sub-directories are candidates for potential sub-deployments.
 *
 * It also allows you to set metadata paths, libs and should the root be part of classpath.
 * Each of sub-dirs can be filtered per path or by default per lib or group.
 *
 * In order to accept anything one must first set shortCircuitFilter instance.
 *
 * @see org.jboss.deployers.vfs.plugins.structure.jar.JARStructure
 * @see org.jboss.deployers.vfs.plugins.structure.dir.DirectoryStructure
 *
 * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
 */
public class GroupingStructure extends AbstractVFSStructureDeployer
{
   private static final String[] META_INF = {"META-INF"};

   private VirtualFileFilter shortCircuitFilter; // by default null, so we don't accept anything
   private boolean rootClasspathEntry;

   // paths
   private String[] metaDataPaths;
   private Set<String> libs;
   private Set<String> groups;

   // filters
   private VirtualFileFilter libFilter;
   private VirtualFileFilter groupFilter;
   private Map<String, VirtualFileFilter> filters; // filter per path

   public GroupingStructure()
   {
      setRelativeOrder(9000); // before jar structure
      setRootClasspathEntry(true); // by default root is part of classpath
      setLibs(Collections.<String>emptySet()); // empty libs
      setMetaDataPaths(META_INF); // default metadata locations
      setGroups(CollectionsFactory.<String>createLazySet()); // lazy groups
      setFilters(Collections.<String, VirtualFileFilter>emptyMap()); // empty filters
   }

   public boolean determineStructure(StructureContext structureContext) throws DeploymentException
   {
      VirtualFile file = structureContext.getFile();

      if (shortCircuitFileCheck(file) == false)
         return false;

      ContextInfo context = null;
      try
      {
         context = createContext(structureContext, metaDataPaths);

         if (rootClasspathEntry)
            addClassPath(structureContext, file, true, true, context);

         // add any archives in libs
         for (String lib : libs)
         {
            VirtualFile libVF = file.getChild(lib);
            if (libVF != null)
            {
               VirtualFileFilter lf = filters.get(lib);
               if (lf == null)
                  lf = libFilter;

               List<VirtualFile> archives = libVF.getChildren(lf);
               for (VirtualFile archive : archives)
                  addClassPath(structureContext, archive, true, true, context);
            }
            else
            {
               if (log.isTraceEnabled())
                  log.trace("No such lib: " + lib + ", " + file);
            }
         }

         // check only children of defined sub-dirs / groups
         for (String group : groups)
         {
            VirtualFile groupVF = file.getChild(group);
            if (groupVF != null)
            {
               VirtualFileFilter gf = filters.get(group);
               if (gf == null)
                  gf = groupFilter;

               List<VirtualFile> children = groupVF.getChildren(gf);
               for (VirtualFile child : children)
                  structureContext.determineChildStructure(child);
            }
            else
            {
               if (log.isTraceEnabled())
                  log.trace("No such group: " + group + ", " + file);
            }
         }

         return true;
      }
      catch (Exception e)
      {
         // Remove the invalid context
         if (context != null)
            structureContext.removeChild(context);

         throw DeploymentException.rethrowAsDeploymentException("Error determining structure: " + file.getName(), e);
      }
   }

   /**
    * Do short circuit file check.
    *
    * @param file the file
    * @return true if we accept the file
    */
   protected boolean shortCircuitFileCheck(VirtualFile file)
   {
      return shortCircuitFilter != null && shortCircuitFilter.accepts(file);
   }

   /**
    * Set short circuit file filter.
    *
    * @param shortCircuitFilter the short circuit file filter
    */
   public void setShortCircuitFilter(VirtualFileFilter shortCircuitFilter)
   {
      this.shortCircuitFilter = shortCircuitFilter;
   }

   /**
    * Is root part of classpath.
    *
    * @param rootClasspathEntry root cp flag
    */
   public void setRootClasspathEntry(boolean rootClasspathEntry)
   {
      this.rootClasspathEntry = rootClasspathEntry;
   }

   /**
    * Set libs; added as cp entries.
    *
    * @param libs the libs
    */
   public void setLibs(Set<String> libs)
   {
      if (libs == null)
         throw new IllegalArgumentException("Null libs");

      this.libs = libs;
   }

   /**
    * Set default lib filter.
    *
    * @param libFilter the filter
    */
   public void setLibFilter(VirtualFileFilter libFilter)
   {
      this.libFilter = libFilter;
   }

   /**
    * Set the default metadata paths.
    *
    * @param metaDataPaths the meta data paths
    */
   public void setMetaDataPaths(String[] metaDataPaths)
   {
      this.metaDataPaths = metaDataPaths;
   }

   /**
    * Set groups.
    * Aka grouping sub-directories.
    *
    * @param groups the groups
    */
   public void setGroups(Set<String> groups)
   {
      if (groups == null)
         throw new IllegalArgumentException("Null groups");

      this.groups = groups;
   }

   /**
    * Add group.
    *
    * @param group the group
    */
   public void addGroup(String group)
   {
      groups.add(group);
   }

   /**
    * Remove group.
    *
    * @param group the group
    */
   public void removeGroup(String group)
   {
      groups.remove(group);
   }

   /**
    * Set default group filter.
    *
    * @param groupFilter the filter
    */
   public void setGroupFilter(VirtualFileFilter groupFilter)
   {
      this.groupFilter = groupFilter;
   }

   /**
    * Set filter per path.
    *
    * @param filters the filters
    */
   public void setFilters(Map<String, VirtualFileFilter> filters)
   {
      if (filters == null)
         throw new IllegalArgumentException("Null filters");

      this.filters = filters;
   }
}
