/*
 * @(#)BasicCommandMap.java 1.3 2006-07-20
 *
 * Copyright (c) 1999-2000 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.activation;

import ch.randelshofer.util.Strings;

import javax.activation.CommandMap;
import javax.activation.CommandInfo;
import javax.activation.DataContentHandler;
import java.util.Hashtable;
import java.util.Enumeration;

import java.io.*;

/**
 * This CommandMap supports multiple commands for the same MIME type.
 *
 * @author  Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland.
 * @version 1.3 2006-07-20 Reworked for Java 1.5. 
 * <br>1.2 2002-02-12 Return application/octet-stream' when data type can not be
 * determined.
 * <br>1.1.1 2002-02-05 Methods in class ch.randelshofer.util.Strings have been
 * renamed.
 * <br>1.1 2000-09-24  Method addCommands(Reader) added.
 * <br>1.0  1999-10-19
 */
public class BasicCommandMap
extends CommandMap {
    Hashtable map = new Hashtable();
    
    /**
     * Adds a command to the map.
     *
     * @param  mimeType  The MIME type of the command.
     * @param  cmdVerb   The command verb.
     * @param  cmdClass  The class name of the java bean that executes to verb.
     */
    public void addCommand(String mimeType, String cmdVerb, String cmdClass) {
        addCommandInfo(mimeType, new CommandInfo(cmdVerb,cmdClass));
    }
    
    /**
     * Adds commands provided by the input stream to the map.
     *
     * The input stream must comply to the mailcap file format.
     *
     * When a mailcap file is parsed, the MailcapCommandMap recognizes
     * certain parameter signatures, specifically those parameter names
     * that beginw ith x-java-. The MailcapCommandMap uses this signature
     * to find command entries for inclusion into its registries. Parameter
     * names with the form x-java-<name> are read by the MailcapCommandMap
     * as identifying a command with the name name.
     * The command implementation is specified by a fully qualified class
     * name of a JavaBean(tm) component. For example; a command for viewing
     * some data can be specified as: x-java-view=com.foo.ViewBean.
     *
     * MailcapCommandMap aware mailcap files have the following general form:
     *
     * <pre><code>
     * # Comments begin with a '#' and continue to the end of the line.<br>
     * &lt;mime type>; ; &lt;parameter list><br>
     * # Where a parameter list consists of one or more parameters,<br>
     * # where parameters look like: x-java-view=com.sun.TextViewer<br>
     * # and a parameter list looks like: <br>
     * text/plain; ; x-java-view=com.sun.TextViewer; x-java-edit=com.sun.TextEdit
     * <br>
     * # Note that mailcap entries that do not contain 'x-java' parameters<br>
     * # and comply to RFC 1524 are simply ignored:<br>
     * image/gif; /usr/dt/bin/sdtimage %s<br>
     * </code></pre>
     *
     * @param  in  The Reader providing the commands.
     */
    public void addCommands(Reader in)
    throws IOException {
        StreamTokenizer scanner = new StreamTokenizer(in);
        scanner.resetSyntax();
        scanner.eolIsSignificant(true);
        scanner.wordChars('A','Z');
        scanner.wordChars('a','z');
        scanner.wordChars('0','9');
        scanner.wordChars('.','.');
        scanner.wordChars('-','-');
        scanner.wordChars(')',')');
        scanner.wordChars('(','(');
        scanner.wordChars('/','/');
        scanner.wordChars('_','_');
        scanner.wordChars('*','*');
        scanner.wordChars(0x00a0,0x00ff);
        scanner.whitespaceChars(0x0000,' ');
        scanner.commentChar('#');
        
        while (scanner.nextToken() != StreamTokenizer.TT_EOF) {
            String mimeType = null;
            String cmdVerb = null;
            String cmdClass = null;
            
            if (scanner.ttype != StreamTokenizer.TT_EOL) {
                if (scanner.ttype != StreamTokenizer.TT_WORD) {
                    throw new IOException("line "+scanner.lineno()+" mime type expected.");
                }
                mimeType = scanner.sval;
                if (scanner.nextToken() != ';') {
                    throw new IOException("line "+scanner.lineno()+" ';' expected.");
                }
                while (scanner.nextToken() == ';') {
                    if (scanner.nextToken() != StreamTokenizer.TT_WORD
                    || ! scanner.sval.startsWith("x-java-")) {
                        throw new IOException("line "+scanner.lineno()+" command name expected.");
                    }
                    cmdVerb = Strings.translate(scanner.sval.substring(7),'-',' ');
                    if (scanner.nextToken() != '=') {
                        throw new IOException("line "+scanner.lineno()+" '=' expected.");
                    }
                    if (scanner.nextToken() != StreamTokenizer.TT_WORD) {
                        throw new IOException("line "+scanner.lineno()+" class name expected.");
                    }
                    cmdClass = scanner.sval;
                    
                    addCommandInfo(mimeType, new CommandInfo(cmdVerb,cmdClass));
                }
            }
        }
        
    }
    
    /**
     * Adds a command info to the map.
     *
     * @param  mimeType  The MIME type of the command.
     * @param  info    A CommandInfo object representing the command.
     */
    public void addCommandInfo(String mimeType, CommandInfo info) {
        mimeType = mimeType.toLowerCase();
        CommandInfo[] entry = (CommandInfo[])map.get(mimeType);
        if (entry == null) {
            map.put(mimeType,new CommandInfo[] { info });
        } else {
            CommandInfo[] entry2 = new CommandInfo[entry.length+1];
            System.arraycopy(entry,0,entry2,0,entry.length);
            entry2[entry.length] = info;
            map.put(mimeType,entry2);
        }
    }
    
    /**
     * Get the preferred command list from a MIME type.
     *
     * If commands with the same command verb occur more than one time,
     * then only the first command is returned.
     *
     * @return  Returns the preferred commands for that MIME type.
     */
    public CommandInfo[] getPreferredCommands(String mimeType) {
        mimeType = mimeType.toLowerCase();
        
        CommandInfo[] infos = (CommandInfo[])map.get(mimeType);
        if (infos == null) {
            return new CommandInfo[0];
        }
        
        // If a command name occurs more than one time,
        // then take only the first occurrence of it.
        Hashtable preferred = new Hashtable();
        for (int i=infos.length-1; i >= 0; i--) {
            preferred.put(infos[i].getCommandName(),infos[i]);
        }
        infos = new CommandInfo[preferred.size()];
        Enumeration enm=preferred.elements();
        int i=0;
        while(enm.hasMoreElements()) {
            infos[i++] = (CommandInfo)enm.nextElement();
        }
        return infos;
    }
    
    /**
     * Get all available commands for this type.
     *
     * @return Returns all CommandInfo objects for the MIME type.
     */
    public CommandInfo[] getAllCommands(String mimeType) {
        mimeType = mimeType.toLowerCase();
        
        CommandInfo[] infos = (CommandInfo[])map.get(mimeType);
        return infos == null ? new CommandInfo[0] : infos;
    }
    
    /**
     * Get the default command corresponding to
     * the MIME type.
     *
     * @return the CommandInfo corresponding to the command.
     */
    public CommandInfo getCommand(String mimeType, String cmdName) {
        mimeType = mimeType.toLowerCase();
        
        CommandInfo[] entry = (CommandInfo[])map.get(mimeType);
        if (entry != null) {
            for (int i=0; i < entry.length; i++) {
                if (entry[i].getCommandName().equals(cmdName)) {
                    return entry[i];
                }
            }
        }
        return null;
    }
    
    /**
     * Locate a DataContentHandler that corresponds to the MIME type.
     * The mechanism and semantics for determining this are determined by
     * the implementation of the particular CommandMap.
     */
    public DataContentHandler createDataContentHandler(String mimeType) {
        return null; // FIXME is this acceptable?
    }
}
