//
//  ========================================================================
//  Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//


package org.eclipse.jetty.webapp;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;


/* ------------------------------------------------------------ */
/**
 * ClasspathPattern performs sequential pattern matching of a class name 
 * against an internal array of classpath pattern entries.
 * 
 * When an entry starts with '-' (minus), reverse matching is performed.
 * When an entry ends with '.' (period), prefix matching is performed.
 * 
 * When class is initialized from a classpath pattern string, entries 
 * in this string should be separated by ':' (semicolon) or ',' (comma).
 */

public class ClasspathPattern
{
    private static class Entry
    {
        public String classpath = null;
        public boolean result = false;
        public boolean partial = false;      
    }
    
    final private List<String> _patterns = new ArrayList<String>();
    final private List<Entry> _entries = new ArrayList<Entry>();
    
    /* ------------------------------------------------------------ */
    public ClasspathPattern()
    {
    }
    
    /* ------------------------------------------------------------ */
    public ClasspathPattern(String[] patterns)
    {
        setPatterns(patterns);
    }
    
    /* ------------------------------------------------------------ */
    public ClasspathPattern(String pattern)
    {
        setPattern(pattern);
    }
    

    /* ------------------------------------------------------------ */
    /**
     * Initialize the matcher by parsing each classpath pattern in an array
     * 
     * @param patterns array of classpath patterns
     */
    private void setPatterns(String[] patterns)
    {
        _patterns.clear();
        _entries.clear();
        addPatterns(patterns);
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @param patterns array of classpath patterns
     */
    private void addPatterns(String[] patterns)
    {
        if (patterns != null)
        {
            Entry entry = null; 
            for (String pattern : patterns)
            {
                entry = createEntry(pattern);
                if (entry != null) 
                {
                    _patterns.add(pattern);
                    _entries.add(entry);
                }
            }
        }
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @param patterns array of classpath patterns
     */
    private void prependPatterns(String[] patterns)
    {
        if (patterns != null)
        {
            Entry entry = null;
            int i=0;
            for (String pattern : patterns)
            {
                entry = createEntry(pattern);
                if (entry != null) 
                {
                    _patterns.add(i,pattern);
                    _entries.add(i,entry);
                    i++;
                }
            }
        }
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Create an entry object containing information about 
     * a single classpath pattern
     * 
     * @param pattern single classpath pattern
     * @return corresponding Entry object
     */
    private Entry createEntry(String pattern)
    {
        Entry entry = null;
        
        if (pattern != null)
        {
            String item = pattern.trim();
            if (item.length() > 0)
            {
                entry = new Entry();
                entry.result = !item.startsWith("-");
                entry.partial = item.endsWith(".");
                entry.classpath = entry.result ? item : item.substring(1).trim();
            }
        }
        return entry;
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Initialize the matcher by parsing a classpath pattern string
     * 
     * @param pattern classpath pattern string
     */
    public void setPattern(String pattern)
    {
        _patterns.clear();
        _entries.clear();
        addPattern(pattern);
    }

    /* ------------------------------------------------------------ */
    /**
     * Parse a classpath pattern string and appending the result
     * to the existing configuration.
     * 
     * @param pattern classpath pattern string
     */
    public void addPattern(String pattern)
    {
        ArrayList<String> patterns = new ArrayList<String>();
        StringTokenizer entries = new StringTokenizer(pattern, ":,");
        while (entries.hasMoreTokens())
        {
            patterns.add(entries.nextToken());
        }
        
        addPatterns(patterns.toArray(new String[patterns.size()]));
    }   
    

    /* ------------------------------------------------------------ */
    public void prependPattern(String classOrPackage)
    {
        ArrayList<String> patterns = new ArrayList<String>();
        StringTokenizer entries = new StringTokenizer(classOrPackage, ":,");
        while (entries.hasMoreTokens())
        {
            patterns.add(entries.nextToken());
        }
        
        prependPatterns(patterns.toArray(new String[patterns.size()]));
    }
    
    
    /* ------------------------------------------------------------ */
    /**
     * @return array of classpath patterns
     */
    public String[] getPatterns()
    {
        String[] patterns = null;
        
        if (_patterns!=null && _patterns.size() > 0)
        {
            patterns = _patterns.toArray(new String[_patterns.size()]);
        }
        
        return patterns;
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Match the class name against the pattern
     *
     * @param name name of the class to match
     * @return true if class matches the pattern
     */
    public boolean match(String name)
    {       
        boolean result=false;

        if (_entries != null)
        {
            name = name.replace('/','.');

            int startIndex = 0;

            while(startIndex < name.length() && name.charAt(startIndex) == '.') {
                startIndex++;
            }

            int dollar = name.indexOf("$");

            int endIndex =  dollar != -1 ? dollar : name.length();

            for (Entry entry : _entries)
            {
                if (entry != null)
                {               
                    if (entry.partial)
                    {
                        if (name.regionMatches(startIndex, entry.classpath, 0, entry.classpath.length()))
                        {
                            result = entry.result;
                            break;
                        }
                    }
                    else
                    {
                        int regionLength = endIndex-startIndex;
                        if (regionLength == entry.classpath.length()
                                && name.regionMatches(startIndex, entry.classpath, 0, regionLength))
                        {
                            result = entry.result;
                            break;
                        }
                    }
                }
            }
        }
        return result;
    }

}
