/* UserDefinedQualifier
 * This file is part of Artemis
 *
 * Copyright(C) 2013  Genome Research Limited
 *
 * This program 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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
package uk.ac.sanger.artemis.components;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.filechooser.FileFilter;

import uk.ac.sanger.artemis.FeatureVector;
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.Selection;
import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
import uk.ac.sanger.artemis.components.genebuilder.cv.DatePanel;
import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
import uk.ac.sanger.artemis.io.EntryInformationException;
import uk.ac.sanger.artemis.io.Qualifier;
import uk.ac.sanger.artemis.io.QualifierVector;
import uk.ac.sanger.artemis.util.Document;
import uk.ac.sanger.artemis.util.FileDocument;
import uk.ac.sanger.artemis.util.LinePushBackReader;
import uk.ac.sanger.artemis.util.ReadOnlyException;
import uk.ac.sanger.artemis.util.StringVector;
import uk.ac.sanger.artemis.util.URLDocument;

/**
 * Load and make available qualifiers from a list in a file 
 * of tag=value pairs and from OBO files.
 */
class UserDefinedQualifiers extends JFrame
{
  private static final long serialVersionUID = 1L;
  private static org.apache.log4j.Logger logger4j = 
      org.apache.log4j.Logger.getLogger(UserDefinedQualifiers.class);
  
  /* collection of loaded files */
  private Vector<QualifierList> obos = new Vector<QualifierList>();
  
  /* component containing list of available values */
  private Box qualifierBox = Box.createHorizontalBox();
  
  /* listener for adding qualifiers **/
  private ActionListener addListener;
  
  /* text area in the feature editor to add qualifiers to */
  private QualifierTextArea qualifier_text_area;
  private Selection selection;
  private JPanel mainPanel;
  
  /* line pattern for tag=value */
  private static Pattern TAG_VALUE_PATTERN = Pattern.compile("^/?(\\w+) ?= ?([^\\n\\r]+)$");
  
  UserDefinedQualifiers()
  {
    super("Qualifier List");
    mainPanel = (JPanel) getContentPane();
    MultiLineToolTipUI.initialize();
    read();
    createComponentToAddCvTerm();
    createMenus();
  }
  
  /**
   * Menu creation
   */
  private void createMenus()
  {
    final JMenuBar menuBar = new JMenuBar();
    final JMenu fileMenu = new JMenu("File");
    menuBar.add(fileMenu);
    final JMenuItem importFile = new JMenuItem("Import qualifiers from file ...");
    importFile.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        importFromFile();
      }
    });
    fileMenu.add(importFile);
    
    final JMenuItem importURL = new JMenuItem("Import qualifiers from URL ...");
    importURL.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        importFromUrl();
      }
    });
    fileMenu.add(importURL);
    
    final JMenuItem pasteQualifiers = new JMenuItem("Paste qualifiers ...");
    pasteQualifiers.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        final QualifierTextArea qualifierText = new QualifierTextArea();
        int status = JOptionPane.showConfirmDialog(UserDefinedQualifiers.this, 
            qualifierText, "Paste in qualifier lists in the format: name = value",
            JOptionPane.OK_CANCEL_OPTION);
        
        if(status == JOptionPane.OK_OPTION)
        {
          QualifierList obo = new QualifierList();
          obo.qualifiers = loadText(new ByteArrayInputStream(qualifierText.getText().getBytes()));
          obos.add(obo);
          createComponentToAddCvTerm();
          mainPanel.revalidate();
          
          writeQualifierList(qualifierText.getText());
        }

      }
    });
    fileMenu.add(pasteQualifiers);
    fileMenu.addSeparator();
    
    final JMenu removeFile = new JMenu("Qualifier source(s)");
    removeFile.addMenuListener(new MenuListener(){
      public void menuCanceled(MenuEvent e){}
      public void menuDeselected(MenuEvent e){}

      public void menuSelected(MenuEvent e)
      {
        removeFile.removeAll();
        for(final QualifierList obo: obos)
        {
          final JCheckBoxMenuItem oboMenu =
              new JCheckBoxMenuItem(obo.doc.getName(), obo.isAcive);
          removeFile.add(oboMenu);
          oboMenu.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
              obo.isAcive = oboMenu.isSelected();
              createComponentToAddCvTerm();
              mainPanel.revalidate();
              mainPanel.repaint();
            }
          });
        }
      }
    });
    fileMenu.add(removeFile);
    setJMenuBar(menuBar);
  }

  /**
   * Add components to the panel for adding CvTerm's to
   * the annotation.
   */
  private void createComponentToAddCvTerm()
  {
    mainPanel.removeAll();
    mainPanel.setLayout(new GridBagLayout());
    final GridBagConstraints c = new GridBagConstraints();

    final JCheckBox ignoreCase = new JCheckBox("Ignore case",true);
    final Vector<String> names = getQualiferNames();
    final JComboBox nameCombo = new JComboBox(names);   
    final JButton addButton = new JButton("ADD");
    final JCheckBox addToAllSelected = new JCheckBox("add to all selected features", false);
    final JTextField keyWord = new JTextField(45);
    final Dimension d  = new Dimension(500, ignoreCase.getPreferredSize().height);
    final Dimension d2 = new Dimension(300, ignoreCase.getPreferredSize().height);
    
    int row = 0;
    c.gridx = 0;
    c.gridy = row;
    c.anchor = GridBagConstraints.EAST;
    
    if(names.size() < 1)
    {
      c.gridwidth = 2;
      mainPanel.add(new JLabel("Import a list of qualifiers from the File menu."), c);
      c.gridwidth = 1;
      ignoreCase.setEnabled(false);
      nameCombo.setEnabled(false);
      addButton.setEnabled(false);
      addToAllSelected.setEnabled(false);
      keyWord.setEnabled(false);
    }
    else
    {
      mainPanel.add(new JLabel("Name: "), c);
      c.gridx = 1;
      c.anchor = GridBagConstraints.WEST;
      mainPanel.add(nameCombo, c);
    }
    
    c.gridx = 2;
    mainPanel.add(Box.createHorizontalStrut(150), c);
    
    // keyword
    keyWord.setSelectionStart(0);
    keyWord.setSelectionEnd(keyWord.getText().length());
    keyWord.setSelectedTextColor(Color.blue);
    keyWord.setMinimumSize(d2);
    keyWord.addActionListener(new ActionListener(){
      // carry out search when enter key is pressed
      public void actionPerformed(ActionEvent event)
      {
        searchQualifiers(keyWord.getText(), nameCombo, 
            ignoreCase.isSelected(), addButton, addToAllSelected, d);
      }
    });
    c.gridy = ++row;
    c.gridx = 0;
    c.anchor = GridBagConstraints.EAST;
    mainPanel.add(new JLabel("Keywords: "),c);
    c.gridx = 1;
    c.anchor = GridBagConstraints.WEST;
    mainPanel.add(keyWord,c);

    c.gridy = ++row;
    mainPanel.add(ignoreCase,c);
     
    // search button
    c.gridx = 0;
    c.gridy = ++row;
    c.anchor = GridBagConstraints.WEST;
    final JButton search = new JButton("SEARCH");
    search.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        searchQualifiers(keyWord.getText(), nameCombo, 
            ignoreCase.isSelected(), addButton, addToAllSelected, d);
      }
    });
    mainPanel.add(search,c);
    
    c.gridx = 0;
    c.gridy = ++row;
    c.gridwidth = 3;
    qualifierBox.add(Box.createVerticalStrut(25));
    mainPanel.add(qualifierBox, c);

    c.gridx = 0;
    c.gridy = ++row;
    c.gridwidth = 1;
    mainPanel.add(addButton, c);  

    c.gridx = 1;
    final JButton closeButton = new JButton("CLOSE");
    mainPanel.add(closeButton, c);
    closeButton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        dispose();
      }
    });
    
    c.gridx = 0;
    c.gridy = ++row;
    c.gridwidth = 2;
    mainPanel.add(addToAllSelected,c);
    
    c.gridy = ++row;
    mainPanel.add(Box.createVerticalStrut(15), c);
  }

  private StringVector searchOboQualifiers(final QualifierList obo, 
                                           final String qName, 
                                           final String keyWord,  
                                           final boolean ignoreCase)
  {
    final Qualifier q = obo.qualifiers.getQualifierByName(qName);
    if(q == null)
      return null;
    final StringVector values = q.getValues();
    
    if(keyWord != null && !keyWord.trim().equals(""))
    {
      final String keyWordLC = keyWord.toLowerCase();
      final StringVector tmp = values.copy();
      for(String val: tmp)
      {
        if(ignoreCase)
        {
          if(val.toLowerCase().indexOf(keyWordLC) == -1)
            values.remove(val);
        }
        else if(val.indexOf(keyWord) == -1)
          values.remove(val);
      }
    }
    return values;
  }
  
  /**
   * Search the qualifiers for the selected qualifier name and value.
   */
  private void searchQualifiers(final String keyWord, 
                                final JComboBox nameCombo, 
                                final boolean ignoreCase,
                                final JButton addButton,
                                final JCheckBox addToAllSelected,
                                final Dimension d)
  {
    final String qName = (String) nameCombo.getSelectedItem();
    final StringVector results = new StringVector();
    for(QualifierList obo: obos)
    {
      if(!obo.isAcive)
        continue;
      StringVector oboResult = searchOboQualifiers(obo, qName, keyWord, ignoreCase);
      if(oboResult != null)
        results.add(oboResult);
    }
    
    final JExtendedComboBox valuesList = new JExtendedComboBox(results, true);
    valuesList.getEditor().getEditorComponent().addMouseListener(
        new ComboMouseListener(valuesList));
    
    valuesList.setPreferredSize(d);
    valuesList.setMaximumSize(d);
    
    if(valuesList.getSelectedItem() != null &&
       valuesList.getSelectedItem().equals("") &&
       valuesList.getItemCount() > 2)
      valuesList.setSelectedIndex(1);
    
    qualifierBox.removeAll();
    qualifierBox.add(valuesList);
    
    if(addListener != null)
      addButton.removeActionListener(addListener);
    addListener = new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        String qval = (String) valuesList.getSelectedItem();
        if(qName.equals("biological_process"))
          qval = "/GO=aspect=P;"+qval+";date="+DatePanel.getDate();
        else if(qName.equals("molecular_function"))
          qval = "/GO=aspect=F;"+qval+";date="+DatePanel.getDate();
        else if(qName.equals("cellular_component"))
          qval = "/GO=aspect=C;"+qval+";date="+DatePanel.getDate();
        else
          qval = "/"+qName+"="+qval;
        
        if(addToAllSelected.isSelected())
        {
          final FeatureVector features = selection.getAllFeatures();
          if(!Options.isBlackBeltMode() && features.size() > 1)
          {
            int status = JOptionPane.showConfirmDialog(UserDefinedQualifiers.this, 
              "Add "+qName+" to "+features.size()+" selected features?",
              "Add Qualifier To Selected Features", 
              JOptionPane.OK_CANCEL_OPTION);
            if(status == JOptionPane.CANCEL_OPTION)
              return;
          }
          final int idx = qval.indexOf("=");
          final Qualifier q = new Qualifier(qval.substring(1, idx), qval.substring( idx+1 ));
          
          try
          {
            for(int i=0; i<features.size(); i++)
              features.elementAt(i).addQualifierValues(q);
          }
          catch (ReadOnlyException e)
          {
            e.printStackTrace();
          }
          catch (EntryInformationException e)
          {
            e.printStackTrace();
          }
        }
        else
          qualifier_text_area.append(qval+"\n");
      }
    }; 
    addButton.addActionListener(addListener);

    mainPanel.revalidate();
  }
  
  /**
   * Return a list of the qualifier names.
   * @return
   */
  private Vector<String> getQualiferNames()
  {
    final Vector<String> names = new Vector<String>();
    for(QualifierList obo: obos)
    {
      if(!obo.isAcive)
        continue;
      QualifierVector qualifiers = obo.qualifiers;
      for(Qualifier q: qualifiers)
        if(!names.contains(q.getName()))
           names.add(q.getName());
    }
    return names;
  }
  
  /**
   * Read qualifier lists
   */
  private void read()
  {
    URL url = ClassLoader.getSystemResource("etc/artemis.qualifiers");
    if(url != null)
    {
      Document doc = new URLDocument(
        ClassLoader.getSystemResource("etc/artemis.qualifiers"));
      createObo(doc);
    }
    
    final String [] qualFiles = 
    {
      "artemis.qualifiers",
      System.getProperty("user.home") + File.separator + ".artemis.qualifiers"
    };
    
    for(String fileName: qualFiles)
    {
      Document doc = new FileDocument(new File(fileName));
      if(doc.readable()) 
        createObo(doc);
    }
  }
  
  /**
   * Load the qualifiers from an input stream.
   * @param ins
   * @throws IOException 
   */
  private QualifierVector loadQualifiers(final Document doc) throws IOException
  {
    QualifierVector qualifiers = loadObo(doc);
    if(qualifiers.size() == 0)
      qualifiers = loadText(doc.getInputStream());
    return qualifiers;
  }
  
  /**
   * Load in qualifiers from an input stream that are in OBO format.
   * @param ins
   * @return
   * @throws IOException 
   */
  private QualifierVector loadObo(final Document doc) throws IOException
  {
    final LinePushBackReader reader = new LinePushBackReader(
        new BufferedReader(new InputStreamReader(doc.getInputStream())));
    final QualifierVector qualifiers = new QualifierVector();
    try
    {
      String line;
      while((line = reader.readLine()) != null) 
      {
        if(line.startsWith("#"))
          continue;

        if(startOfStanza(line))
        {
          String val = null;
          String name = null;
          String id = null;
          while((line = reader.readLine()) != null &&
                !startOfStanza(line) &&
                 line.indexOf("import:") == -1)
          {
            if(line.startsWith("namespace:"))
              name = line.substring(10).trim();
            else if(line.startsWith("name:"))
              val = line.substring(5).trim();
            else if(line.startsWith("id:"))
              id = line.substring(3).trim();
          }
          
          if(line != null)
            reader.pushBack(line);
          
          if(id != null)
          {
            if(  name != null && 
                (name.equals("biological_process") || 
                 name.equals("molecular_function") || 
                 name.equals("cellular_component")))
              val = "term="+val+"; GOid="+id;
            else
              val = "term="+val+"; id="+id;
          }
          if(name != null)
            qualifiers.addQualifierValues(new Qualifier(name, val));
        }
        else if(line.startsWith("import:"))
        {
          // import OBO file from a URL
          final URLDocument urlDoc = new URLDocument(
              new URL(line.substring(7).trim()) );
          // check to ensure not recursively reading from same file
          if(!doc.getLocation().equals(urlDoc.getLocation()))
            createObo(urlDoc);
        }
      }
      reader.close();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    return qualifiers;
  }
  
  /**
   * Each unit of the OBO file starts with one of the three supported stanza 
   * types: [Term], [Typedef], and [Instance]. Parsers/serializers will round-trip 
   * (successfully load and save) unrecognized stanzas. 
   * @param line
   * @return
   */
  private boolean startOfStanza(final String line)
  {
    return line.startsWith("[");
  }
  
  /**
   * Load in qualifiers from an input stream that are in the format:
   * name=value
   * @param ins
   * @return
   */
  private QualifierVector loadText(final InputStream ins)
  {
    final BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
    final QualifierVector qualifiers = new QualifierVector();
    try
    {
      String line;
      while((line = reader.readLine()) != null) 
      {
        Matcher m = TAG_VALUE_PATTERN.matcher(line);
        if(m.matches())
          qualifiers.addQualifierValues(new Qualifier(m.group(1), m.group(2)));
      }
      reader.close();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    return qualifiers;
  }
  
  /**
   * Import OBO
   */
  private void importFromFile()
  {
    final StickyFileChooser fc = new StickyFileChooser();
    //final JCheckBox autoImport = new JCheckBox("Automatically import", true);
    //fc.setAccessory(autoImport);
    
    OboFileFilter oboFilter = new OboFileFilter(new String[]{"obo", "OBO"}, "OBO files");
    fc.addChoosableFileFilter(oboFilter);
    fc.setFileFilter(oboFilter);
    int status = fc.showOpenDialog(UserDefinedQualifiers.this);
    if(status != StickyFileChooser.APPROVE_OPTION)
      return;
    
    final FileDocument doc = new FileDocument(fc.getSelectedFile());
    if(!doc.readable())
    {
      JOptionPane.showMessageDialog(UserDefinedQualifiers.this, 
          "Cannot read file "+doc.getName(), 
          "Problem reading file", JOptionPane.WARNING_MESSAGE);
      return;
    }
    
    createObo(doc);
    
    createComponentToAddCvTerm();
    mainPanel.revalidate();
    mainPanel.repaint();
    //if(autoImport.isSelected())
    writeQualifierList(fc.getSelectedFile().getAbsolutePath());
  }
  
  private void importFromUrl()
  {
    String s = (String)JOptionPane.showInputDialog(
        UserDefinedQualifiers.this,
        "URL:", "Import Qualifiers",
        JOptionPane.PLAIN_MESSAGE,
        null, null,
        "http://www.geneontology.org/GO_slims/goslim_generic.obo");
    if(s == null || s.trim().equals("") || s.trim().equals("http://"))
      return;

    try
    {
      String urlStr = s.trim();
      createObo(new URLDocument(new URL(urlStr)));
      createComponentToAddCvTerm();
      mainPanel.revalidate();
      mainPanel.repaint();
      
      writeQualifierList(urlStr);
    }
    catch (MalformedURLException e)
    {
      e.printStackTrace();
    }
  }
  
  private void createObo(Document doc)
  {
    try
    {
      logger4j.debug("Reading qualifiers from: "+doc.getName());
      final QualifierList obo = new QualifierList();
      obo.doc = doc;
      obo.qualifiers = loadQualifiers(doc);
      obos.add(obo);
    }
    catch (IOException e)
    {
      logger4j.error(e.getMessage());
      JOptionPane.showMessageDialog(UserDefinedQualifiers.this, 
          "Problem Loading:\n"+doc.getName()+"\n"+
          e.getMessage(), "Problem Loading", 
          JOptionPane.ERROR_MESSAGE);
    }
  }


  /**
   * Write or append to artemis qualifiers list
   * @param loc
   * @param isOBO
   */
  private static void writeQualifierList(final String loc)
  {
    int status = JOptionPane.showConfirmDialog(
        null, 
        "Automatically import:\n"+loc+"\nbetween session?", 
        "Import Option", JOptionPane.YES_NO_OPTION);
    if(status != JOptionPane.YES_OPTION)
      return;
    
    final boolean isOBO;
    if(loc.toLowerCase().endsWith(".obo"))
      isOBO = true;
    else
    {
      File f = new File(loc);
      if(f.exists())
        isOBO = true;
      else
        isOBO = false;
    }
    
    final String qualPath = System.getProperty("user.home") + File.separator + ".artemis.qualifiers";
    try
    {
      final FileDocument doc = new FileDocument(new File(qualPath));
      if(doc.readable())
      {
        final BufferedReader reader = 
            new BufferedReader(new InputStreamReader(doc.getInputStream()));
        try
        {
          String line;
          while((line = reader.readLine()) != null)
          {
            if(line.startsWith("import: file://"+loc))
            {
              System.out.println("Import line already exists :\n"+line);
              return;
            }
            else if(line.startsWith("import: "+loc))
            {
              System.out.println("Import line already exists :\n"+line);
              return;
            }
          }
        }
        finally
        {
          reader.close();
        }
      }

      final File qualFile = new File(qualPath);
      final boolean exists = qualFile.exists();
      final BufferedWriter bufferedwriter = new BufferedWriter(
          new FileWriter(qualFile, true));
      
      if(!exists)
      {
        bufferedwriter.append("# User defined list of qualifiers in the format of:\n");
        bufferedwriter.append("# name=value\n");
        bufferedwriter.append("# The qualifiers are separated by lines.\n");
        bufferedwriter.append("# OBO files can be used by defining import statements:\n");
        bufferedwriter.append("#import: http://www.geneontology.org/GO_slims/goslim_generic.obo\n\n");
      }
      
      if(isOBO)
      {
        if(loc.startsWith("http://"))
          bufferedwriter.append("\nimport: "+loc+"\n");
        else
          bufferedwriter.append("\nimport: file://"+loc+"\n");
      }
      else
        bufferedwriter.append(loc+"\n");

      bufferedwriter.close();
    }
    catch (FileNotFoundException filenotfoundexception)
    {
      System.err.println(qualPath+" read error");
    }
    catch (IOException e)
    {
      System.err.println(qualPath+" i/o error");
    }
  }
  
  private class OboFileFilter extends FileFilter
  {
    private String[] suffix;
    private String descr;
    OboFileFilter(final String[] suffix, final String descr)
    {
      this.suffix = suffix;
      this.descr = descr;
    }
    
    public boolean accept(final File file)
    {
      if(file.isDirectory())
        return true;

      for(String s: suffix)
      {
        if(file.getName().endsWith("." + s) )
          return true;
      }
      return false;
    }

    public String getDescription()
    {
      return descr;
    }
  }
  
  protected void setQualifierTextArea(final QualifierTextArea qualifier_text_area)
  {
    this.qualifier_text_area = qualifier_text_area;
  }
  
  protected void setSelection(final Selection selection)
  {
    this.selection = selection;
  }
  
  private class ComboMouseListener extends MouseAdapter 
  {
    private JComboBox cb;
    ComboMouseListener(JComboBox cb)
    {
      this.cb = cb;
    }
    
    public void mouseEntered(MouseEvent me) 
    {
      if(cb.getSelectedItem() != null)
        cb.setToolTipText(getWrappedStr((String) cb.getSelectedItem()));
    }
    
    private String getWrappedStr(String s)
    {
      final StringBuilder buff = new StringBuilder();
      final int lineLen = 60;
      for(int k=0; k<s.length(); k+=lineLen)
      {
        int end = k + lineLen;
        if(end > s.length())
          end = s.length();
        buff.append ( s.substring(k,end) ).append("\n");
      }
      return buff.toString();
    }
  }


  private class QualifierList
  {
    Document doc;
    QualifierVector qualifiers;
    boolean isAcive = true;
  }
  
  public static void main(String args[])
  {
    final javax.swing.plaf.FontUIResource font_ui_resource =
        Options.getOptions().getFontUIResource();

    final Enumeration<Object> keys = UIManager.getDefaults().keys();
    while(keys.hasMoreElements()) 
    {
      Object key = keys.nextElement();
      Object value = UIManager.get(key);
      if(value instanceof javax.swing.plaf.FontUIResource) 
        UIManager.put(key, font_ui_resource);
    }

    final JFrame f = new UserDefinedQualifiers();
    f.pack();
    f.setVisible(true);
  }
}