You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC
svn commit: r627255 [19/41] - in
/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/
com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/
com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ContextualDiffProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ContextualDiffProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ContextualDiffProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ContextualDiffProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,464 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package com.ecyrd.jspwiki.diff;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.commons.jrcs.diff.*;
+import org.apache.commons.jrcs.diff.myers.MyersDiff;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+
+/**
+ * A seriously better diff provider, which highlights changes word-by-word using
+ * CSS.
+ *
+ * Suggested by John Volkar.
+ *
+ * @author John Volkar
+ * @author Janne Jalkanen
+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
+ */
+
+public class ContextualDiffProvider implements DiffProvider
+{
+
+ private static final Logger log = Logger.getLogger( ContextualDiffProvider.class );
+
+ public static final String PROP_UNCHANGED_CONTEXT_LIMIT = "jspwiki.contextualDiffProvider.unchangedContextLimit";
+
+ //TODO all of these publics can become jspwiki.properties entries...
+ //TODO span title= can be used to get hover info...
+
+ public boolean m_emitChangeNextPreviousHyperlinks = true;
+
+ //Don't use spans here the deletion and insertions are nested in this...
+ public String m_changeStartHtml = ""; //This could be a image '>' for a start marker
+ public String m_changeEndHtml = ""; //and an image for an end '<' marker
+ public String m_diffStart = "<div class=\"diff-wikitext\">";
+ public String m_diffEnd = "</div>";
+
+ // Unfortunately we need to do dumb HTML here for RSS feeds.
+
+ public String m_insertionStartHtml = "<font color=\"#8000FF\"><span class=\"diff-insertion\">";
+ public String m_insertionEndHtml = "</span></font>";
+ public String m_deletionStartHtml = "<strike><font color=\"red\"><span class=\"diff-deletion\">";
+ public String m_deletionEndHtml = "</span></font></strike>";
+ private String m_anchorPreIndex = "<a name=\"change-";
+ private String m_anchorPostIndex = "\" />";
+ private String m_backPreIndex = "<a class=\"diff-nextprev\" title=\"Go to previous change\" href=\"#change-";
+ private String m_backPostIndex = "\"><<</a>";
+ private String m_forwardPreIndex = "<a class=\"diff-nextprev\" title=\"Go to next change\" href=\"#change-";
+ private String m_forwardPostIndex = "\">>></a>";
+ public String m_elidedHeadIndicatorHtml = "<br/><br/><b>...</b>";
+ public String m_elidedTailIndicatorHtml = "<b>...</b><br/><br/>";
+ public String m_lineBreakHtml = "<br />";
+ public String m_alternatingSpaceHtml = " ";
+
+ // This one, I will make property file based...
+ private static final int LIMIT_MAX_VALUE = (Integer.MAX_VALUE /2) - 1;
+ private int m_unchangedContextLimit = LIMIT_MAX_VALUE;
+
+
+
+ public ContextualDiffProvider()
+ {}
+
+ /**
+ * @see com.ecyrd.jspwiki.WikiProvider#getProviderInfo()
+ */
+ public String getProviderInfo()
+ {
+ return "ContextualDiffProvider";
+ }
+
+ /**
+ * @see com.ecyrd.jspwiki.WikiProvider#initialize(com.ecyrd.jspwiki.WikiEngine,
+ * java.util.Properties)
+ */
+ public void initialize(WikiEngine engine, Properties properties) throws NoRequiredPropertyException, IOException
+ {
+ String configuredLimit = properties.getProperty(PROP_UNCHANGED_CONTEXT_LIMIT, Integer
+ .toString(LIMIT_MAX_VALUE));
+ int limit = LIMIT_MAX_VALUE;
+ try
+ {
+ limit = Integer.parseInt(configuredLimit);
+ }
+ catch (NumberFormatException e)
+ {
+ log.warn("Failed to parseInt " + PROP_UNCHANGED_CONTEXT_LIMIT + "=" + configuredLimit
+ + " Will use a huge number as limit.", e);
+ }
+ m_unchangedContextLimit = limit;
+ }
+
+
+
+ /**
+ * Do a colored diff of the two regions. This. is. serious. fun. ;-)
+ *
+ * @see com.ecyrd.jspwiki.diff.DiffProvider#makeDiffHtml(java.lang.String,
+ * java.lang.String)
+ */
+ public synchronized String makeDiffHtml( WikiContext ctx, String wikiOld, String wikiNew )
+ {
+ //
+ // Sequencing handles lineterminator to <br /> and every-other consequtive space to a
+ //
+ String[] alpha = sequence( TextUtil.replaceEntities( wikiOld ) );
+ String[] beta = sequence( TextUtil.replaceEntities( wikiNew ) );
+
+ Revision rev = null;
+ try
+ {
+ rev = Diff.diff( alpha, beta, new MyersDiff() );
+ }
+ catch( DifferentiationFailedException dfe )
+ {
+ log.error( "Diff generation failed", dfe );
+ return "Error while creating version diff.";
+ }
+
+ int revSize = rev.size();
+
+ StringBuffer sb = new StringBuffer();
+
+ sb.append( m_diffStart );
+
+ //
+ // The MyersDiff is a bit dumb by converting a single line multi-word diff into a series
+ // of Changes. The ChangeMerger pulls them together again...
+ //
+ ChangeMerger cm = new ChangeMerger( sb, alpha, revSize );
+
+ rev.accept( cm );
+
+ cm.shutdown();
+
+ sb.append( m_diffEnd );
+
+ return sb.toString();
+ }
+
+ /**
+ * Take the string and create an array from it, split it first on newlines, making
+ * sure to preserve the newlines in the elements, split each resulting element on
+ * spaces, preserving the spaces.
+ *
+ * All this preseving of newlines and spaces is so the wikitext when diffed will have fidelity
+ * to it's original form. As a side affect we see edits of purely whilespace.
+ */
+ private String[] sequence( String wikiText )
+ {
+ String[] linesArray = Diff.stringToArray( wikiText );
+
+ List list = new ArrayList();
+
+ for( int i = 0; i < linesArray.length; i++ )
+ {
+ String line = linesArray[i];
+
+ String lastToken = null;
+ String token = null;
+ // StringTokenizer might be discouraged but it still is perfect here...
+ for (StringTokenizer st = new StringTokenizer( line, " ", true ); st.hasMoreTokens();)
+ {
+ token = st.nextToken();
+
+ if(" ".equals( lastToken) && " ".equals( token ))
+ token = m_alternatingSpaceHtml;
+
+ list.add(token);
+ lastToken = token;
+ }
+
+ list.add(m_lineBreakHtml); // Line Break
+ }
+
+ return (String[])list.toArray( new String[0] );
+ }
+
+ /**
+ * This helper class does the housekeeping for merging
+ * our various changes down and also makes sure that the
+ * whole change process is threadsafe by encapsulating
+ * all necessary variables.
+ */
+ private final class ChangeMerger implements RevisionVisitor
+ {
+ private StringBuffer m_sb = null;
+
+ /** Keeping score of the original lines to process */
+ private int m_max = -1;
+
+ private int m_index = 0;
+
+ /** Index of the next element to be copied into the output. */
+ private int m_firstElem = 0;
+
+ /** Link Anchor counter */
+ private int m_count = 1;
+
+ /** State Machine Mode */
+ private int m_mode = -1; /* -1: Unset, 0: Add, 1: Del, 2: Change mode */
+
+ /** Buffer to coalesce the changes together */
+ private StringBuffer m_origBuf = null;
+
+ private StringBuffer m_newBuf = null;
+
+ /** Reference to the source string array */
+ private String[] m_origStrings = null;
+
+ private ChangeMerger( final StringBuffer sb, final String[] origStrings, final int max )
+ {
+ m_sb = sb;
+ m_origStrings = origStrings;
+ m_max = max;
+
+ m_origBuf = new StringBuffer();
+ m_newBuf = new StringBuffer();
+ }
+
+ private void updateState( Delta delta )
+ {
+ m_index++;
+
+ Chunk orig = delta.getOriginal();
+
+ if (orig.first() > m_firstElem)
+ {
+ // We "skip" some lines in the output.
+ // So flush out the last Change, if one exists.
+ flushChanges();
+
+ // Allow us to "skip" large swaths of unchanged text, show a "limited" amound of
+ // unchanged context so the changes are shown in
+ if ((orig.first() - m_firstElem) > 2 * m_unchangedContextLimit)
+ {
+ if (m_firstElem > 0)
+ {
+ int endIndex = Math.min( m_firstElem + m_unchangedContextLimit, m_origStrings.length -1 );
+
+ for (int j = m_firstElem; j < endIndex; j++)
+ m_sb.append(m_origStrings[j]);
+
+ m_sb.append(m_elidedTailIndicatorHtml);
+ }
+
+ m_sb.append(m_elidedHeadIndicatorHtml);
+
+ int startIndex = Math.max(orig.first() - m_unchangedContextLimit, 0);
+ for (int j = startIndex; j < orig.first(); j++)
+ m_sb.append(m_origStrings[j]);
+
+ }
+ else
+ {
+ // No need to skip anything, just output the whole range...
+ for (int j = m_firstElem; j < orig.first(); j++)
+ m_sb.append( m_origStrings[j] );
+ }
+ }
+ m_firstElem = orig.last() + 1;
+ }
+
+ public void visit( Revision rev )
+ {
+ // GNDN (Goes nowhere, does nothing)
+ }
+
+ public void visit( AddDelta delta )
+ {
+ updateState( delta );
+
+ // We have run Deletes up to now. Flush them out.
+ if( m_mode == 1 )
+ {
+ flushChanges();
+ m_mode = -1;
+ }
+ // We are in "neutral mode". Start a new Change
+ if( m_mode == -1 )
+ {
+ m_mode = 0;
+ }
+
+ // We are in "add mode".
+ if( m_mode == 0 || m_mode == 2 )
+ {
+ addNew( delta.getRevised() );
+ m_mode = 1;
+ }
+ }
+
+ public void visit( ChangeDelta delta )
+ {
+ updateState( delta );
+
+ // We are in "neutral mode". A Change might be merged with an add or delete.
+ if( m_mode == -1 )
+ {
+ m_mode = 2;
+ }
+
+ // Add the Changes to the buffers.
+ addOrig( delta.getOriginal() );
+ addNew( delta.getRevised() );
+ }
+
+ public void visit( DeleteDelta delta )
+ {
+ updateState( delta );
+
+ // We have run Adds up to now. Flush them out.
+ if( m_mode == 0 )
+ {
+ flushChanges();
+ m_mode = -1;
+ }
+ // We are in "neutral mode". Start a new Change
+ if( m_mode == -1 )
+ {
+ m_mode = 1;
+ }
+
+ // We are in "delete mode".
+ if( m_mode == 1 || m_mode == 2 )
+ {
+ addOrig( delta.getOriginal() );
+ m_mode = 1;
+ }
+ }
+
+ public void shutdown()
+ {
+ m_index = m_max + 1; // Make sure that no hyperlink gets created
+ flushChanges();
+
+ if (m_firstElem < m_origStrings.length)
+ {
+ // If there's more than the limit of the orginal left just emit limit and elided...
+ if ((m_origStrings.length - m_firstElem) > m_unchangedContextLimit)
+ {
+ int endIndex = Math.min( m_firstElem + m_unchangedContextLimit, m_origStrings.length -1 );
+
+ for (int j = m_firstElem; j < endIndex; j++)
+ m_sb.append( m_origStrings[j] );
+
+ m_sb.append(m_elidedTailIndicatorHtml);
+ }
+ else
+ // emit entire tail of original...
+ {
+ for (int j = m_firstElem; j < m_origStrings.length; j++)
+ m_sb.append(m_origStrings[j]);
+ }
+ }
+ }
+
+ private void addOrig( Chunk chunk )
+ {
+ if( chunk != null )
+ {
+ chunk.toString( m_origBuf );
+ }
+ }
+
+ private void addNew( Chunk chunk )
+ {
+ if( chunk != null )
+ {
+ chunk.toString( m_newBuf );
+ }
+ }
+
+
+
+ private void flushChanges()
+ {
+
+ if( m_newBuf.length() + m_origBuf.length() > 0 )
+ {
+ // This is the span element which encapsulates anchor and the change itself
+ m_sb.append( m_changeStartHtml );
+
+ // Do we want to have a "back link"?
+ if( m_emitChangeNextPreviousHyperlinks && m_count > 1 )
+ {
+ m_sb.append( m_backPreIndex );
+ m_sb.append( m_count - 1 );
+ m_sb.append( m_backPostIndex );
+ }
+
+ // An anchor for the change.
+ if (m_emitChangeNextPreviousHyperlinks)
+ {
+ m_sb.append( m_anchorPreIndex );
+ m_sb.append( m_count++ );
+ m_sb.append( m_anchorPostIndex );
+ }
+
+ // ... has been added
+ if( m_newBuf.length() > 0 )
+ {
+ m_sb.append( m_insertionStartHtml );
+ m_sb.append( m_newBuf );
+ m_sb.append( m_insertionEndHtml );
+ }
+
+ // .. has been removed
+ if( m_origBuf.length() > 0 )
+ {
+ m_sb.append( m_deletionStartHtml );
+ m_sb.append( m_origBuf );
+ m_sb.append( m_deletionEndHtml );
+ }
+
+ // Do we want a "forward" link?
+ if( m_emitChangeNextPreviousHyperlinks && (m_index < m_max) )
+ {
+ m_sb.append( m_forwardPreIndex );
+ m_sb.append( m_count ); // Has already been incremented.
+ m_sb.append( m_forwardPostIndex );
+ }
+
+ m_sb.append( m_changeEndHtml );
+
+ // Nuke the buffers.
+ m_origBuf = new StringBuffer();
+ m_newBuf = new StringBuffer();
+ }
+
+ // After a flush, everything is reset.
+ m_mode = -1;
+ }
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DiffProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DiffProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DiffProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DiffProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,76 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.diff;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiProvider;
+
+/**
+ * Provides an SPI for creating a diff between two page versions.
+ */
+public interface DiffProvider extends WikiProvider
+{
+ /**
+ * The return string is to be XHTML compliant ready to display html. No further
+ * processing of this text will be done by the wiki engine.
+ *
+ * @return An XHTML diff.
+ * @param context The Wiki Context
+ * @param oldWikiText the old text
+ * @param newWikiText the new text
+ */
+ public String makeDiffHtml(WikiContext context, String oldWikiText, String newWikiText);
+
+ /**
+ * If there is no diff provider set, this provider will work instead.
+ */
+ public static class NullDiffProvider implements DiffProvider
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public String makeDiffHtml(WikiContext ctx, String oldWikiText, String newWikiText)
+ {
+ return "You are using the NullDiffProvider, check your properties file.";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void initialize(WikiEngine engine, Properties properties)
+ throws NoRequiredPropertyException, IOException
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getProviderInfo()
+ {
+ return "NullDiffProvider";
+ }
+ }
+
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DifferenceManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DifferenceManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DifferenceManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/DifferenceManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,139 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package com.ecyrd.jspwiki.diff;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.util.ClassUtil;
+
+
+
+/**
+ * Load, initialize and delegate to the DiffProvider that will actually do the work.
+ *
+ * @author John Volkar
+ */
+public class DifferenceManager
+{
+ private static final Logger log = Logger.getLogger(DifferenceManager.class);
+
+ /** Property value for storing a diff provider. Value is {@value}. */
+ public static final String PROP_DIFF_PROVIDER = "jspwiki.diffProvider";
+
+
+ private DiffProvider m_provider;
+
+ /**
+ * Creates a new DifferenceManager for the given engine.
+ *
+ * @param engine The WikiEngine.
+ * @param props A set of properties.
+ */
+ public DifferenceManager(WikiEngine engine, Properties props)
+ {
+ loadProvider(props);
+
+ initializeProvider(engine, props);
+
+ log.info("Using difference provider: " + m_provider.getProviderInfo());
+ }
+
+ private void loadProvider(Properties props)
+ {
+ String providerClassName = props.getProperty( PROP_DIFF_PROVIDER,
+ TraditionalDiffProvider.class.getName() );
+
+ try
+ {
+ Class providerClass = ClassUtil.findClass( "com.ecyrd.jspwiki.diff", providerClassName );
+ m_provider = (DiffProvider)providerClass.newInstance();
+ }
+ catch( ClassNotFoundException e )
+ {
+ log.warn("Failed loading DiffProvider, will use NullDiffProvider.", e);
+ }
+ catch( InstantiationException e )
+ {
+ log.warn("Failed loading DiffProvider, will use NullDiffProvider.", e);
+ }
+ catch( IllegalAccessException e )
+ {
+ log.warn("Failed loading DiffProvider, will use NullDiffProvider.", e);
+ }
+
+ if( null == m_provider )
+ {
+ m_provider = new DiffProvider.NullDiffProvider();
+ }
+ }
+
+
+ private void initializeProvider(WikiEngine engine, Properties props)
+ {
+ try
+ {
+ m_provider.initialize( engine, props);
+ }
+ catch (NoRequiredPropertyException e1)
+ {
+ log.warn("Failed initializing DiffProvider, will use NullDiffProvider.", e1);
+ m_provider = new DiffProvider.NullDiffProvider(); //doesn't need init'd
+ }
+ catch (IOException e1)
+ {
+ log.warn("Failed initializing DiffProvider, will use NullDiffProvider.", e1);
+ m_provider = new DiffProvider.NullDiffProvider(); //doesn't need init'd
+ }
+ }
+
+ /**
+ * Returns valid XHTML string to be used in any way you please.
+ *
+ * @param context The Wiki Context
+ * @param firstWikiText The old text
+ * @param secondWikiText the new text
+ * @return XHTML, or empty string, if no difference detected.
+ */
+ public String makeDiff(WikiContext context, String firstWikiText, String secondWikiText)
+ {
+ String diff = null;
+ try
+ {
+ diff = m_provider.makeDiffHtml( context, firstWikiText, secondWikiText);
+
+ if( diff == null )
+ diff = "";
+ }
+ catch(Exception e)
+ {
+ diff = "Failed to create a diff, check the logs.";
+ log.warn( diff, e);
+ }
+ return diff;
+ }
+}
+
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ExternalDiffProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ExternalDiffProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ExternalDiffProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/ExternalDiffProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,199 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package com.ecyrd.jspwiki.diff;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+
+/**
+ * This DiffProvider allows external command line tools to be used to generate
+ * the diff.
+ */
+public class ExternalDiffProvider implements DiffProvider
+{
+ private static final Logger log = Logger.getLogger(ExternalDiffProvider.class);
+
+ /**
+ * Determines the command to be used for 'diff'. This program must be able
+ * to output diffs in the unified format. For example 'diff -u %s1 %s2'.
+ */
+ public static final String PROP_DIFFCOMMAND = "jspwiki.diffCommand";
+
+ private String m_diffCommand = null;
+ private String m_encoding;
+
+ private static final char DIFF_ADDED_SYMBOL = '+';
+ private static final char DIFF_REMOVED_SYMBOL = '-';
+
+ private static final String CSS_DIFF_ADDED = "<tr><td bgcolor=\"#99FF99\" class=\"diffadd\">";
+ private static final String CSS_DIFF_REMOVED = "<tr><td bgcolor=\"#FF9933\" class=\"diffrem\">";
+ private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">";
+ private static final String CSS_DIFF_CLOSE = "</td></tr>";
+
+ //FIXME This could/should be a property for this provider, there is not guarentee that
+ //the external program generates a format suitible for the colorization code of the
+ //TraditionalDiffProvider, currently set to true for legacy compatibility.
+ //I don't think this 'feature' ever worked right, did it?...
+ private boolean m_traditionalColorization = true;
+
+ /**
+ * Creates a new ExternalDiffProvider.
+ */
+ public ExternalDiffProvider()
+ {
+ }
+
+ /**
+ * @see com.ecyrd.jspwiki.WikiProvider#getProviderInfo()
+ * {@inheritDoc}
+ */
+ public String getProviderInfo()
+ {
+ return "ExternalDiffProvider";
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see com.ecyrd.jspwiki.WikiProvider#initialize(com.ecyrd.jspwiki.WikiEngine, java.util.Properties)
+ */
+ public void initialize( WikiEngine engine, Properties properties )
+ throws NoRequiredPropertyException, IOException
+ {
+ m_diffCommand = properties.getProperty( PROP_DIFFCOMMAND );
+
+ if( (null == m_diffCommand) || (m_diffCommand.trim().equals( "" )) )
+ {
+ throw new NoRequiredPropertyException( "ExternalDiffProvider missing required property", PROP_DIFFCOMMAND );
+ }
+
+ m_encoding = engine.getContentEncoding();
+ }
+
+
+ /**
+ * Makes the diff by calling "diff" program.
+ * {@inheritDoc}
+ */
+ public String makeDiffHtml( WikiContext ctx, String p1, String p2 )
+ {
+ File f1 = null;
+ File f2 = null;
+ String diff = null;
+
+ try
+ {
+ f1 = FileUtil.newTmpFile(p1, m_encoding);
+ f2 = FileUtil.newTmpFile(p2, m_encoding);
+
+ String cmd = TextUtil.replaceString(m_diffCommand, "%s1", f1.getPath());
+ cmd = TextUtil.replaceString(cmd, "%s2", f2.getPath());
+
+ String output = FileUtil.runSimpleCommand(cmd, f1.getParent());
+
+ // FIXME: Should this rely on the system default encoding?
+ String rawWikiDiff = new String(output.getBytes("ISO-8859-1"), m_encoding);
+
+ String htmlWikiDiff = TextUtil.replaceEntities( rawWikiDiff );
+
+ if (m_traditionalColorization) //FIXME, see comment near declaration...
+ diff = colorizeDiff(diff);
+ else
+ diff = htmlWikiDiff;
+
+ }
+ catch (IOException e)
+ {
+ log.error("Failed to do file diff", e);
+ }
+ catch (InterruptedException e)
+ {
+ log.error("Interrupted", e);
+ }
+ finally
+ {
+ if( f1 != null )
+ f1.delete();
+ if( f2 != null )
+ f2.delete();
+ }
+
+ return diff;
+ }
+
+
+ /**
+ * Goes through output provided by a diff command and inserts HTML tags to
+ * make the result more legible. Currently colors lines starting with a +
+ * green, those starting with - reddish (hm, got to think of color blindness
+ * here...).
+ */
+ static String colorizeDiff(String diffText) throws IOException
+ {
+ if( diffText == null )
+ return "Invalid diff - probably something wrong with server setup.";
+
+ String line = null;
+ String start = null;
+ String stop = null;
+
+ BufferedReader in = new BufferedReader( new StringReader( diffText ) );
+ StringBuffer out = new StringBuffer();
+
+ out.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
+ while( (line = in.readLine()) != null )
+ {
+ stop = CSS_DIFF_CLOSE;
+
+ if( line.length() > 0 )
+ {
+ switch( line.charAt( 0 ) )
+ {
+ case DIFF_ADDED_SYMBOL:
+ start = CSS_DIFF_ADDED;
+ break;
+ case DIFF_REMOVED_SYMBOL:
+ start = CSS_DIFF_REMOVED;
+ break;
+ default:
+ start = CSS_DIFF_UNCHANGED;
+ }
+ }
+ else
+ {
+ start = CSS_DIFF_UNCHANGED;
+ }
+
+ out.append( start );
+ out.append( line.trim() );
+ out.append( stop + "\n" );
+ }
+
+ out.append( "</table>\n" );
+ return out.toString();
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/TraditionalDiffProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/TraditionalDiffProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/TraditionalDiffProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/diff/TraditionalDiffProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,185 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package com.ecyrd.jspwiki.diff;
+
+import java.io.IOException;
+import java.text.ChoiceFormat;
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.util.Properties;
+import java.util.ResourceBundle;
+
+import org.apache.commons.jrcs.diff.*;
+import org.apache.commons.jrcs.diff.myers.MyersDiff;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.i18n.InternationalizationManager;
+
+
+/**
+ * This is the JSPWiki 'traditional' diff.
+ * @author Janne Jalkanen
+ * @author Erik Bunn
+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
+ */
+
+public class TraditionalDiffProvider implements DiffProvider
+{
+ private static final Logger log = Logger.getLogger(TraditionalDiffProvider.class);
+
+ private static final String CSS_DIFF_ADDED = "<tr><td class=\"diffadd\">";
+ private static final String CSS_DIFF_REMOVED = "<tr><td class=\"diffrem\">";
+ private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">";
+ private static final String CSS_DIFF_CLOSE = "</td></tr>" + Diff.NL;
+
+
+ public TraditionalDiffProvider()
+ {
+ }
+
+
+ /**
+ * @see com.ecyrd.jspwiki.WikiProvider#getProviderInfo()
+ */
+ public String getProviderInfo()
+ {
+ return "TraditionalDiffProvider";
+ }
+
+ /**
+ * @see com.ecyrd.jspwiki.WikiProvider#initialize(com.ecyrd.jspwiki.WikiEngine, java.util.Properties)
+ */
+ public void initialize(WikiEngine engine, Properties properties)
+ throws NoRequiredPropertyException, IOException
+ {
+ }
+
+ /**
+ * Makes a diff using the BMSI utility package. We use our own diff printer,
+ * which makes things easier.
+ */
+ public String makeDiffHtml( WikiContext ctx, String p1, String p2 )
+ {
+ String diffResult = "";
+
+ try
+ {
+ String[] first = Diff.stringToArray(TextUtil.replaceEntities(p1));
+ String[] second = Diff.stringToArray(TextUtil.replaceEntities(p2));
+ Revision rev = Diff.diff(first, second, new MyersDiff());
+
+ if( rev == null || rev.size() == 0 )
+ {
+ // No difference
+
+ return "";
+ }
+
+ StringBuffer ret = new StringBuffer(rev.size() * 20); // Guessing how big it will become...
+
+ ret.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
+ rev.accept( new RevisionPrint(ctx,ret) );
+ ret.append("</table>\n");
+
+ return ret.toString();
+ }
+ catch( DifferentiationFailedException e )
+ {
+ diffResult = "makeDiff failed with DifferentiationFailedException";
+ log.error(diffResult, e);
+ }
+
+ return diffResult;
+ }
+
+
+ public static final class RevisionPrint
+ implements RevisionVisitor
+ {
+ private StringBuffer m_result = null;
+ private WikiContext m_context;
+ private ResourceBundle m_rb;
+
+ private RevisionPrint(WikiContext ctx,StringBuffer sb)
+ {
+ m_result = sb;
+ m_context = ctx;
+ m_rb = ctx.getBundle( InternationalizationManager.CORE_BUNDLE );
+ }
+
+ public void visit(Revision rev)
+ {
+ // GNDN (Goes nowhere, does nothing)
+ }
+
+ public void visit(AddDelta delta)
+ {
+ Chunk changed = delta.getRevised();
+ print(changed, m_rb.getString( "diff.traditional.added" ) );
+ changed.toString(m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE);
+ }
+
+ public void visit(ChangeDelta delta)
+ {
+ Chunk changed = delta.getOriginal();
+ print(changed, m_rb.getString( "diff.traditional.changed") );
+ changed.toString(m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE);
+ delta.getRevised().toString(m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE);
+ }
+
+ public void visit(DeleteDelta delta)
+ {
+ Chunk changed = delta.getOriginal();
+ print(changed, m_rb.getString( "diff.traditional.removed") );
+ changed.toString(m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE);
+ }
+
+ private void print(Chunk changed, String type)
+ {
+ m_result.append(CSS_DIFF_UNCHANGED);
+
+ String[] choiceString =
+ {
+ m_rb.getString("diff.traditional.oneline"),
+ m_rb.getString("diff.traditional.lines")
+ };
+ double[] choiceLimits = { 1, 2 };
+
+ MessageFormat fmt = new MessageFormat("");
+ fmt.setLocale( m_context.getContext().getLocale() );
+ ChoiceFormat cfmt = new ChoiceFormat( choiceLimits, choiceString );
+ fmt.applyPattern( type );
+ Format[] formats = { NumberFormat.getInstance(), cfmt, NumberFormat.getInstance() };
+ fmt.setFormats( formats );
+
+ Object[] params = { new Integer(changed.first() + 1),
+ new Integer(changed.size()),
+ new Integer(changed.size()) };
+ m_result.append( fmt.format(params) );
+ m_result.append(CSS_DIFF_CLOSE);
+ }
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/PageEventFilter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/PageEventFilter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/PageEventFilter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/PageEventFilter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,166 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.ecyrd.jspwiki.event;
+
+import com.ecyrd.jspwiki.filters.BasicPageFilter;
+import com.ecyrd.jspwiki.filters.FilterException;
+
+import com.ecyrd.jspwiki.WikiContext;
+import java.util.Properties;
+
+/**
+ * Fires WikiPageEvents for page events.
+ * <p>
+ * Adding a PageEventFilter to the FilterManager will automatically
+ * attach an event delegate with the WikiEventManager to provide for
+ * firing and listener management. All that remains is then adding
+ * the listener to the filter via the WikiEventManager. This is quite
+ * simple:
+ * </p>
+ * <pre>
+ * PageEventFilter filter = new PageEventFilter();
+ * engine.getFilterManager().addPageFilter(filter,5000);
+ * // attach listener to filter
+ * WikiEventManager.addWikiEventListener(filter,listener);
+ * </pre>
+ * <p>
+ * This class provides convenience methods for adding and removing
+ * WikiEventListeners.
+ * </p>
+ *
+ * @see com.ecyrd.jspwiki.event.WikiEventManager
+ * @author Murray Altheim
+ */
+public class PageEventFilter extends BasicPageFilter
+{
+
+ /**
+ * Called whenever a new PageFilter is instantiated and reset.
+ */
+ public void initialize( Properties properties )
+ throws FilterException
+ {
+ //
+ }
+
+ /**
+ * This method is called whenever a page has been loaded from the provider,
+ * but not yet been sent through the TranslatorReader. Note that you cannot
+ * do HTML translation here, because TranslatorReader is likely to escape it.
+ *
+ * @param wikiContext The current wikicontext.
+ * @param content WikiMarkup.
+ */
+ public String preTranslate( WikiContext wikiContext, String content )
+ throws FilterException
+ {
+ fireEvent( WikiPageEvent.PRE_TRANSLATE, wikiContext );
+ return content;
+ }
+
+
+ /**
+ * This method is called after a page has been fed through the TranslatorReader,
+ * so anything you are seeing here is translated content. If you want to
+ * do any of your own WikiMarkup2HTML translation, do it here.
+ */
+ public String postTranslate( WikiContext wikiContext, String htmlContent )
+ throws FilterException
+ {
+ fireEvent( WikiPageEvent.POST_TRANSLATE, wikiContext );
+ return htmlContent;
+ }
+
+
+ /**
+ * This method is called before the page has been saved to the PageProvider.
+ */
+ public String preSave( WikiContext wikiContext, String content )
+ throws FilterException
+ {
+ fireEvent( WikiPageEvent.PRE_SAVE, wikiContext );
+ return content;
+ }
+
+
+ /**
+ * This method is called after the page has been successfully saved.
+ * If the saving fails for any reason, then this method will not
+ * be called.
+ * <p>
+ * Since the result is discarded from this method, this is only useful
+ * for things like counters, etc.
+ */
+ public void postSave( WikiContext wikiContext, String content )
+ throws FilterException
+ {
+ fireEvent( WikiPageEvent.POST_SAVE, wikiContext );
+ }
+
+
+ // events processing .......................................................
+
+
+ /**
+ * Registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ *
+ * @param listener the event listener
+ */
+ public final synchronized void addWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.addWikiEventListener( this, listener );
+ }
+
+ /**
+ * Un-registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ *
+ * @param listener the event listener
+ */
+ public final synchronized void removeWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.removeWikiEventListener( this, listener );
+ }
+
+ /**
+ * Fires a WikiPageEvent of the provided type and page name
+ * to all registered listeners. Only <tt>PAGE_LOCK</tt> and
+ * <tt>PAGE_UNLOCK</tt> event types will fire an event; other
+ * event types are ignored.
+ *
+ * @see com.ecyrd.jspwiki.event.WikiPageEvent
+ * @param type the WikiPageEvent type to be fired.
+ * @param context the WikiContext of the event.
+ */
+ protected final void fireEvent( int type, WikiContext context )
+ {
+ if ( WikiEventManager.isListening(this) && WikiPageEvent.isValidType(type) )
+ {
+ WikiPageEvent event = new WikiPageEvent(
+ context.getEngine(),
+ type,
+ context.getPage().getName());
+ WikiEventManager.fireEvent(this,event);
+ }
+ }
+
+} // end com.ecyrd.jspwiki.event.PageEventFilter
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEngineEvent.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEngineEvent.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEngineEvent.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEngineEvent.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,160 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.ecyrd.jspwiki.event;
+
+import com.ecyrd.jspwiki.WikiEngine;
+
+/**
+ * WikiEngineEvent indicates a change in the state of the WikiEngine.
+ *
+ * @author Murray Altheim
+ * @author Andrew Jaquith
+ * @see com.ecyrd.jspwiki.event.WikiEvent
+ * @since 2.4.20
+ */
+public class WikiEngineEvent extends WikiEvent
+{
+ private static final long serialVersionUID = 1829433967558773970L;
+
+ /** Indicates a WikiEngine initialization event, fired as the
+ * wiki service is being initialized (in progress). */
+ public static final int INITIALIZING = -1;
+
+ /** Indicates a WikiEngine initialized event, fired after the
+ * wiki service is fully available. */
+ public static final int INITIALIZED = 0;
+
+ /** Indicates a WikiEngine closing event, fired as a signal that
+ * the wiki service is shutting down. */
+ public static final int SHUTDOWN = 1;
+
+ /** Indicates a WikiEngine stopped event, fired after halting the wiki service.
+ * A WikiEngine in this state is not expected to provide further services.
+ */
+ public static final int STOPPED = 2;
+
+ private WikiEngine m_engine;
+
+ // ............
+
+
+ /**
+ * Constructs an instance of this event.
+ * @param eventSource the Object that is the source of the event,
+ * which <b>must</b> be the WikiEngine. If it is not, this
+ * method thows a ClassCastException
+ * @param type the event type
+ */
+ public WikiEngineEvent( Object eventSource, int type )
+ {
+ super( eventSource, type );
+ m_engine = (WikiEngine)eventSource;
+ }
+
+
+ /**
+ * Sets the type of this event.
+ *
+ * @param type the type of this WikiEngineEvent.
+ */
+ protected void setType( int type )
+ {
+ if ( type >= INITIALIZING && type <= STOPPED )
+ {
+ super.setType(type);
+ }
+ else
+ {
+ super.setType(ERROR);
+ }
+ }
+
+
+ /**
+ * Returns the WikiEngine that spawned this event.
+ *
+ * @return the WikiEngine that spawned this event.
+ */
+ public WikiEngine getEngine()
+ {
+ return m_engine;
+ }
+
+
+ /**
+ * Returns the WikiEngine that spawned this event.
+ *
+ * @return the WikiEngine that spawned this event.
+ * @deprecated use {@link #getEngine()} instead.
+ */
+ public WikiEngine getWikiEngine()
+ {
+ return m_engine;
+ }
+
+
+ /**
+ * Returns <code>true</code> if the int value is a WikiPageEvent type.
+ * @param type the event type
+ * @return the result
+ */
+ public static boolean isValidType( int type )
+ {
+ return type >= INITIALIZING && type <= STOPPED;
+ }
+
+
+ /**
+ * Returns a textual representation of the event type.
+ *
+ * @return a String representation of the type
+ */
+ public final String eventName()
+ {
+ switch ( getType() )
+ {
+ case INITIALIZING: return "INITIALIZING";
+ case INITIALIZED: return "INITIALIZED";
+ case SHUTDOWN: return "SHUTDOWN";
+ case STOPPED: return "STOPPED";
+ default: return super.eventName();
+ }
+ }
+
+
+ /**
+ * Returns a human-readable description of the event type.
+ *
+ * @return a String description of the type
+ */
+ public final String getTypeDescription()
+ {
+ switch ( getType() )
+ {
+ case INITIALIZING: return "wiki engine initializing";
+ case INITIALIZED: return "wiki engine initialized";
+ case SHUTDOWN: return "wiki engine shutting down";
+ case STOPPED: return "wiki engine stopped";
+ default: return super.getTypeDescription();
+ }
+ }
+
+} // end class com.ecyrd.jspwiki.event.WikiEngineEvent
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEvent.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEvent.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEvent.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEvent.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,157 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.ecyrd.jspwiki.event;
+
+import java.util.EventObject;
+
+/**
+ * Abstract parent class for wiki events.
+ *
+ * @author Murray Altheim
+ * @author Andrew Jaquith
+ * @since 2.3.79
+ */
+public abstract class WikiEvent extends EventObject
+{
+ private static final long serialVersionUID = 1829433967558773960L;
+
+ /** Indicates a exception or error state. */
+ public static final int ERROR = -99;
+
+ /** Indicates an undefined state. */
+ public static final int UNDEFINED = -98;
+
+ private int m_type = UNDEFINED;
+
+ private final long m_when;
+
+ // ............
+
+
+ /**
+ * Constructs an instance of this event.
+ * @param src the Object that is the source of the event.
+ * @param type the event type.
+ */
+ public WikiEvent( Object src, int type )
+ {
+ super( src );
+ m_when = System.currentTimeMillis();
+ setType( type );
+ }
+
+
+ /**
+ * Returns the timestamp of when this WikiEvent occurred.
+ *
+ * @return this event's timestamp
+ * @since 2.4.74
+ */
+ public long getWhen()
+ {
+ return m_when;
+ }
+
+
+ /**
+ * Sets the type of this event. Validation of acceptable
+ * type values is the responsibility of each subclass.
+ *
+ * @param type the type of this WikiEvent.
+ */
+ protected void setType( int type )
+ {
+ m_type = type;
+ }
+
+
+ /**
+ * Returns the type of this event.
+ *
+ * @return the type of this WikiEvent. See the enumerated values
+ * defined in {@link com.ecyrd.jspwiki.event.WikiEvent}).
+ */
+ public int getType()
+ {
+ return m_type;
+ }
+
+
+ /** Returns a String (human-readable) description of an event type.
+ * This should be subclassed as necessary.
+ * @return the String description
+ */
+ public String getTypeDescription()
+ {
+ switch ( m_type )
+ {
+ case ERROR: return "exception or error event";
+ case UNDEFINED: return "undefined event type";
+ default: return "unknown event type (" + m_type + ")";
+ }
+ }
+
+
+ /**
+ * Returns true if the int value is a valid WikiEvent type.
+ * Because the WikiEvent class does not itself any event types,
+ * this method returns true if the event type is anything except
+ * {@link #ERROR} or {@link #UNDEFINED}. This method is meant to
+ * be subclassed as appropriate.
+ */
+ public static boolean isValidType( int type )
+ {
+ return type != ERROR && type != UNDEFINED;
+ }
+
+
+ /**
+ * Returns a textual representation of an event type.
+ * @return the String representation
+ */
+ public String eventName()
+ {
+ switch( m_type )
+ {
+ case ERROR: return "ERROR";
+ case UNDEFINED: return "UNDEFINED";
+ default: return "UNKNOWN (" + m_type + ")";
+ }
+ }
+
+ /**
+ * Prints a String (human-readable) representation of this object.
+ * This should be subclassed as necessary.
+ * @see java.lang.Object#toString()
+ * @return the String representation
+ */
+ public String toString()
+ {
+ StringBuffer out = new StringBuffer();
+ out.append( "WikiEvent." );
+ out.append( eventName() );
+ out.append( " [source=" );
+ out.append( getSource().toString() );
+ out.append( "]" );
+ return out.toString();
+ }
+
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventListener.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventListener.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventListener.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventListener.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,42 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.ecyrd.jspwiki.event;
+
+import java.util.EventListener;
+
+/**
+ * Defines an interface for an object that listens for WikiEvents.
+ *
+ * @author Murray Altheim
+ * @since 2.3.92
+ */
+public interface WikiEventListener extends EventListener
+{
+
+ /**
+ * Fired when a WikiEvent is triggered by an event source.
+ *
+ * @param event a WikiEvent object
+ */
+ public void actionPerformed( WikiEvent event );
+
+
+} // end com.ecryd.jspwiki.event.WikiEventListener
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/event/WikiEventManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,618 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.ecyrd.jspwiki.event;
+
+import java.lang.ref.WeakReference;
+import java.util.*;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A singleton class that manages the addition and removal of WikiEvent
+ * listeners to a event source, as well as the firing of events to those
+ * listeners. An "event source" is the object delegating its event
+ * handling to an inner delegating class supplied by this manager. The
+ * class being serviced is considered a "client" of the delegate. The
+ * WikiEventManager operates across any number of simultaneously-existing
+ * WikiEngines since it manages all delegation on a per-object basis.
+ * Anything that might fire a WikiEvent (or any of its subclasses) can be
+ * a client.
+ * </p>
+ *
+ * <h3>Using a Delegate for Event Listener Management</h3>
+ * <p>
+ * Basically, rather than have all manner of client classes maintain their
+ * own listener lists, add and remove listener methods, any class wanting
+ * to attach listeners can simply request a delegate object to provide that
+ * service. The delegate handles the listener list, the add and remove
+ * listener methods. Firing events is then a matter of calling the
+ * WikiEventManager's {@link #fireEvent(Object,WikiEvent)} method, where
+ * the Object is the client. Prior to instantiating the event object, the
+ * client can call {@link #isListening(Object)} to see there are any
+ * listeners attached to its delegate.
+ * </p>
+ *
+ * <h3>Adding Listeners</h3>
+ * <p>
+ * Adding a WikiEventListener to an object is very simple:
+ * </p>
+ * <pre>
+ * WikiEventManager.addWikiEventListener(object,listener);
+ * </pre>
+ *
+ * <h3>Removing Listeners</h3>
+ * <p>
+ * Removing a WikiEventListener from an object is very simple:
+ * </p>
+ * <pre>
+ * WikiEventManager.removeWikiEventListener(object,listener);
+ * </pre>
+ * If you only have a reference to the listener, the following method
+ * will remove it from any clients managed by the WikiEventManager:
+ * <pre>
+ * WikiEventManager.removeWikiEventListener(listener);
+ * </pre>
+ *
+ * <h3>Backward Compatibility: Replacing Existing <tt>fireEvent()</tt> Methods</h3>
+ * <p>
+ * Using one manager for all events processing permits consolidation of all event
+ * listeners and their associated methods in one place rather than having them
+ * attached to specific subcomponents of an application, and avoids a great deal
+ * of event-related cut-and-paste code. Convenience methods that call the
+ * WikiEventManager for event delegation can be written to maintain existing APIs.
+ * </p>
+ * <p>
+ * For example, an existing <tt>fireEvent()</tt> method might look something like
+ * this:
+ * </p>
+ * <pre>
+ * protected final void fireEvent( WikiEvent event )
+ * {
+ * for ( Iterator it = m_listeners.iterator(); it.hasNext(); )
+ * {
+ * WikiEventListener listener = (WikiEventListener)it.next();
+ * listener.actionPerformed(event);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * One disadvantage is that the above method is supplied with event objects,
+ * which are created even when no listener exists for them. In a busy wiki
+ * with many users unused/unnecessary event creation could be considerable.
+ * Another advantage is that in addition to the iterator, there must be code
+ * to support the addition and remove of listeners. The above could be
+ * replaced with the below code (and with no necessary local support for
+ * adding and removing listeners):
+ * </p>
+ * <pre>
+ * protected final void fireEvent( int type )
+ * {
+ * if ( WikiEventManager.isListening(this) )
+ * {
+ * WikiEventManager.fireEvent(this,new WikiEngineEvent(this,type));
+ * }
+ * }
+ * </pre>
+ * <p>
+ * This only needs to be customized to supply the specific parameters for
+ * whatever WikiEvent you want to create.
+ * </p>
+ *
+ * <h3 id="preloading">Preloading Listeners</h3>
+ * <p>
+ * This may be used to create listeners for objects that don't yet exist,
+ * particularly designed for embedded applications that need to be able
+ * to listen for the instantiation of an Object, by maintaining a cache
+ * of client-less WikiEvent sources that set their client upon being
+ * popped from the cache. Each time any of the methods expecting a client
+ * parameter is called with a null parameter it will preload an internal
+ * cache with a client-less delegate object that will be popped and
+ * returned in preference to creating a new object. This can have unwanted
+ * side effects if there are multiple clients populating the cache with
+ * listeners. The only check is for a Class match, so be aware if others
+ * might be populating the client-less cache with listeners.
+ * </p>
+ * <h3>Listener lifecycle</h3>
+ * <p>
+ * Note that in most cases it is not necessary to remove a listener.
+ * As of 2.4.97, the listeners are stored as WeakReferences, and will be
+ * automatically cleaned at the next garbage collection, if you no longer
+ * hold a reference to them. Of course, until the garbage is collected,
+ * your object might still be getting events, so if you wish to avoid that,
+ * please remove it explicitly as described above.
+ * </p>
+ * @author Murray Altheim
+ * @since 2.4.20
+ */
+public final class WikiEventManager
+{
+ private static final Logger log = Logger.getLogger(WikiEventManager.class);
+
+ /* If true, permits a WikiEventMonitor to be set. */
+ private static boolean c_permitMonitor = false;
+
+ /* Optional listener to be used as all-event monitor. */
+ private static WikiEventListener c_monitor = null;
+
+ /* The Map of client object to WikiEventDelegate. */
+ private final Map m_delegates = new HashMap();
+
+ /* The Vector containing any preloaded WikiEventDelegates. */
+ private final Vector m_preloadCache = new Vector();
+
+ /* Singleton instance of the WikiEventManager. */
+ private static WikiEventManager c_instance = null;
+
+ // ............
+
+ /**
+ * Constructor for a WikiEventManager.
+ */
+ private WikiEventManager()
+ {
+ c_instance = this;
+ log.debug("instantiated WikiEventManager");
+ }
+
+ /**
+ * As this is a singleton class, this returns the single
+ * instance of this class provided with the property file
+ * filename and bit-wise application settings.
+ *
+ * @return A shared instance of the WikiEventManager
+ */
+ public static WikiEventManager getInstance()
+ {
+ if( c_instance == null )
+ {
+ synchronized( WikiEventManager.class )
+ {
+ WikiEventManager mgr = new WikiEventManager();
+ // start up any post-instantiation services here
+ return mgr;
+ }
+ }
+ return c_instance;
+ }
+
+
+ // public/API methods ......................................................
+
+
+ /**
+ * Registers a WikiEventListener with a WikiEventDelegate for
+ * the provided client object.
+ *
+ * <h3>Monitor Listener</h3>
+ *
+ * If <tt>client</tt> is a reference to the WikiEventManager class
+ * itself and the compile-time flag {@link #c_permitMonitor} is true,
+ * this attaches the listener as an all-event monitor, overwriting
+ * any previous listener (hence returning true).
+ * <p>
+ * You can remove any existing monitor by either calling this method
+ * with <tt>client</tt> as a reference to this class and the
+ * <tt>listener</tt> parameter as null, or
+ * {@link #removeWikiEventListener(Object,WikiEventListener)} with a
+ * <tt>client</tt> as a reference to this class. The <tt>listener</tt>
+ * parameter in this case is ignored.
+ *
+ * @param client the client of the event source
+ * @param listener the event listener
+ * @return true if the listener was added (i.e., it was not already in the list and was added)
+ */
+ public static final boolean addWikiEventListener(
+ Object client, WikiEventListener listener )
+ {
+ if ( client == WikiEventManager.class )
+ {
+ if ( c_permitMonitor ) c_monitor = listener;
+ return c_permitMonitor;
+ }
+ WikiEventDelegate delegate = getInstance().getDelegateFor(client);
+ return delegate.addWikiEventListener(listener);
+ }
+
+
+ /**
+ * Un-registers a WikiEventListener with the WikiEventDelegate for
+ * the provided client object.
+ *
+ * @param client the client of the event source
+ * @param listener the event listener
+ * @return true if the listener was found and removed.
+ */
+ public static final boolean removeWikiEventListener(
+ Object client, WikiEventListener listener )
+ {
+ if ( client == WikiEventManager.class )
+ {
+ c_monitor = null;
+ return true;
+ }
+ WikiEventDelegate delegate = getInstance().getDelegateFor(client);
+ return delegate.removeWikiEventListener(listener);
+ }
+
+
+ /**
+ * Return the Set containing the WikiEventListeners attached to the
+ * delegate for the supplied client. If there are no attached listeners,
+ * returns an empty Iterator rather than null. Note that this will
+ * create a delegate for the client if it did not exist prior to the call.
+ *
+ * <h3>NOTE</h3>
+ * <p>
+ * This method returns a Set rather than an Iterator because of the high
+ * likelihood of the Set being modified while an Iterator might be active.
+ * This returns an unmodifiable reference to the actual Set containing
+ * the delegate's listeners. Any attempt to modify the Set will throw an
+ * {@link java.lang.UnsupportedOperationException}. This method is not
+ * synchronized and it should be understood that the composition of the
+ * backing Set may change at any time.
+ * </p>
+ *
+ * @param client the client of the event source
+ * @return an unmodifiable Set containing the WikiEventListeners attached to the client
+ * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set
+ */
+ public static final Set getWikiEventListeners( Object client )
+ throws UnsupportedOperationException
+ {
+ WikiEventDelegate delegate = getInstance().getDelegateFor(client);
+ return delegate.getWikiEventListeners();
+ }
+
+
+ /**
+ * Un-registers a WikiEventListener from any WikiEventDelegate client managed by this
+ * WikiEventManager. Since this is a one-to-one relation, the first match will be
+ * returned upon removal; a true return value indicates the WikiEventListener was
+ * found and removed.
+ *
+ * @param listener the event listener
+ * @return true if the listener was found and removed.
+ */
+ public static final boolean removeWikiEventListener( WikiEventListener listener )
+ {
+ // get the Map.entry object for the entire Map, then check match on entry (listener)
+ WikiEventManager mgr = getInstance();
+ Map sources = mgr.getDelegates();
+ synchronized( sources )
+ {
+ // get an iterator over the Map.Enty objects in the map
+ Iterator it = sources.entrySet().iterator();
+ while( it.hasNext() )
+ {
+ Map.Entry entry = (Map.Entry)it.next();
+ // the entry value is the delegate
+ WikiEventDelegate delegate = (WikiEventDelegate)entry.getValue();
+
+ // now see if we can remove the listener from the delegate
+ // (delegate may be null because this is a weak reference)
+ if( delegate != null && delegate.removeWikiEventListener(listener) )
+ {
+ return true; // was removed
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns true if there are one or more listeners registered with
+ * the provided client Object (undelegated event source). This locates
+ * any delegate and checks to see if it has any listeners attached.
+ *
+ * @param client the client Object
+ * @return True, if there is a listener for this client object.
+ */
+ public static boolean isListening( Object client )
+ {
+ WikiEventDelegate source = getInstance().getDelegateFor(client);
+ return source != null ? source.isListening() : false ;
+ }
+
+
+ /**
+ * Notify all listeners of the WikiEventDelegate having a registered
+ * interest in change events of the supplied WikiEvent.
+ *
+ * @param client the client initiating the event.
+ * @param event the WikiEvent to fire.
+ */
+ public static void fireEvent( Object client, WikiEvent event )
+ {
+ WikiEventDelegate source = getInstance().getDelegateFor(client);
+ if ( source != null ) source.fireEvent(event);
+ if ( c_monitor != null ) c_monitor.actionPerformed(event);
+ }
+
+
+ // private and utility methods .............................................
+
+
+ /**
+ * Return the client-to-delegate Map.
+ */
+ private Map getDelegates()
+ {
+ return m_delegates;
+ }
+
+
+ /**
+ * Returns a WikiEventDelegate for the provided client Object.
+ * If the parameter is a class reference, will generate and return a
+ * client-less WikiEventDelegate. If the parameter is not a Class and
+ * the delegate cache contains any objects matching the Class of any
+ * delegates in the cache, the first Class-matching delegate will be
+ * used in preference to creating a new delegate.
+ * If a null parameter is supplied, this will create a client-less
+ * delegate that will attach to the first incoming client (i.e.,
+ * there will be no Class-matching requirement).
+ *
+ * @param client the client Object, or alternately a Class reference
+ * @return the WikiEventDelegate.
+ */
+ private WikiEventDelegate getDelegateFor( Object client )
+ {
+ synchronized( m_delegates )
+ {
+ if( client == null || client instanceof Class ) // then preload the cache
+ {
+ WikiEventDelegate delegate = new WikiEventDelegate(client);
+ m_preloadCache.add(delegate);
+ m_delegates.put( client, delegate );
+ return delegate;
+ }
+ else if( !m_preloadCache.isEmpty() )
+ {
+ // then see if any of the cached delegates match the class of the incoming client
+ for( int i = m_preloadCache.size()-1 ; i >= 0 ; i-- ) // start with most-recently added
+ {
+ WikiEventDelegate delegate = (WikiEventDelegate)m_preloadCache.elementAt(i);
+ if( delegate.getClientClass() == null
+ || delegate.getClientClass().equals(client.getClass()) )
+ {
+ // we have a hit, so use it, but only on a client we haven't seen before
+ if( !m_delegates.keySet().contains(client) )
+ {
+ m_preloadCache.remove(delegate);
+ m_delegates.put( client, delegate );
+ return delegate;
+ }
+ }
+ }
+ }
+ // otherwise treat normally...
+ WikiEventDelegate delegate = (WikiEventDelegate)m_delegates.get( client );
+ if( delegate == null )
+ {
+ delegate = new WikiEventDelegate( client );
+ m_delegates.put( client, delegate );
+ }
+ return delegate;
+ }
+ }
+
+
+ // .........................................................................
+
+
+ /**
+ * Inner delegating class that manages event listener addition and
+ * removal. Classes that generate events can obtain an instance of
+ * this class from the WikiEventManager and delegate responsibility
+ * to it. Interaction with this delegating class is done via the
+ * methods of the {@link WikiEventDelegate} API.
+ *
+ * @author Murray Altheim
+ * @since 2.4.20
+ */
+ private static final class WikiEventDelegate
+ {
+ /* A list of event listeners for this instance. */
+
+ private ArrayList m_listenerList = new ArrayList();
+
+ private Class m_class = null;
+
+ /**
+ * Constructor for an WikiEventDelegateImpl, provided
+ * with the client Object it will service, or the Class
+ * of client, the latter when used to preload a future
+ * incoming delegate.
+ */
+ protected WikiEventDelegate( Object client )
+ {
+ if( client instanceof Class )
+ {
+ m_class = (Class)client;
+ }
+ }
+
+ /**
+ * Returns the class of the client-less delegate, null if
+ * this delegate is attached to a client Object.
+ */
+ protected Class getClientClass()
+ {
+ return m_class;
+ }
+
+
+ /**
+ * Return an unmodifiable Set containing the WikiEventListeners of
+ * this WikiEventDelegateImpl. If there are no attached listeners,
+ * returns an empty Set rather than null.
+ *
+ * @return an unmodifiable Set containing this delegate's WikiEventListeners
+ * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set
+ */
+ public Set getWikiEventListeners()
+ {
+ synchronized( m_listenerList )
+ {
+ TreeSet set = new TreeSet( new WikiEventListenerComparator() );
+
+ for( Iterator i = m_listenerList.iterator(); i.hasNext(); )
+ {
+ WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get();
+
+ if( l != null )
+ {
+ set.add( l );
+ }
+ }
+
+ return Collections.unmodifiableSet(set);
+ }
+ }
+
+
+ /**
+ * Adds <tt>listener</tt> as a listener for events fired by the WikiEventDelegate.
+ *
+ * @param listener the WikiEventListener to be added
+ * @return true if the listener was added (i.e., it was not already in the list and was added)
+ */
+ public boolean addWikiEventListener( WikiEventListener listener )
+ {
+ synchronized( m_listenerList )
+ {
+ return m_listenerList.add( new WeakReference(listener) );
+ }
+ }
+
+
+ /**
+ * Removes <tt>listener</tt> from the WikiEventDelegate.
+ *
+ * @param listener the WikiEventListener to be removed
+ * @return true if the listener was removed (i.e., it was actually in the list and was removed)
+ */
+ public boolean removeWikiEventListener( WikiEventListener listener )
+ {
+ synchronized( m_listenerList )
+ {
+ for( Iterator i = m_listenerList.iterator(); i.hasNext(); )
+ {
+ WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get();
+
+ if( l == listener )
+ {
+ i.remove();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Returns true if there are one or more listeners registered
+ * with this instance.
+ */
+ public boolean isListening()
+ {
+ synchronized( m_listenerList )
+ {
+ return !m_listenerList.isEmpty();
+ }
+ }
+
+
+ /**
+ * Notify all listeners having a registered interest
+ * in change events of the supplied WikiEvent.
+ */
+ public void fireEvent( WikiEvent event )
+ {
+ boolean needsCleanup = false;
+
+ try
+ {
+ synchronized( m_listenerList )
+ {
+ for( int i = 0; i < m_listenerList.size(); i++ )
+ {
+ WikiEventListener listener = (WikiEventListener) ((WeakReference)m_listenerList.get(i)).get();
+
+ if( listener != null )
+ {
+ listener.actionPerformed( event );
+ }
+ else
+ {
+ needsCleanup = true;
+ }
+ }
+
+ //
+ // Remove all such listeners which have expired
+ //
+ if( needsCleanup )
+ {
+ for( int i = 0; i < m_listenerList.size(); i++ )
+ {
+ WeakReference w = (WeakReference)m_listenerList.get(i);
+
+ if( w.get() == null ) m_listenerList.remove(i--);
+ }
+ }
+
+ }
+ }
+ catch( ConcurrentModificationException e )
+ {
+ //
+ // We don't die, we just don't do notifications in that case.
+ //
+ log.info("Concurrent modification of event list; please report this.",e);
+ }
+
+ }
+
+ } // end inner class WikiEventDelegate
+
+ private static class WikiEventListenerComparator implements Comparator
+ {
+ // TODO: This method is a critical performance bottleneck
+ public int compare(Object arg0, Object arg1)
+ {
+ if( arg0 instanceof WikiEventListener && arg1 instanceof WikiEventListener )
+ {
+ WikiEventListener w0 = (WikiEventListener) arg0;
+ WikiEventListener w1 = (WikiEventListener) arg1;
+
+ if( w1 == w0 || w0.equals(w1) ) return 0;
+
+ return w1.hashCode() - w0.hashCode();
+ }
+
+ throw new ClassCastException( arg1.getClass().getName() + " != " + arg0.getClass().getName() );
+ }
+ }
+} // end com.ecyrd.jspwiki.event.WikiEventManager