/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Mathew A. Nelson
* - Initial API and implementation
* Flemming N. Larsen
* - Bugfix: Tabs were sometimes added at the end of the document when
* strings are inserted. Bug fixed with the insertString() method, were the
* tabCount is now decremented when a '}' is found in the current element
* - Updated to use methods from the Logger, which replaces logger methods
* that have been (re)moved from the robocode.util.Utils class
*******************************************************************************/
package net.sf.robocode.ui.editor;
import net.sf.robocode.io.Logger;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;
import javax.swing.undo.UndoManager;
/**
* @author Mathew A. Nelson (original)
* @author Flemming N. Larsen (contributor)
*/
@SuppressWarnings("serial")
public class JavaDocument extends PlainDocument {
private static final String IN_COMMENT = "inComment";
private static final String ENDS_COMMENT = "endsComment";
private static final String STARTS_COMMENT = "startsComment";
private UndoHandler undoHandler;
private boolean needsRedraw;
private EditWindow editWindow;
private boolean editing;
public JavaDocument() {
super();
init();
}
public EditWindow getEditWindow() {
return editWindow;
}
private void init() {
addUndoableEditListener(getUndoHandler());
}
@Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
if (!editing) {
super.insertString(offs, str, a);
return;
}
if (str.equals("}")) {
if (getText(offs - 1, 1).equals("\t")) {
super.remove(offs - 1, 1);
super.insertString(offs - 1, str, a);
} else {
super.insertString(offs, str, a);
}
} else if (str.equals("\n")) {
int elementIndex = getDefaultRootElement().getElementIndex(offs);
Element element = getDefaultRootElement().getElement(elementIndex);
int startOffset = element.getStartOffset();
int endOffset = element.getEndOffset();
String elementText;
elementText = getText(startOffset, endOffset - startOffset);
int tabCount = 0;
while (elementText.charAt(tabCount) == '\t') {
tabCount++;
}
if (elementText.indexOf("{") >= 0) {
tabCount++;
}
if (elementText.indexOf("}") >= 0) {
tabCount--;
}
StringBuffer tabs = new StringBuffer();
for (int i = 0; i < tabCount; i++) {
tabs.append('\t');
}
super.insertString(offs, str + tabs.toString(), a);
} else {
super.insertString(offs, str, a);
}
}
@Override
protected void insertUpdate(DefaultDocumentEvent event, AttributeSet attributeSet) {
if (editWindow != null) {
editWindow.setModified(true);
}
int orgChangedIndex = getDefaultRootElement().getElementIndex(event.getOffset());
super.insertUpdate(event, attributeSet);
// Get the root element
Element rootElement = getDefaultRootElement();
// Determine what changes were made to the document
DocumentEvent.ElementChange deltas = event.getChange(rootElement);
if (deltas == null) {
Element changedElement = getDefaultRootElement().getElement(orgChangedIndex);
processMultilineComments(changedElement, false);
} else {
Element changedElements[] = deltas.getChildrenAdded();
if (changedElements == null || changedElements.length == 0) {
Logger.logError("Unknown insert even, 0 children added.");
} else {
for (Element element : changedElements) {
processMultilineComments(element, true);
}
}
}
}
public boolean isNeedsRedraw() {
return needsRedraw;
}
@Override
protected void postRemoveUpdate(DefaultDocumentEvent event) {
if (editWindow != null) {
editWindow.setModified(true);
}
super.postRemoveUpdate(event);
processMultilineComments(event);
}
public void processMultilineComments(DefaultDocumentEvent event) {
Element rootElement = getDefaultRootElement();
// Determine what changes were made to the document
int changedIndex = getDefaultRootElement().getElementIndex(event.getOffset());
Element changedElement = getDefaultRootElement().getElement(changedIndex);
processMultilineComments(changedElement, event.getChange(rootElement) != null);
}
public void processMultilineComments(Element element, boolean isDeltas) {
int elementIndex = getDefaultRootElement().getElementIndex(element.getStartOffset());
int startOffset = element.getStartOffset();
int endOffset = element.getEndOffset();
String elementText;
try {
elementText = getText(startOffset, endOffset - startOffset);
} catch (BadLocationException e) {
Logger.logError("Error processing updates: ", e);
return;
}
boolean followingLineComment,
previousLineComment = false,
startsComment = false,
endsComment = false;
// If we already had a comment flag, then the last line must
// have ended "still in a comment"
MutableAttributeSet a = (MutableAttributeSet) element.getAttributes();
if (a.isDefined(IN_COMMENT)) {
previousLineComment = true;
} // we don't have a comment flag, so check the last line.
// note: This should only happen on new lines!
else if (isDeltas) {
int lastElementIndex = elementIndex - 1;
if (lastElementIndex >= 0) {
AbstractElement lastElement = (AbstractElement) getDefaultRootElement().getElement(lastElementIndex);
if (!lastElement.isDefined(ENDS_COMMENT) && lastElement.isDefined(IN_COMMENT)
|| lastElement.isDefined(STARTS_COMMENT)) {
a.addAttribute(IN_COMMENT, IN_COMMENT);
previousLineComment = true;
}
}
}
followingLineComment = previousLineComment;
int cIndex = elementText.indexOf("//");
int sIndex, eIndex;
if (cIndex >= 0) {
sIndex = elementText.lastIndexOf("/*", cIndex);
eIndex = elementText.lastIndexOf("*/", cIndex);
} else {
sIndex = elementText.lastIndexOf("/*");
eIndex = elementText.lastIndexOf("*/");
}
if (eIndex > sIndex) {
followingLineComment = false;
startsComment = false;
endsComment = true;
} else if (sIndex > eIndex) {
followingLineComment = true;
startsComment = true;
endsComment = false;
}
// Following lines should be comments.
if (followingLineComment) {
// We started the comment
if (startsComment) {
if (!a.isDefined(STARTS_COMMENT)) {
// mark line startsComment
a.addAttribute(STARTS_COMMENT, STARTS_COMMENT);
// make sure next line(s) are marked inComment, until an endComment line
setFollowingLinesCommentFlag(startOffset, true);
}
} else if (a.isDefined(STARTS_COMMENT)) {
a.removeAttribute(STARTS_COMMENT);
}
// If we used to end the comment but no longer do, fix that...
if (a.isDefined(ENDS_COMMENT)) {
// unmark line as endsComment
a.removeAttribute(ENDS_COMMENT);
// make sure next line(s) are marked inComment, until a endsComment line
setFollowingLinesCommentFlag(startOffset, true);
} // For cut & paste we need to check anyway.
else if (isDeltas) {
setFollowingLinesCommentFlag(startOffset, true);
}
} // Else following lines are NOT comments
else {
// We ended the comment
if (endsComment) {
if (!a.isDefined(ENDS_COMMENT)) {
// mark line endsComment
a.addAttribute(ENDS_COMMENT, ENDS_COMMENT);
// Make sure next line(s) are marked !inComment, until a startComment line
setFollowingLinesCommentFlag(startOffset, false);
}
} else if (a.isDefined(ENDS_COMMENT)) {
a.removeAttribute(ENDS_COMMENT);
}
// If we used to start a comment, but no longer do, fix that...
if (a.isDefined(STARTS_COMMENT)) {
// mark line startsComment
a.removeAttribute(STARTS_COMMENT);
// make sure next line(s) are marked !inComment, until an endComment line
setFollowingLinesCommentFlag(startOffset, false);
} // For cut & paste we need to check anyway.
else if (isDeltas) {
setFollowingLinesCommentFlag(startOffset, false);
}
}
}
public void setEditWindow(EditWindow newEditWindow) {
editWindow = newEditWindow;
}
public void setFollowingLinesCommentFlag(int offset, boolean commentFlag) {
int elementIndex = getDefaultRootElement().getElementIndex(offset) + 1;
boolean done = false;
while (!done) {
// need to check for last line somehow...
Element e = getDefaultRootElement().getElement(elementIndex);
if (e == null) {
done = true;
} else {
MutableAttributeSet a = (MutableAttributeSet) e.getAttributes();
if (commentFlag) {
if (a.isDefined(IN_COMMENT)) {
done = true;
} else {
a.addAttribute(IN_COMMENT, IN_COMMENT);
needsRedraw = true;
}
if (a.isDefined(ENDS_COMMENT)) {
done = true;
}
} else {
if (!a.isDefined(IN_COMMENT)) {
done = true;
} else {
a.removeAttribute(IN_COMMENT);
needsRedraw = true;
}
if (a.isDefined(STARTS_COMMENT)) {
done = true;
}
}
elementIndex++;
}
}
}
public void setNeedsRedraw(boolean newNeedsRedraw) {
needsRedraw = newNeedsRedraw;
}
public boolean getEditing() {
return editing;
}
public void setEditing(boolean editing) {
// This check is a bugfix for [2643448] - Editor UNDO does delete the line when no undo left.
// All undo edits are discarded, when the editor is going from not editing to editing.
if (editing && !this.editing) {
getUndoHandler().discardAllEdits();
}
this.editing = editing;
}
/**
* Returns the UndoHandler
*
* @return the UndoHandler
*/
private UndoHandler getUndoHandler() {
if (undoHandler == null) {
undoHandler = new UndoHandler();
}
return undoHandler;
}
/**
* Undo
*/
public void undo() {
if (getUndoHandler().canUndo()) {
writeLock();
try {
getUndoHandler().undo();
} finally {
writeUnlock();
}
needsRedraw = true;
}
}
/**
* Redo
*/
public void redo() {
if (getUndoHandler().canRedo()) {
writeLock();
try {
getUndoHandler().redo();
} finally {
writeUnlock();
}
needsRedraw = true;
}
}
private class UndoHandler extends UndoManager {
@Override
public synchronized void undo() {
super.undo();
processMultilineComments((DefaultDocumentEvent) lastEdit());
}
@Override
public synchronized void redo() {
super.redo();
processMultilineComments((DefaultDocumentEvent) lastEdit());
}
}
}