/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.text;

import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Element;
import javax.swing.text.GapContent;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.PlainDocument;
import javax.swing.text.Segment;

import jnpad.config.Config;
import jnpad.text.syntax.ContentTypes;
import jnpad.util.UnexpectedException;
import jnpad.util.Utilities;

/**
 * The Class JNPadDocument.
 * 
 * @version 0.3
 * @since jNPad 0.1
 */
public class JNPadDocument extends PlainDocument {
  /** The Constant useTabsAttribute. */
  public static final String  useTabsAttribute    = "JNPadDocument.useTabs";                        //$NON-NLS-1$

  /** The Constant autoIndentAttribute. */
  public static final String  autoIndentAttribute = "JNPadDocument.autoIndent";                     //$NON-NLS-1$

  /** The Constant delimitersAttribute. */
  public static final String  delimitersAttribute = "JNPadDocument.delimitersTabs";                 //$NON-NLS-1$

  /** The scheme. */
  private Scheme              _scheme;
  private Scheme              _mscheme;
  private DocumentListener    _handler;

  /** Logger */
  private static final Logger LOGGER              = Logger.getLogger(JNPadDocument.class.getName());

  /** UID */
  private static final long   serialVersionUID    = 8537681881189609777L;

  /**
   * Instantiates a new jNPad document.
   */
  public JNPadDocument() {
    super(new JNPadGapContent());

    //Set the end of line to be a new line.
    putProperty(DefaultEditorKit.EndOfLineStringProperty, Utilities.LF_STRING);

    putProperty(tabSizeAttribute   , Config.TEXT_TAB_SIZE.getValue());
    putProperty(autoIndentAttribute, Config.TEXT_AUTO_INDENT.getValue());
    putProperty(delimitersAttribute, Config.TEXT_DELIMITERS.getValue());
  }

  /**
   * Gets the scheme.
   *
   * @return the scheme
   */
  public Scheme getScheme() {
    return _scheme;
  }

  /**
   * Gets the mini scheme.
   *
   * @return the mini scheme
   */
  public Scheme getMiniScheme() {
    return _mscheme;
  }
  
  /**
   * Gets the content type.
   *
   * @return the content type
   */
  public String getContentType() {
    return _scheme != null ? _scheme.getContentType() :  ContentTypes.PLAIN;
  }

  /**
   * Sets the content type.
   *
   * @param type the new content type
   */
  public void setContentType(String type) {
    _scheme  = Scheme.getScheme(Utilities.defaultString(type, ContentTypes.PLAIN), false);
    _mscheme = Scheme.getScheme(Utilities.defaultString(type, ContentTypes.PLAIN), true);
    
    if (Scheme.hasCLikeSyntax(type)) {
      if (_handler == null)
        _handler = new DocumentHandler();
      addDocumentListener(_handler);
    }
    else if (_handler != null) {
      removeDocumentListener(_handler);
    }
  }
  
  /**
   * Clears the document..
   * 
   * @throws UnexpectedException the unexpected exception
   */
  public void clear() throws UnexpectedException {
    try {
      remove(0, getLength());
    }
    catch (BadLocationException ex) {
      throw new UnexpectedException(ex);
    }
  }

  /**
   * Gets the text.
   * 
   * @return the text
   * @throws UnexpectedException the unexpected exception
   */
  public String getText() throws UnexpectedException {
    try {
      return getText(0, getLength());
    }
    catch (Exception ex) {
      throw new UnexpectedException(ex);
    }
  }

  /**
   * Gets the line text.
   * 
   * @param line the line
   * @return the line text
   * @throws BadLocationException the bad location exception
   */
  public String getLineText(int line) throws BadLocationException {
    checkLine(line);
    return TextUtilities.getTextOfLine(this, line);
  }

  /**
   * Gets the line text of offset.
   * 
   * @param offset the offset
   * @return the line text of offset
   * @throws BadLocationException the bad location exception
   */
  public String getLineTextOfOffset(int offset) throws BadLocationException {
    checkOffset(offset);
    return TextUtilities.getTextOfLineAtPosition(this, offset);
  }

  /**
   * Gets the line text of offset only up to pos.
   * 
   * @param offset the offset
   * @return the line text of offset only up to pos
   * @throws BadLocationException the bad location exception
   */
  public String getLineTextOfOffsetOnlyUpToPos(int offset) throws BadLocationException {
    checkOffset(offset);
    return TextUtilities.getTextOfLineAtPosition_onlyUpToPos(this, offset);
  }

  /**
   * Gets the line number.
   * 
   * @param offset the offset
   * @return the line number
   * @throws BadLocationException the bad location exception
   */
  public int getLineNumber(int offset) throws BadLocationException {
    checkOffset(offset);
    return TextUtilities.getLineNumber(this, offset);
  }

  /**
   * Gets the line.
   * 
   * @param line the line
   * @return the line
   * @throws BadLocationException the bad location exception
   */
  public Element getLine(int line) throws BadLocationException {
    checkLine(line);
    return TextUtilities.getLine(this, line);
  }

  /**
   * Gets the line count.
   * 
   * @return the line count
   */
  public int getLineCount() {
    Element map = getDefaultRootElement();
    return map.getElementCount();
  }

  /**
   * Check offset.
   * 
   * @param offset int
   * @throws BadLocationException the bad location exception
   */
  private void checkOffset(int offset) throws BadLocationException {
    if (offset < 0) {
      throw new BadLocationException("Can't translate offset to line", -1); //$NON-NLS-1$
    }
    else if (offset > getLength()) {
      throw new BadLocationException("Can't translate offset to line", getLength() + 1); //$NON-NLS-1$
    }
  }

  /**
   * Check line.
   * 
   * @param line int
   * @throws BadLocationException the bad location exception
   */
  private void checkLine(int line) throws BadLocationException {
    int lineCount = getLineCount();
    if (line < 0) {
      throw new BadLocationException("Negative line", -1); //$NON-NLS-1$
    }
    else if (line >= lineCount) {
      throw new BadLocationException("No such line", getLength() + 1); //$NON-NLS-1$
    }
  }

  /**
   * Returns the character in the document at the specified offset.
   * 
   * @param offset The offset of the character.
   * @return The character.
   * @throws BadLocationException If the offset is invalid.
   */
  public char charAt(int offset) throws BadLocationException {
    return ((JNPadGapContent) getContent()).charAt(offset);
  }

  /////////////////////////////////////////////////////////////////////////////
  /**
   * The Class JNPadGapContent.
   */
  private static class JNPadGapContent extends GapContent {
    /** UID */
    private static final long serialVersionUID = -1699909757565203087L;

    /**
     * Char at.
     * 
     * @param offset the offset
     * @return the char
     * @throws BadLocationException the bad location exception
     */
    public char charAt(int offset) throws BadLocationException {
      if (offset < 0 || offset >= length()) {
        throw new BadLocationException("Invalid offset", offset); //$NON-NLS-1$
      }
      int g0 = getGapStart();
      char[] array = (char[]) getArray();
      if (offset < g0) { // below gap
        return array[offset];
      }
      return array[getGapEnd() + offset - g0]; // above gap
    }
  }
  /////////////////////////////////////////////////////////////////////////////

  /** The Constant START_COMMENT. */
  public static final Object START_COMMENT     = new AttributeKey("CommentStart");    //$NON-NLS-1$

  /** The Constant END_COMMENT. */
  public static final Object END_COMMENT       = new AttributeKey("CommentEnd");      //$NON-NLS-1$

  /** The Constant MULTILINE_COMMENT. */
  public static final Object MULTILINE_COMMENT = new AttributeKey("MultilineComment"); //$NON-NLS-1$

  /** The Constant JAVADOC. */
  public static final Object JAVADOC           = new AttributeKey("Javadoc");         //$NON-NLS-1$

  /////////////////////////////////////////////////////////////////////////////
  /**
   * The Class AttributeKey.
   */
  static class AttributeKey {
    private String attributeName;

    /**
     * Instantiates a new attribute key.
     *
     * @param attributeName the attribute name
     */
    AttributeKey(String attributeName) {
      this.attributeName = attributeName;
    }

    /**
     * To string.
     *
     * @return the string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
      return attributeName;
    }
  }  
  /////////////////////////////////////////////////////////////////////////////
  
  /**
   * Insert update.
   *
   * @param ev the DefaultDocumentEvent
   * @param att the AttributeSet
   * @see javax.swing.text.PlainDocument#insertUpdate(javax.swing.text.AbstractDocument.DefaultDocumentEvent, javax.swing.text.AttributeSet)
   */
  @Override
  protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet att) {
    super.insertUpdate(ev, att);
    //_insertUpdate(ev);
  }

  /**
   * Post remove update.
   *
   * @param ev the DefaultDocumentEvent
   * @see javax.swing.text.AbstractDocument#postRemoveUpdate(javax.swing.text.AbstractDocument.DefaultDocumentEvent)
   */
  @Override
  protected void postRemoveUpdate(DefaultDocumentEvent ev) {
    super.postRemoveUpdate(ev);
    //_postRemoveUpdate(ev);
  }
  
  /////////////////////////////////////////////////////////////////////////////
  /**
   * The Class DocumentHandler.
   */
  class DocumentHandler implements DocumentListener, Serializable {
    /** UID */
    private static final long serialVersionUID = 6446285225479775497L; // Keep FindBugs happy

    /**
     * Changed update.
     *
     * @param e the DocumentEvent
     * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
     */
    @Override
    public void changedUpdate(final DocumentEvent e) {/* empty */}

    /**
     * Insert update.
     *
     * @param e the DocumentEvent
     * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
     */
    @Override
    public void insertUpdate(final DocumentEvent e) {
      _insertUpdate(e);
    }

    /**
     * Removes the update.
     *
     * @param e the DocumentEvent
     * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
     */
    @Override
    public void removeUpdate(final DocumentEvent e) {
      _postRemoveUpdate(e);
    }
  }
  /////////////////////////////////////////////////////////////////////////////

  /**
   * _insert update.
   *
   * @param ev the DocumentEvent
   */
  private void _insertUpdate(DocumentEvent ev) {
    DocumentEvent.ElementChange change = ev.getChange(getDefaultRootElement());
    int startOffset;
    int length;
    if (change == null) {
      startOffset = ev.getOffset();
      length = ev.getLength();
    }
    else {
      Element[] elements = change.getChildrenAdded();
      startOffset = elements[0].getStartOffset();
      length = elements[elements.length - 1].getEndOffset() - elements[0].getStartOffset();
    }
    
    try {
      checkTextToComment(startOffset, length);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }
  
  /**
   * _post remove update.
   *
   * @param ev the DocumentEvent
   */
  private void _postRemoveUpdate(DocumentEvent ev) {
    try {
      checkTextToComment(ev.getOffset(), ev.getLength());
    }
    catch (Exception ex) {
      //LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }
  
  /**
   * Check text to comment.
   *
   * @param offset the offset
   * @param len the len
   * @throws BadLocationException the bad location exception
   */
  private void checkTextToComment(int offset, int len) throws BadLocationException {
    Element root = getDefaultRootElement();
    int firstParagraph = root.getElementIndex(offset);
    int lastParagraph = root.getElementIndex(offset + len);

    boolean inComment = getCommentStatus(firstParagraph);
    if (getContentType() == ContentTypes.JAVA) {
      boolean inJavadoc = getJavadocStatus(firstParagraph);
      for (int i = firstParagraph; i <= lastParagraph; i++) {
        boolean[] r = checkTextToCommentAndJavadoc(root.getElement(i), inComment, inJavadoc);
        inComment = r[0];
        inJavadoc = r[1];
      }
      if (inJavadoc) {
        javadocFollowingText(lastParagraph + 1);
      }
      else {
        unjavadocFollowingText(lastParagraph + 1);
      }
    }
    else {
      for (int i = firstParagraph; i <= lastParagraph; i++) {
        inComment = checkTextToComment(root.getElement(i), inComment);
      }
    }

    if (inComment) {
      commentFollowingText(lastParagraph + 1);
    }
    else {
      uncommentFollowingText(lastParagraph + 1);
    }
  }

  /**
   * Gets the comment status.
   *
   * @param paragraphNumber the paragraph number
   * @return the comment status
   */
  private boolean getCommentStatus(int paragraphNumber) {
    if (paragraphNumber > 0) {
      Element previousElement = getDefaultRootElement().getElement(paragraphNumber - 1);
      return containsMultilineCommentAttribute(previousElement);
    }
    return false;
  }

  /**
   * Gets the javadoc status.
   *
   * @param paragraphNumber the paragraph number
   * @return the javadoc status
   * @since 0.3
   */
  private boolean getJavadocStatus(int paragraphNumber) {
    if (paragraphNumber > 0) {
      Element previousElement = getDefaultRootElement().getElement(paragraphNumber - 1);
      return containsJavadocAttribute(previousElement);
    }
    return false;
  }
  
  /**
   * Comment following text.
   *
   * @param startParagraph the start paragraph
   */
  private void commentFollowingText(int startParagraph) {
    int lastParagraphToScan = getDefaultRootElement().getElementCount();
    for (int i = startParagraph; i < lastParagraphToScan; i++) {
      if (containsEndComment(getDefaultRootElement().getElement(i))) {
        Element e = getDefaultRootElement().getElement(i);
        fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
                    e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
        break;
      }
      addMultilineCommentAttribute(getDefaultRootElement().getElement(i));
    }
  }

  /**
   * Javadoc following text.
   *
   * @param startParagraph the start paragraph
   * @since 0.3
   */
  private void javadocFollowingText(int startParagraph) {
    int lastParagraphToScan = getDefaultRootElement().getElementCount();
    for (int i = startParagraph; i < lastParagraphToScan; i++) {
      if (containsEndComment(getDefaultRootElement().getElement(i))) {
        Element e = getDefaultRootElement().getElement(i);
        fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
                    e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
        break;
      }
      addJavadocAttribute(getDefaultRootElement().getElement(i));
    }
  }
  
  /**
   * Uncomment following text.
   *
   * @param startParagraph the start paragraph
   */
  private void uncommentFollowingText(int startParagraph) {
    int lastParagraphToScan = getDefaultRootElement().getElementCount();
    for (int i = startParagraph; i < lastParagraphToScan; i++) {
      if (containsMultilineCommentAttribute(getDefaultRootElement().getElement(i))
                && !containsStartComment(getDefaultRootElement().getElement(i)))
        removeMultilineCommentAttribute(getDefaultRootElement().getElement(i));
      else {
        Element e = getDefaultRootElement().getElement(i);
        fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
                    e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
        break;
      }
    }
  }

  /**
   * Unjavadoc following text.
   *
   * @param startParagraph the start paragraph
   * @since 0.3
   */
  private void unjavadocFollowingText(int startParagraph) {
    int lastParagraphToScan = getDefaultRootElement().getElementCount();
    for (int i = startParagraph; i < lastParagraphToScan; i++) {
      if (containsJavadocAttribute(getDefaultRootElement().getElement(i))
                && !containsStartComment(getDefaultRootElement().getElement(i)))
        removeJavadocAttribute(getDefaultRootElement().getElement(i));
      else {
        Element e = getDefaultRootElement().getElement(i);
        fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
                    e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
        break;
      }
    }
  }

  /**
   * Check text to comment.
   * 
   * @param paragraph the paragraph
   * @param inComment the in comment
   * @return true, if successful
   * @throws BadLocationException the bad location exception
   */
  private boolean checkTextToComment(Element paragraph, boolean inComment) throws BadLocationException {
    int startPosition = paragraph.getStartOffset();
    int length = (paragraph.getEndOffset() < getLength() ? paragraph.getEndOffset()
            : getLength()) - startPosition;
    Segment content = new Segment();
    getText(startPosition, length, content);

    boolean slashFound        = false;
    boolean starFound         = false;
    boolean commentStartFound = false;
    boolean commentEndFound   = false;
    boolean stringLiteral     = false;
    boolean charLiteral       = false;

    for (int wordIndex = 0; wordIndex < content.length(); wordIndex++) {
      char indexedChar = content.charAt(wordIndex);
      if (!inComment && slashFound && indexedChar == '/')
        break;
      else if (!inComment && indexedChar == '\"')
        stringLiteral = !stringLiteral;
      else if (!inComment && indexedChar == '\'')
        charLiteral = !charLiteral;
      else if (!starFound && !stringLiteral && !charLiteral && indexedChar == '/')
        slashFound = true;
      else if (!slashFound && !stringLiteral && !charLiteral && indexedChar == '*')
        starFound = true;
      else if (starFound && !stringLiteral && !charLiteral && indexedChar == '/') {
        inComment = false;
        commentEndFound = true;
        starFound = false;
      }
      else if (slashFound && !stringLiteral && !charLiteral && indexedChar == '*') {
        inComment = true;
        commentStartFound = true;
        slashFound = false;
      }
      else {
        starFound = false;
        slashFound = false;
      }
    }

    if (inComment)
      addMultilineCommentAttribute(paragraph);
    else
      removeMultilineCommentAttribute(paragraph);

    if (commentStartFound)
      addStartComment(paragraph);
    else
      removeStartComment(paragraph);

    if (commentEndFound)
      addEndComment(paragraph);
    else
      removeEndComment(paragraph);

    return inComment;
  }
  
  /**
   * Check text to comment and javadoc.
   *
   * @param paragraph the paragraph
   * @param inComment the in comment
   * @param inJavadoc the in javadoc
   * @return the boolean[]
   * @throws BadLocationException the bad location exception
   * @since 0.3
   */
  private boolean[] checkTextToCommentAndJavadoc(Element paragraph, boolean inComment, boolean inJavadoc) throws BadLocationException {
    int startPosition = paragraph.getStartOffset();
    int length = (paragraph.getEndOffset() < getLength() ? paragraph.getEndOffset()
            : getLength()) - startPosition;
    Segment content = new Segment();
    getText(startPosition, length, content);

    boolean slashFound = false;
    boolean starFound = false;
    boolean commentStartFound = false;
    boolean commentEndFound = false;
    boolean stringLiteral = false;
    boolean charLiteral = false;

    //System.out.println("----------------");
    //System.out.println(content);

    for (int wordIndex = 0; wordIndex < content.length(); wordIndex++) {
      char indexedChar = content.charAt(wordIndex);
      if (!inComment && !inJavadoc && slashFound && indexedChar == '/') {
        //System.out.println("1");
        break;
      }
      else if (!inComment && !inJavadoc && indexedChar == '\"') {
        //System.out.println("2");
        stringLiteral = !stringLiteral;
      }
      else if (!inComment && !inJavadoc && indexedChar == '\'') {
        //System.out.println("3");
        charLiteral = !charLiteral;
      }
      else if (!starFound && !stringLiteral && !charLiteral && indexedChar == '/') {
        //System.out.println("4");
        slashFound = true;
      }
      else if (!slashFound && !stringLiteral && !charLiteral && indexedChar == '*') {
        //System.out.println("5");
        starFound = true;
      }
      else if (starFound && !stringLiteral && !charLiteral && indexedChar == '/') {
        //System.out.println("6");
        inComment = false;
        inJavadoc = false;
        commentEndFound = true;
        starFound = false;
      }
      else if (slashFound && !stringLiteral && !charLiteral && indexedChar == '*') {
        //System.out.println("7");
        if (wordIndex + 1 < content.length() && content.charAt(wordIndex + 1) == '*') {
          inJavadoc = true;
        }
        else {
          inComment = true;
        }
        commentStartFound = true;
        slashFound = false;
      }
      else {
        //System.out.println("8");
        starFound = false;
        slashFound = false;
      }
    }
    //System.out.println(inComment + " - " + inJavadoc);
    //System.out.println("----------------");

    if (inComment)
      addMultilineCommentAttribute(paragraph);
    else
      removeMultilineCommentAttribute(paragraph);

    if (commentStartFound)
      addStartComment(paragraph);
    else
      removeStartComment(paragraph);

    if (commentEndFound)
      addEndComment(paragraph);
    else
      removeEndComment(paragraph);

    if (inJavadoc)
      addJavadocAttribute(paragraph);
    else
      removeJavadocAttribute(paragraph);

    return new boolean[] { inComment, inJavadoc };
  }

  /**
   * Contains start comment.
   *
   * @param e the Element
   * @return true, if successful
   */
  private static boolean containsStartComment(Element e) {
    return e.getAttributes().containsAttribute(START_COMMENT, START_COMMENT);
  }

  /**
   * Contains multiline comment attribute.
   *
   * @param e the Element
   * @return true, if successful
   */
  private static boolean containsMultilineCommentAttribute(Element e) {
    return e.getAttributes().containsAttribute(MULTILINE_COMMENT, MULTILINE_COMMENT);
  }

  /**
   * Contains javadoc attribute.
   *
   * @param e the Element
   * @return true, if successful
   * @since 0.3
   */
  private static boolean containsJavadocAttribute(Element e) {
    return e.getAttributes().containsAttribute(JAVADOC, JAVADOC);
  }
  
  /**
   * Contains end comment.
   *
   * @param e the Element
   * @return true, if successful
   */
  private static boolean containsEndComment(Element e) {
    return e.getAttributes().containsAttribute(END_COMMENT, END_COMMENT);
  }

  /**
   * Adds the start comment.
   *
   * @param e the Element
   */
  private void addStartComment(Element e) {
    addAttribute(e, START_COMMENT, START_COMMENT);
  }

  /**
   * Removes the start comment.
   *
   * @param e the Element
   */
  private void removeStartComment(Element e) {
    removeAttribute(e, START_COMMENT);
  }

  /**
   * Adds the multiline comment attribute.
   *
   * @param e the Element
   */
  private void addMultilineCommentAttribute(Element e) {
    addAttribute(e, MULTILINE_COMMENT, MULTILINE_COMMENT);
  }

  /**
   * Removes the multiline comment attribute.
   *
   * @param e the Element
   */
  private void removeMultilineCommentAttribute(Element e) {
    removeAttribute(e, MULTILINE_COMMENT);
  }

  /**
   * Adds the javadoc attribute.
   *
   * @param e the Element
   * @since 0.3
   */
  private void addJavadocAttribute(Element e) {
    addAttribute(e, JAVADOC, JAVADOC);
  }

  /**
   * Removes the javadoc attribute.
   *
   * @param e the Element
   * @since 0.3
   */
  private void removeJavadocAttribute(Element e) {
    removeAttribute(e, JAVADOC);
  }
  
  /**
   * Adds the end comment.
   *
   * @param e the Element
   */
  private void addEndComment(Element e) {
    addAttribute(e, END_COMMENT, END_COMMENT);
  }

  /**
   * Removes the end comment.
   *
   * @param e the Element
   */
  private void removeEndComment(Element e) {
    removeAttribute(e, END_COMMENT);
  }

  /**
   * Adds the attribute.
   *
   * @param e the Element
   * @param name the name
   * @param value the value
   */
  private void addAttribute(Element e, Object name, Object value) {
    ((MutableAttributeSet) e.getAttributes()).addAttribute(name, value);
    fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
            e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
  }

  /**
   * Removes the attribute.
   *
   * @param e the Element
   * @param name the name
   */
  private void removeAttribute(Element e, Object name) {
    ((MutableAttributeSet) e.getAttributes()).removeAttribute(name);
    fireChangedUpdate(new AbstractDocument.DefaultDocumentEvent(e.getStartOffset(),
            e.getEndOffset() - e.getStartOffset(), DocumentEvent.EventType.CHANGE));
  }
  
}
