Package org.fife.ui.rsyntaxtextarea

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

/*
* 02/21/2005
*
* CodeTemplateManager.java - manages code templates.
*
* This library is distributed under a modified BSD license.  See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rsyntaxtextarea;

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.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.<p>
*
* For more flexible boilerplate code insertion, consider using the
* <a href="http://javadoc.fifesoft.com/autocomplete/org/fife/ui/autocomplete/TemplateCompletion.html">TemplateCompletion
* class</a> in the
* <a href="https://github.com/bobbylight/AutoComplete">AutoComplete
* add-on library</a>.
*
* @author Robert Futrell
* @version 1.0
*/
public class CodeTemplateManager {

  private int maxTemplateIDLength;
  private List<CodeTemplate> templates;

  private Segment s;
  private TemplateComparator comparator;
  private File directory;


  /**
   * Constructor.
   */
  public CodeTemplateManager() {
    s = new Segment();
    comparator = new TemplateComparator();
    templates = new ArrayList<CodeTemplate>();
  }


  /**
   * 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 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);
      @SuppressWarnings("unchecked")
      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 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<CodeTemplate> i=templates.iterator(); i.hasNext(); ) {
      CodeTemplate template = 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 (CodeTemplate template : templates) {
      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 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<CodeTemplate> temp =
          new ArrayList<CodeTemplate>(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((CodeTemplate)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<CodeTemplate> i=templates.iterator(); i.hasNext(); ) {
      CodeTemplate temp = 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.
   */
  @SuppressWarnings("rawtypes")
  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 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

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.