package de.unisiegen.tpml.graphics ;
import java.awt.Color ;
import java.beans.PropertyChangeEvent ;
import java.beans.PropertyChangeListener ;
import java.beans.PropertyChangeSupport ;
import java.io.IOException ;
import java.io.Reader ;
import java.io.StringReader ;
import java.util.HashMap ;
import java.util.LinkedList ;
import javax.swing.text.AttributeSet ;
import javax.swing.text.BadLocationException ;
import javax.swing.text.DefaultStyledDocument ;
import javax.swing.text.SimpleAttributeSet ;
import javax.swing.text.StyleConstants ;
import org.apache.log4j.Logger ;
import de.unisiegen.tpml.core.exceptions.LanguageParserMultiException ;
import de.unisiegen.tpml.core.exceptions.LanguageParserReplaceException ;
import de.unisiegen.tpml.core.exceptions.LanguageParserWarningException ;
import de.unisiegen.tpml.core.expressions.Expression ;
import de.unisiegen.tpml.core.expressions.Identifier ;
import de.unisiegen.tpml.core.languages.AbstractLanguageScanner ;
import de.unisiegen.tpml.core.languages.Language ;
import de.unisiegen.tpml.core.languages.LanguageParser ;
import de.unisiegen.tpml.core.languages.LanguageParserException ;
import de.unisiegen.tpml.core.languages.LanguageScanner ;
import de.unisiegen.tpml.core.languages.LanguageScannerException ;
import de.unisiegen.tpml.core.languages.LanguageSymbol ;
import de.unisiegen.tpml.core.prettyprinter.PrettyStyle ;
import de.unisiegen.tpml.core.types.TypeName ;
import de.unisiegen.tpml.core.util.Theme ;
import de.unisiegen.tpml.core.util.beans.Bean ;
/**
* An implementation of the {@link javax.swing.text.StyledDocument} interface to
* enable syntax highlighting using the lexer of the current
* {@link de.unisiegen.tpml.core.languages.Language}.
*
* @author Benedikt Meurer
* @author Christian Fehler
* @version $Id:StyledLanguageDocument.java 526M 2006-10-27 16:12:51Z (local) $
* @see javax.swing.text.DefaultStyledDocument
*/
public class StyledLanguageDocument extends DefaultStyledDocument implements
Bean
{
//
// Constants
//
/**
* Empty array of language exceptions.
*/
protected static final LanguageScannerException [ ] EMPTY_ARRAY = new LanguageScannerException [ 0 ] ;
/**
* The {@link Logger} for this class.
*
* @see Logger
*/
protected static final Logger logger = Logger
.getLogger ( StyledLanguageDocument.class ) ;
/**
* The unique serialization identifier of this class.
*/
protected static final long serialVersionUID = 5866657214159718809L ;
//
// Attributes
//
/**
* If any <code>PropertyChangeListeners</code> have been registered, the
* <code>changeSupport</code> field describes them.
*
* @serial
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #removePropertyChangeListener(PropertyChangeListener)
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #firePropertyChange(String, boolean, boolean)
* @see #firePropertyChange(String, int, int)
* @see #firePropertyChange(String, Object, Object)
*/
protected PropertyChangeSupport changeSupport ;
/**
* The {@link Language} for which this document was allocated.
*/
protected Language language ;
/**
* The current exceptions from the {@link #language}s scanner.
*
* @see #getExceptions()
* @see #getExceptions(int)
*/
protected LanguageScannerException exceptions[] ;
/**
* The attributes default style.
*/
protected SimpleAttributeSet normalSet = new SimpleAttributeSet ( ) ;
/**
* The attributes for the various {@link PrettyStyle}s.
*/
protected HashMap < PrettyStyle , SimpleAttributeSet > attributes = new HashMap < PrettyStyle , SimpleAttributeSet > ( ) ;
/**
* The currently active {@link Theme}.
*
* @see Theme
*/
protected Theme theme = Theme.currentTheme ( ) ;
//
// Constructor
//
/**
* Allocates a new <code>StyledLanguageDocument</code> for the given
* <code>language</code>, where the <code>language</code> is used to
* determine the scanner (aka lexer) for the documents content and thereby
* dictates the syntax highlighting.
*
* @param pLanguage the {@link Language} for which to allocate a document.
* @throws NullPointerException if the <code>language</code> is
* <code>null</code>.
*/
public StyledLanguageDocument ( Language pLanguage )
{
if ( pLanguage == null )
{
throw new NullPointerException ( "Language is null" ) ; //$NON-NLS-1$
}
this.language = pLanguage ;
// setup the normal attribute set
StyleConstants.setForeground ( this.normalSet , Theme.currentTheme ( )
.getExpressionColor ( ) ) ;
StyleConstants.setBold ( this.normalSet , false ) ;
// setup the comment set
SimpleAttributeSet commentSet = new SimpleAttributeSet ( ) ;
StyleConstants.setItalic ( commentSet , true ) ;
this.attributes.put ( PrettyStyle.COMMENT , commentSet ) ;
// setup the constant set
SimpleAttributeSet constantSet = new SimpleAttributeSet ( ) ;
StyleConstants.setBold ( constantSet , true ) ;
this.attributes.put ( PrettyStyle.CONSTANT , constantSet ) ;
// setup the keyword set
SimpleAttributeSet keywordSet = new SimpleAttributeSet ( ) ;
StyleConstants.setBold ( keywordSet , true ) ;
this.attributes.put ( PrettyStyle.KEYWORD , keywordSet ) ;
// setup the identifier set
SimpleAttributeSet identifierSet = new SimpleAttributeSet ( ) ;
this.attributes.put ( PrettyStyle.IDENTIFIER , identifierSet ) ;
// setup the type set
SimpleAttributeSet typeSet = new SimpleAttributeSet ( ) ;
StyleConstants.setBold ( typeSet , true ) ;
this.attributes.put ( PrettyStyle.TYPE , typeSet ) ;
// initially setup the attributes
initAttributes ( ) ;
// update the attributes whenever the current theme changes
this.theme.addPropertyChangeListener ( new PropertyChangeListener ( )
{
public void propertyChange ( @ SuppressWarnings ( "unused" )
PropertyChangeEvent evt )
{
try
{
// reload the attributes
initAttributes ( ) ;
// reprocess the document
processChanged ( ) ;
}
catch ( BadLocationException e )
{
// just ignore...
}
}
} ) ;
}
//
// Listener registration
//
/**
* Adds a {@link PropertyChangeListener} to the listener list. The listener is
* registered for all bound properties of the derived class. If
* <code>listener</code> is <code>null</code>, no exception is thrown and
* no action is performed.
*
* @param listener the {@link PropertyChangeListener} to be added.
* @see #getPropertyChangeListeners()
* @see #removePropertyChangeListener(PropertyChangeListener)
*/
public synchronized void addPropertyChangeListener (
PropertyChangeListener listener )
{
if ( listener == null )
{
return ;
}
if ( this.changeSupport == null )
{
this.changeSupport = new PropertyChangeSupport ( this ) ;
}
this.changeSupport.addPropertyChangeListener ( listener ) ;
}
/**
* Adds a {@link PropertyChangeListener} to the listener list for a specific
* property. The specified property may be user-defined, or one of the
* properties provided by the object. If <code>listener</code> is
* <code>null</code>, no exception is thrown and no action is performed.
*
* @param propertyName one of the property names of the object.
* @param listener the {@link PropertyChangeListener} to be added.
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners(String)
*/
public synchronized void addPropertyChangeListener ( String propertyName ,
PropertyChangeListener listener )
{
if ( listener == null )
{
return ;
}
if ( this.changeSupport == null )
{
this.changeSupport = new PropertyChangeSupport ( this ) ;
}
this.changeSupport.addPropertyChangeListener ( propertyName , listener ) ;
}
/**
* Support for reporting bound property changes for boolean properties. This
* method can be called when a bound property has changed and it will send the
* appropriate {@link PropertyChangeEvent} to any registered
* {@link PropertyChangeListener}s.
*
* @param propertyName the propery whose value has changed.
* @param oldValue the property's previous value.
* @param newValue the property's new value.
*/
protected void firePropertyChange ( String propertyName , boolean oldValue ,
boolean newValue )
{
PropertyChangeSupport tmpChangeSupport = this.changeSupport ;
if ( tmpChangeSupport == null )
{
return ;
}
tmpChangeSupport.firePropertyChange ( propertyName , oldValue , newValue ) ;
}
/**
* Support for reporting bound property changes for boolean properties. This
* method can be called when a bound property has changed and it will send the
* appropriate {@link PropertyChangeEvent} to any registered
* {@link PropertyChangeListener}s.
*
* @param propertyName the propery whose value has changed.
* @param oldValue the property's previous value.
* @param newValue the property's new value.
*/
protected void firePropertyChange ( String propertyName , int oldValue ,
int newValue )
{
PropertyChangeSupport tmpChangeSupport = this.changeSupport ;
if ( tmpChangeSupport == null )
{
return ;
}
tmpChangeSupport.firePropertyChange ( propertyName , oldValue , newValue ) ;
}
//
// Listener invocation
//
/**
* Support for reporting bound property changes for Object properties. This
* method can be called when a bound property has changed and it will send the
* appropriate {@link PropertyChangeEvent} to any registered
* {@link PropertyChangeListener}s.
*
* @param propertyName the property whose value has changed.
* @param oldValue the property's previous value.
* @param newValue the property's new value.
*/
protected void firePropertyChange ( String propertyName , Object oldValue ,
Object newValue )
{
PropertyChangeSupport tmpChangeSupport = this.changeSupport ;
if ( tmpChangeSupport == null )
{
return ;
}
tmpChangeSupport.firePropertyChange ( propertyName , oldValue , newValue ) ;
}
//
// Accessors
//
/**
* Returns the current {@link LanguageScannerException}s that were detected
* while trying to interpret the token stream.
*
* @return the exceptions.
* @see #getExceptions(int)
*/
public LanguageScannerException [ ] getExceptions ( )
{
return ( this.exceptions != null ) ? this.exceptions : EMPTY_ARRAY ;
}
/**
* Returns the exception at the given <code>index</code>.
*
* @param index the index of the exception to return.
* @return the exception at the <code>index</code>.
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is out
* of bounds.
* @see #getExceptions()
*/
public LanguageScannerException getExceptions ( int index )
{
return getExceptions ( ) [ index ] ;
}
//
// Primitives
//
/**
* Returns the {@link Expression} for the program text within this document.
* Throws an exception if a parsing error occurred.
*
* @return the {@link Expression} for the program text.
* @throws Exception
*/
public Expression getExpression ( ) throws Exception
{
return this.language.newParser (
new StringReader ( getText ( 0 , getLength ( ) ) ) ).parse ( ) ;
}
/**
* Returns an array of all the property change listeners registered on this
* object.
*
* @return all of this object's {@link PropertyChangeListener}s or an empty
* array if no property change listeners are currently registered.
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #removePropertyChangeListener(PropertyChangeListener)
*/
public synchronized PropertyChangeListener [ ] getPropertyChangeListeners ( )
{
if ( this.changeSupport == null )
{
return new PropertyChangeListener [ 0 ] ;
}
return this.changeSupport.getPropertyChangeListeners ( ) ;
}
/**
* Returns an array of all the listeners which have been associated with the
* named property.
*
* @param propertyName a valid property name.
* @return all of the {@link PropertyChangeListener}s associated with the
* named property or an empty array if no listeners have been added
*/
public synchronized PropertyChangeListener [ ] getPropertyChangeListeners (
String propertyName )
{
if ( this.changeSupport == null )
{
return new PropertyChangeListener [ 0 ] ;
}
return this.changeSupport.getPropertyChangeListeners ( propertyName ) ;
}
//
// Initialization
//
/**
* Initializes the attributes to use the fonts from the current theme.
*/
protected void initAttributes ( )
{
// determine the current font family and size
String fontFamily = this.theme.getFont ( ).getFamily ( ) ;
int fontSize = this.theme.getFont ( ).getSize ( ) ;
// use the colors and font from the current theme
StyleConstants.setFontFamily ( this.normalSet , fontFamily ) ;
StyleConstants.setFontSize ( this.normalSet , fontSize ) ;
StyleConstants.setForeground ( this.attributes.get ( PrettyStyle.COMMENT ) ,
this.theme.getCommentColor ( ) ) ;
StyleConstants.setFontFamily ( this.attributes.get ( PrettyStyle.COMMENT ) ,
fontFamily ) ;
StyleConstants.setFontSize ( this.attributes.get ( PrettyStyle.COMMENT ) ,
fontSize ) ;
StyleConstants.setForeground (
this.attributes.get ( PrettyStyle.CONSTANT ) , this.theme
.getConstantColor ( ) ) ;
StyleConstants.setFontFamily (
this.attributes.get ( PrettyStyle.CONSTANT ) , fontFamily ) ;
StyleConstants.setFontSize ( this.attributes.get ( PrettyStyle.CONSTANT ) ,
fontSize ) ;
StyleConstants.setForeground ( this.attributes.get ( PrettyStyle.KEYWORD ) ,
this.theme.getKeywordColor ( ) ) ;
StyleConstants.setFontFamily ( this.attributes.get ( PrettyStyle.KEYWORD ) ,
fontFamily ) ;
StyleConstants.setFontSize ( this.attributes.get ( PrettyStyle.KEYWORD ) ,
fontSize ) ;
StyleConstants.setForeground ( this.attributes
.get ( PrettyStyle.IDENTIFIER ) , this.theme.getIdentifierColor ( ) ) ;
StyleConstants.setFontFamily ( this.attributes
.get ( PrettyStyle.IDENTIFIER ) , fontFamily ) ;
StyleConstants.setFontSize (
this.attributes.get ( PrettyStyle.IDENTIFIER ) , fontSize ) ;
StyleConstants.setForeground ( this.attributes.get ( PrettyStyle.TYPE ) ,
this.theme.getTypeColor ( ) ) ;
StyleConstants.setFontFamily ( this.attributes.get ( PrettyStyle.TYPE ) ,
fontFamily ) ;
StyleConstants.setFontSize ( this.attributes.get ( PrettyStyle.TYPE ) ,
fontSize ) ;
}
//
// Change handling
//
/**
* {@inheritDoc}
*
* @see javax.swing.text.AbstractDocument#insertString(int, java.lang.String,
* javax.swing.text.AttributeSet)
*/
@ Override
public void insertString ( int offset , String str , AttributeSet set )
throws BadLocationException
{
super.insertString ( offset , str , set ) ;
processChanged ( ) ;
}
/**
* Processes the document content after a change.
*
* @throws BadLocationException if the processing failed.
*/
@ SuppressWarnings (
{ "unused" , "null" } )
public void processChanged ( ) throws BadLocationException
{
// reset the character attributes
setCharacterAttributes ( 0 , getLength ( ) , this.normalSet , true ) ;
// allocate a list to collect the exceptions
LanguageScannerException [ ] tmpExceptions = null ;
try
{
// start with first character
int offset = 0 ;
// determine the document content
String content = getText ( offset , getLength ( ) ) ;
// allocate the scanner (initially)
final LanguageScanner scanner = this.language
.newScanner ( new StringReader ( content ) ) ;
// collect the tokens returned by the scanner
final LinkedList < LanguageSymbol > symbols = new LinkedList < LanguageSymbol > ( ) ;
// determine the tokens for the content
while ( true )
{
try
{
// read the next token from the scanner
LanguageSymbol symbol = scanner.nextSymbol ( ) ;
if ( symbol == null )
{
break ;
}
// add the token to our list
symbols.add ( symbol ) ;
// check if we have an attribute set for the token
SimpleAttributeSet set = this.attributes.get ( scanner
.getStyleBySymbol ( symbol ) ) ;
if ( set == null )
{
set = this.normalSet ;
}
// apply the character attribute set
setCharacterAttributes ( offset + symbol.getLeft ( ) , symbol
.getRight ( )
- symbol.getLeft ( ) , set , true ) ;
}
catch ( LanguageScannerException e )
{
// calculate the new offset
int newOffset = offset + e.getRight ( ) ;
// skip the problematic characters
content = content.substring ( e.getRight ( ) ) ;
// adjust the exception according to the offset
e = new LanguageScannerException ( offset + e.getLeft ( ) , offset
+ e.getRight ( ) , e.getMessage ( ) , e.getCause ( ) ) ;
// setup the error attribute set
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setFontFamily ( errorSet , this.theme.getFont ( )
.getFamily ( ) ) ;
StyleConstants.setFontSize ( errorSet , this.theme.getFont ( )
.getSize ( ) ) ;
StyleConstants.setForeground ( errorSet , Color.RED ) ;
StyleConstants.setUnderline ( errorSet , true ) ;
errorSet.addAttribute ( "exception" , e ) ; //$NON-NLS-1$
// apply the error character attribute set to indicate the syntax
// error
setCharacterAttributes ( e.getLeft ( ) , e.getRight ( )
- e.getLeft ( ) , errorSet , false ) ;
// adjust the offset to point after the error
offset = newOffset ;
// restart the scanner after the error
scanner.restart ( new StringReader ( content ) ) ;
// add the exception to our list
if ( tmpExceptions == null )
{
tmpExceptions = new LanguageScannerException [ ]
{ e } ;
}
else
{
LanguageScannerException [ ] newExceptions = new LanguageScannerException [ tmpExceptions.length + 1 ] ;
System.arraycopy ( tmpExceptions , 0 , newExceptions , 0 ,
tmpExceptions.length ) ;
newExceptions [ tmpExceptions.length ] = e ;
tmpExceptions = newExceptions ;
}
}
}
// Parse only if the scanner is happy
if ( tmpExceptions == null )
{
// allocate a parser based on a scanner that operates on the previously
// collected
// tokens from the scanner step above...
LanguageParser parser = this.language
.newParser ( new AbstractLanguageScanner ( )
{
public void restart ( Reader reader )
{
throw new UnsupportedOperationException ( ) ;
}
public LanguageSymbol nextSymbol ( ) throws IOException ,
LanguageScannerException
{
return ( ! symbols.isEmpty ( ) ) ? symbols.poll ( ) : null ;
}
@ Override
public PrettyStyle getStyleBySymbolId ( int id )
{
return ( ( AbstractLanguageScanner ) scanner )
.getStyleBySymbolId ( id ) ;
}
} ) ;
// ...and try to parse the token stream
try
{
Expression expression = parser.parse ( ) ;
for ( Identifier id : expression.getIdentifiersFree ( ) )
{
SimpleAttributeSet freeSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( freeSet , Theme.currentTheme ( )
.getFreeIdColor ( ) ) ;
StyleConstants.setBold ( freeSet , true ) ;
freeSet.addAttribute ( "Free Identifier" , "Free Identifier" ) ; //$NON-NLS-1$ //$NON-NLS-2$
setCharacterAttributes ( id.getParserStartOffset ( ) , id
.getParserEndOffset ( )
- id.getParserStartOffset ( ) , freeSet , false ) ;
}
for ( TypeName typeName : expression.getTypeNamesFree ( ) )
{
SimpleAttributeSet freeSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( freeSet , Theme.currentTheme ( )
.getFreeIdColor ( ) ) ;
StyleConstants.setBold ( freeSet , true ) ;
freeSet.addAttribute ( "Free TypeName" , "Free TypeName" ) ; //$NON-NLS-1$ //$NON-NLS-2$
setCharacterAttributes ( typeName.getParserStartOffset ( ) ,
typeName.getParserEndOffset ( )
- typeName.getParserStartOffset ( ) , freeSet , false ) ;
}
}
catch ( LanguageParserReplaceException e )
{
String [ ] messageRename = e.getMessagesReplace ( ) ;
int [ ] startOffsetRename = e.getParserStartOffsetReplace ( ) ;
int [ ] endOffsetRename = e.getParserEndOffsetReplace ( ) ;
String [ ] messageNegative = e.getMessagesNegative ( ) ;
int [ ] startOffsetNegative = e.getParserStartOffsetNegative ( ) ;
int [ ] endOffsetNegative = e.getParserEndOffsetNegative ( ) ;
tmpExceptions = new LanguageParserException [ startOffsetRename.length
+ startOffsetNegative.length ] ;
for ( int i = 0 ; i < startOffsetRename.length ; i ++ )
{
tmpExceptions [ i ] = new LanguageParserReplaceException (
messageRename [ i ] , startOffsetRename [ i ] ,
endOffsetRename [ i ] , e.getReplaceText ( ) ) ;
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( errorSet , Color.BLUE ) ;
StyleConstants.setUnderline ( errorSet , true ) ;
errorSet.addAttribute ( "exception" , tmpExceptions [ i ] ) ; //$NON-NLS-1$
setCharacterAttributes ( startOffsetRename [ i ] ,
endOffsetRename [ i ] - startOffsetRename [ i ] , errorSet ,
false ) ;
}
for ( int i = 0 ; i < startOffsetNegative.length ; i ++ )
{
tmpExceptions [ startOffsetRename.length + i ] = new LanguageParserException (
messageNegative [ i ] , startOffsetNegative [ i ] ,
endOffsetNegative [ i ] ) ;
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( errorSet , Color.RED ) ;
StyleConstants.setUnderline ( errorSet , true ) ;
errorSet.addAttribute (
"exception" , tmpExceptions [ startOffsetRename.length + i ] ) ; //$NON-NLS-1$
setCharacterAttributes ( startOffsetNegative [ i ] ,
endOffsetNegative [ i ] - startOffsetNegative [ i ] , errorSet ,
false ) ;
}
}
catch ( LanguageParserMultiException e )
{
String [ ] message = e.getMessages ( ) ;
int [ ] startOffset = e.getParserStartOffset ( ) ;
int [ ] endOffset = e.getParserEndOffset ( ) ;
tmpExceptions = new LanguageParserException [ startOffset.length ] ;
for ( int i = 0 ; i < startOffset.length ; i ++ )
{
tmpExceptions [ i ] = new LanguageParserException ( message [ i ] ,
startOffset [ i ] , endOffset [ i ] ) ;
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( errorSet , Color.RED ) ;
StyleConstants.setUnderline ( errorSet , true ) ;
errorSet.addAttribute ( "exception" , tmpExceptions [ i ] ) ; //$NON-NLS-1$
setCharacterAttributes ( startOffset [ i ] , endOffset [ i ]
- startOffset [ i ] , errorSet , false ) ;
}
}
catch ( LanguageParserWarningException e )
{
// setup the warning attribute set
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setBackground ( errorSet , Theme.currentTheme ( )
.getParserWarningColor ( ) ) ;
errorSet.addAttribute ( "warning" , e ) ; //$NON-NLS-1$
// check if this is unexpected end of file
if ( e.getLeft ( ) < 0 && e.getRight ( ) < 0 )
{
setCharacterAttributes ( getLength ( ) , getLength ( ) , errorSet ,
false ) ;
}
else
{
// apply the error character attribute set to indicate the syntax
// error
setCharacterAttributes ( e.getLeft ( ) , e.getRight ( )
- e.getLeft ( ) , errorSet , false ) ;
}
// add the exception to our list
if ( tmpExceptions == null )
{
tmpExceptions = new LanguageScannerException [ ]
{ new LanguageParserWarningException ( e.getMessage ( ) , e
.getRight ( ) , e.getRight ( ) , e.getInsertText ( ) ) } ;
}
else
{
LanguageScannerException [ ] newExceptions = new LanguageScannerException [ tmpExceptions.length + 1 ] ;
System.arraycopy ( tmpExceptions , 0 , newExceptions , 0 ,
tmpExceptions.length ) ;
newExceptions [ tmpExceptions.length ] = new LanguageParserWarningException (
e.getMessage ( ) , e.getRight ( ) , e.getRight ( ) , e
.getInsertText ( ) ) ;
tmpExceptions = newExceptions ;
}
}
catch ( LanguageParserException e )
{
// setup the error attribute set
SimpleAttributeSet errorSet = new SimpleAttributeSet ( ) ;
StyleConstants.setForeground ( errorSet , Color.RED ) ;
StyleConstants.setUnderline ( errorSet , true ) ;
errorSet.addAttribute ( "exception" , e ) ; //$NON-NLS-1$
// check if this is unexpected end of file
if ( e.getLeft ( ) < 0 && e.getRight ( ) < 0 )
{
setCharacterAttributes ( getLength ( ) , getLength ( ) , errorSet ,
false ) ;
}
else
{
// apply the error character attribute set to indicate the syntax
// error
setCharacterAttributes ( e.getLeft ( ) , e.getRight ( )
- e.getLeft ( ) , errorSet , false ) ;
}
// add the exception to our list
if ( tmpExceptions == null )
{
tmpExceptions = new LanguageScannerException [ ]
{ e } ;
}
else
{
LanguageScannerException [ ] newExceptions = new LanguageScannerException [ tmpExceptions.length + 1 ] ;
System.arraycopy ( tmpExceptions , 0 , newExceptions , 0 ,
tmpExceptions.length ) ;
newExceptions [ tmpExceptions.length ] = e ;
tmpExceptions = newExceptions ;
}
}
}
}
catch ( Exception e )
{
logger.warn (
"Failed to process changes in the styled language document" , e ) ; //$NON-NLS-1$
}
// update the exceptions property if necessary
if ( this.exceptions != tmpExceptions )
{
LanguageScannerException [ ] oldExceptions = this.exceptions ;
this.exceptions = tmpExceptions ;
firePropertyChange ( "exceptions" , oldExceptions , this.exceptions ) ; //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*
* @see javax.swing.text.Document#remove(int, int)
*/
@ Override
public void remove ( int offset , int length ) throws BadLocationException
{
super.remove ( offset , length ) ;
processChanged ( ) ;
}
/**
* Removes a {@link PropertyChangeListener} from the listener list. This
* method should be used to remove PropertyChangeListeners that were
* registered for all bound properties of this class. If <code>listener</code>
* is <code>null</code>, no exception is thrown and no action is performed.
*
* @param listener the {@link PropertyChangeListener} to be removed.
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #getPropertyChangeListeners()
*/
public synchronized void removePropertyChangeListener (
PropertyChangeListener listener )
{
if ( listener == null || this.changeSupport == null )
{
return ;
}
this.changeSupport.removePropertyChangeListener ( listener ) ;
}
/**
* Removes a {@link PropertyChangeListener} from the listener list for a
* specific property. This method should be used to remove
* {@link PropertyChangeListener}s that were registered for a specific bound
* property. If <code>listener</code> is <code>null</code>, no exception
* is thrown and no action is performed.
*
* @param propertyName a valid property name.
* @param listener the {@link PropertyChangeListener} to be removed.
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners(String)
*/
public synchronized void removePropertyChangeListener ( String propertyName ,
PropertyChangeListener listener )
{
if ( listener == null || this.changeSupport == null )
{
return ;
}
this.changeSupport.removePropertyChangeListener ( propertyName , listener ) ;
}
}