Package org.fife.ui.rsyntaxtextarea

Source Code of org.fife.ui.rsyntaxtextarea.CodeTemplateManager$XMLFileFilter

/*
* 02/21/2005
*
* CodeTemplateManager.java - manages code templates.
* Copyright (C) 2005 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
*/
package org.fife.ui.rsyntaxtextarea;

import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import javax.swing.KeyStroke;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Segment;

import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate;


/**
* Manages "code templates."<p>
*
* All methods in this class are synchronized for thread safety, but as a
* best practice, you should probably only modify the templates known to a
* <code>CodeTemplateManager</code> on the EDT.  Modifying a
* <code>CodeTemplate</code> retrieved from a <code>CodeTemplateManager</code>
* while <em>not</em> on the EDT could cause problems.
*
* @author Robert Futrell
* @version 0.1
*/
public class CodeTemplateManager {

  private int maxTemplateIDLength;
  private List templates;

  private KeyStroke insertTrigger;
  private String insertTriggerString;
  private Segment s;
  private TemplateComparator comparator;
  private File directory;

  private static final int mask = InputEvent.CTRL_MASK|InputEvent.SHIFT_MASK;
  static final KeyStroke TEMPLATE_KEYSTROKE = KeyStroke.
                getKeyStroke(KeyEvent.VK_SPACE, mask);


  /**
   * Constructor.
   */
  public CodeTemplateManager() {

    // Default insert trigger is a space.
    // FIXME:  See notes in RSyntaxTextAreaDefaultInputMap.
    setInsertTrigger(TEMPLATE_KEYSTROKE);

    s = new Segment();
    comparator = new TemplateComparator();
    templates = new ArrayList();

  }


  /**
   * Registers the specified template with this template manager.
   *
   * @param template The template to register.
   * @throws IllegalArgumentException If <code>template</code> is
   *         <code>null</code>.
   * @see #removeTemplate(CodeTemplate)
   * @see #removeTemplate(String)
   */
  public synchronized void addTemplate(CodeTemplate template) {
    if (template==null) {
      throw new IllegalArgumentException("template cannot be null");
    }
    templates.add(template);
    sortTemplates();
  }


  /**
   * Returns the keystroke that is the "insert trigger" for templates;
   * that is, the character that, when inserted into an instance of
   * <code>RSyntaxTextArea</code>, triggers the search for
   * a template matching the token ending at the caret position.
   *
   * @return The insert trigger.
   * @see #getInsertTriggerString()
   * @see #setInsertTrigger(KeyStroke)
   */
  /*
   * FIXME:  This text IS what's inserted if the trigger character is pressed
   * in a text area but no template matches, but it is NOT the trigger
   * character used in the text areas.  This is because space (" ") is
   * hard-coded into RSyntaxTextAreaDefaultInputMap.java.  We need to make
   * this dynamic somehow.  See RSyntaxTextAreaDefaultInputMap.java.
   */
  public KeyStroke getInsertTrigger() {
    return insertTrigger;
  }


  /**
   * Returns the "insert trigger" for templates; that is, the character
   * that, when inserted into an instance of <code>RSyntaxTextArea</code>,
   * triggers the search for a template matching the token ending at the
   * caret position.
   *
   * @return The insert trigger character.
   * @see #getInsertTrigger()
   * @see #setInsertTrigger(KeyStroke)
   */
  /*
   * FIXME:  This text IS what's inserted if the trigger character is pressed
   * in a text area but no template matches, but it is NOT the trigger
   * character used in the text areas.  This is because space (" ") is
   * hard-coded into RSyntaxTextAreaDefaultInputMap.java.  We need to make
   * this dynamic somehow.  See RSyntaxTextAreaDefaultInputMap.java.
   */
  public String getInsertTriggerString() {
    return insertTriggerString;
  }


  /**
   * Returns the template that should be inserted at the current caret
   * position, assuming the trigger character was pressed.
   *
   * @param textArea The text area that's getting text inserted into it.
   * @return A template that should be inserted, if appropriate, or
   *         <code>null</code> if no template should be inserted.
   */
  public synchronized CodeTemplate getTemplate(RSyntaxTextArea textArea) {
    int caretPos = textArea.getCaretPosition();
    int charsToGet = Math.min(caretPos, maxTemplateIDLength);
    try {
      Document doc = textArea.getDocument();
      doc.getText(caretPos-charsToGet, charsToGet, s);
      int index = Collections.binarySearch(templates, s, comparator);
      return index>=0 ? (CodeTemplate)templates.get(index) : null;
    } catch (BadLocationException ble) {
      ble.printStackTrace();
      throw new InternalError("Error in CodeTemplateManager");
    }
  }


  /**
   * Returns the number of templates this manager knows about.
   *
   * @return The template count.
   */
  public synchronized int getTemplateCount() {
    return templates.size();
  }


  /**
   * Returns the templates currently available.
   *
   * @return The templates available.
   */
  public synchronized CodeTemplate[] getTemplates() {
    CodeTemplate[] temp = new CodeTemplate[templates.size()];
    return (CodeTemplate[])templates.toArray(temp);
  }


  /**
   * Returns whether the specified character is a valid character for a
   * <code>CodeTemplate</code> id.
   *
   * @param ch The character to check.
   * @return Whether the character is a valid template character.
   */
  public static final boolean isValidChar(char ch) {
    return RSyntaxUtilities.isLetterOrDigit(ch) || ch=='_';
  }


  /**
   * Returns the specified code template.
   *
   * @param template The template to remove.
   * @return <code>true</code> if the template was removed, <code>false</code>
   *         if the template was not in this template manager.
   * @throws IllegalArgumentException If <code>template</code> is
   *         <code>null</code>.
   * @see #removeTemplate(String)
   * @see #addTemplate(CodeTemplate)
   */
  public synchronized boolean removeTemplate(CodeTemplate template) {

    if (template==null) {
      throw new IllegalArgumentException("template cannot be null");
    }

    // TODO: Do a binary search
    return templates.remove(template);

  }


  /**
   * Returns the code template with the specified id.
   *
   * @param id The id to check for.
   * @return The code template that was removed, or <code>null</code> if
   *         there was no template with the specified ID.
   * @throws IllegalArgumentException If <code>id</code> is <code>null</code>.
   * @see #removeTemplate(CodeTemplate)
   * @see #addTemplate(CodeTemplate)
   */
  public synchronized CodeTemplate removeTemplate(String id) {

    if (id==null) {
      throw new IllegalArgumentException("id cannot be null");
    }

    // TODO: Do a binary search
    for (Iterator i=templates.iterator(); i.hasNext(); ) {
      CodeTemplate template = (CodeTemplate)i.next();
      if (id.equals(template.getID())) {
        i.remove();
        return template;
      }
    }

    return null;

  }


  /**
   * Replaces the current set of available templates with the ones
   * specified.
   *
   * @param newTemplates The new set of templates.  Note that we will
   *        be taking a shallow copy of these and sorting them.
   */
  public synchronized void replaceTemplates(CodeTemplate[] newTemplates) {
    templates.clear();
    if (newTemplates!=null) {
      for (int i=0; i<newTemplates.length; i++) {
        templates.add(newTemplates[i]);
      }
    }
    sortTemplates(); // Also recomputes maxTemplateIDLength.
  }


  /**
   * Saves all templates as XML files in the current template directory.
   *
   * @return Whether or not the save was successful.
   */
  public synchronized boolean saveTemplates() {

    if (templates==null)
      return true;
    if (directory==null || !directory.isDirectory())
      return false;

    // Blow away all old XML files to start anew, as some might be from
    // templates we're removed from the template manager.
    File[] oldXMLFiles = directory.listFiles(new XMLFileFilter());
    if (oldXMLFiles==null)
      return false; // Either an IOException or it isn't a directory.
    int count = oldXMLFiles.length;
    for (int i=0; i<count; i++) {
      /*boolean deleted = */oldXMLFiles[i].delete();
    }

    // Save all current templates as XML.
    boolean wasSuccessful = true;
    for (Iterator i=templates.iterator(); i.hasNext(); ) {
      CodeTemplate template = (CodeTemplate)i.next();
      File xmlFile = new File(directory, template.getID() + ".xml");
      try {
        XMLEncoder e = new XMLEncoder(new BufferedOutputStream(
                    new FileOutputStream(xmlFile)));
        e.writeObject(template);
        e.close();
      } catch (IOException ioe) {
        ioe.printStackTrace();
        wasSuccessful = false;
      }
    }

    return wasSuccessful;

  }


  /**
   * Sets the "trigger" character for templates.
   *
   * @param trigger The trigger character to set for templates.  This means
   *        that when this character is pressed in an
   *        <code>RSyntaxTextArea</code>,  the last-typed token is found,
   *        and is checked against all template ID's to see if a template
   *        should be inserted.  If a template ID matches, that template is
   *        inserted; if not, the trigger character is inserted.  If this
   *        parameter is <code>null</code>, no change is made to the trigger
   *        character.
   * @see #getInsertTrigger()
   * @see #getInsertTriggerString()
   */
  /*
   * FIXME:  The trigger set here IS inserted when no matching template
   * is found, but a space character (" ") is always used as the "trigger"
   * to look for templates.  This is because it is hard-coded in
   * RSyntaxTextArea's input map this way.  We need to change this.
   * See RSyntaxTextAreaDefaultInputMap.java.
   */
  public void setInsertTrigger(KeyStroke trigger) {
    if (trigger!=null) {
      insertTrigger = trigger;
      insertTriggerString = Character.toString(trigger.getKeyChar());
    }
  }


  /**
   * Sets the directory in which to look for templates.  Calling this
   * method adds any new templates found in the specified directory to
   * the templates already registered.
   *
   * @param dir The new directory in which to look for templates.
   * @return The new number of templates in this template manager, or
   *         <code>-1</code> if the specified directory does not exist.
   */
  public synchronized int setTemplateDirectory(File dir) {

    if (dir!=null && dir.isDirectory()) {

      this.directory = dir;

      File[] files = dir.listFiles(new XMLFileFilter());
      int newCount = files==null ? 0 : files.length;
      int oldCount = templates.size();

      List temp = new ArrayList(oldCount+newCount);
      temp.addAll(templates);

      for (int i=0; i<newCount; i++) {
        try {
          XMLDecoder d = new XMLDecoder(new BufferedInputStream(
            new FileInputStream(files[i])));
          Object obj = d.readObject();
          if (!(obj instanceof CodeTemplate)) {
            throw new IOException("Not a CodeTemplate: " +
                    files[i].getAbsolutePath());
          }
          temp.add(obj);
          d.close();
        } catch (/*IO, NoSuchElement*/Exception e) {
          // NoSuchElementException can be thrown when reading
          // an XML file not in the format expected by XMLDecoder.
          // (e.g. CodeTemplates in an old format).
          e.printStackTrace();
        }
      }
      templates = temp;
      sortTemplates();

      return getTemplateCount();

    }

    return -1;

  }


  /**
   * Removes any null entries in the current set of templates (if
   * any), sorts the remaining templates, and computes the new
   * maximum template ID length.
   */
  private synchronized void sortTemplates() {

    // Get the maximum length of a template ID.
    maxTemplateIDLength = 0;

    // Remove any null entries (should only happen because of
    // IOExceptions, etc. when loading from files), and sort
    // the remaining list.
    for (Iterator i=templates.iterator(); i.hasNext(); ) {
      CodeTemplate temp = (CodeTemplate)i.next();
      if (temp==null || temp.getID()==null) {
        i.remove();
      }
      else {
        maxTemplateIDLength = Math.max(maxTemplateIDLength,
                    temp.getID().length());
      }
    }

    Collections.sort(templates);

  }


  /**
   * A comparator that takes a <code>CodeTemplate</code> as its first
   * parameter and a <code>Segment</code> as its second, and knows
   * to compare the template's ID to the segment's text.
   */
  private static class TemplateComparator implements Comparator, Serializable{

    public int compare(Object template, Object segment) {

      // Get template start index (0) and length.
      CodeTemplate t = (CodeTemplate)template;
      final char[] templateArray = t.getID().toCharArray();
      int i = 0;
      int len1 = templateArray.length;

      // Find "token" part of segment and get its offset and length.
      Segment s = (Segment)segment;
      char[] segArray = s.array;
      int len2 = s.count;
      int j = s.offset + len2 - 1;
      while (j>=s.offset && isValidChar(segArray[j])) {
        j--;
      }
      j++;
      int segShift = j - s.offset;
      len2 -= segShift;

      int n = Math.min(len1, len2);
      while (n-- != 0) {
        char c1 = templateArray[i++];
        char c2 = segArray[j++];
        if (c1 != c2)
          return c1 - c2;
      }
      return len1 - len2;

    }

  }


  /**
   * A file filter for File.listFiles() (NOT for JFileChoosers!) that
   * accepts only XML files.
   */
  private static class XMLFileFilter implements FileFilter {
    public boolean accept(File f) {
      return f.getName().toLowerCase().endsWith(".xml");
    }
  }


}
TOP

Related Classes of org.fife.ui.rsyntaxtextarea.CodeTemplateManager$XMLFileFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.