📄 referencemanager.java
字号:
/* JSPWiki - a JSP-based WikiWiki clone. Copyright (C) 2001-2004 Janne Jalkanen (Janne.Jalkanen@iki.fi), Erik Bunn (ebu@memecry.net) 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;import java.util.*;import java.io.*;import org.apache.log4j.*;import com.ecyrd.jspwiki.filters.BasicPageFilter;import com.ecyrd.jspwiki.attachment.*;import com.ecyrd.jspwiki.providers.*;/* BUGS - if a wikilink is added to a page, then removed, RefMan still thinks that the page refers to the wikilink page. Hm. - if a page is deleted, gets very confused. - Serialization causes page attributes to be missing, when InitializablePlugins are not executed properly. Thus, serialization should really also mark whether a page is serializable or not... *//* A word about synchronizing: I expect this object to be accessed in three situations: - when a WikiEngine is created and it scans its wikipages - when the WE saves a page - when a JSP page accesses one of the WE's ReferenceManagers to display a list of (un)referenced pages. So, access to this class is fairly rare, and usually triggered by user interaction. OTOH, the methods in this class use their storage objects intensively (and, sorry to say, in an unoptimized manner =). My deduction: using unsynchronized HashMaps etc and syncing methods or code blocks is preferrable to using slow, synced storage objects. We don't have iterative code here, so I'm going to use synced methods for now. Please contact me if you notice problems with ReferenceManager, and especially with synchronization, or if you have suggestions about syncing. ebu@memecry.net*//** * Keeps track of wikipage references: * <UL> * <LI>What pages a given page refers to * <LI>What pages refer to a given page * </UL> * * This is a quick'n'dirty approach without any finesse in storage and * searching algorithms; we trust java.util.*. * <P> * This class contains two HashMaps, m_refersTo and m_referredBy. The * first is indexed by WikiPage names and contains a Collection of all * WikiPages the page refers to. (Multiple references are not counted, * naturally.) The second is indexed by WikiPage names and contains * a Set of all pages that refer to the indexing page. (Notice - * the keys of both Maps should be kept in sync.) * <P> * When a page is added or edited, its references are parsed, a Collection * is received, and we crudely replace anything previous with this new * Collection. We then check each referenced page name and make sure they * know they are referred to by the new page. * <P> * Based on this information, we can perform non-optimal searches for * e.g. unreferenced pages, top ten lists, etc. * <P> * The owning class must take responsibility of filling in any pre-existing * information, probably by loading each and every WikiPage and calling this * class to update the references when created. * * @author ebu@memecry.net * @since 1.6.1 */public class ReferenceManager extends BasicPageFilter{ /** Maps page wikiname to a Collection of pages it refers to. The Collection * must contain Strings. The Collection may contain names of non-existing * pages. */ private Map m_refersTo; private Map m_unmutableRefersTo; /** Maps page wikiname to a Set of referring pages. The Set must * contain Strings. Non-existing pages (a reference exists, but not a file * for the page contents) may have an empty Set in m_referredBy. */ private Map m_referredBy; private Map m_unmutableReferredBy; /** The WikiEngine that owns this object. */ private WikiEngine m_engine; private boolean m_matchEnglishPlurals = false; private static Logger log = Logger.getLogger(ReferenceManager.class); private static final String SERIALIZATION_FILE = "refmgr.ser"; /** We use this also a generic serialization id */ private static final long serialVersionUID = 1L; /** * Builds a new ReferenceManager. * * @param engine The WikiEngine to which this is managing references to. */ public ReferenceManager( WikiEngine engine ) { m_refersTo = new HashMap(); m_referredBy = new HashMap(); m_engine = engine; m_matchEnglishPlurals = TextUtil.getBooleanProperty( engine.getWikiProperties(), WikiEngine.PROP_MATCHPLURALS, m_matchEnglishPlurals ); // // Create two maps that contain unmutable versions of the two basic maps. // m_unmutableReferredBy = Collections.unmodifiableMap( m_referredBy ); m_unmutableRefersTo = Collections.unmodifiableMap( m_refersTo ); } /** * Does a full reference update. */ private void updatePageReferences( WikiPage page ) throws ProviderException { String content = m_engine.getPageManager().getPageText( page.getName(), WikiPageProvider.LATEST_VERSION ); TreeSet res = new TreeSet(); Collection links = m_engine.scanWikiLinks( page, content ); res.addAll( links ); Collection attachments = m_engine.getAttachmentManager().listAttachments( page ); for( Iterator atti = attachments.iterator(); atti.hasNext(); ) { res.add( ((Attachment)(atti.next())).getName() ); } updateReferences( page.getName(), res ); } /** * Initializes the entire reference manager with the initial set of pages * from the collection. * * @param pages A collection of all pages you want to be included in the reference * count. * @since 2.2 */ public void initialize( Collection pages ) throws ProviderException { log.debug( "Initializing new ReferenceManager with "+pages.size()+" initial pages." ); long start = System.currentTimeMillis(); log.info( "Starting cross reference scan of WikiPages" ); // // First, try to serialize old data from disk. If that fails, // we'll go and update the entire reference lists (which'll take // time) // try { long saved = unserializeFromDisk(); // // Now we must check if any of the pages have been changed // while we were in the electronic la-la-land, and update // the references for them. // Iterator it = pages.iterator(); while( it.hasNext() ) { WikiPage page = (WikiPage) it.next(); if( page instanceof Attachment ) { // Skip attachments } else { // Refresh with the latest copy page = m_engine.getPage( page.getName() ); if( page.getLastModified() == null ) { log.fatal( "Provider returns null lastModified. Please submit a bug report." ); } else if( page.getLastModified().getTime() > saved ) { updatePageReferences( page ); } } } } catch( Exception e ) { log.info("Unable to unserialize old refmgr information, rebuilding database: "+e.getMessage()); buildKeyLists( pages ); // Scan the existing pages from disk and update references in the manager. Iterator it = pages.iterator(); while( it.hasNext() ) { WikiPage page = (WikiPage)it.next(); if( page instanceof Attachment ) { // We cannot build a reference list from the contents // of attachments, so we skip them. } else { updatePageReferences( page ); } } serializeToDisk(); } log.info( "Cross reference scan done (" + (System.currentTimeMillis()-start) + " ms)" ); } /** * Reads the serialized data from the disk back to memory. * Returns the date when the data was last written on disk */ private synchronized long unserializeFromDisk() throws IOException, ClassNotFoundException { ObjectInputStream in = null; long saved = 0L; try { long start = System.currentTimeMillis(); File f = new File( m_engine.getWorkDir(), SERIALIZATION_FILE ); in = new ObjectInputStream( new BufferedInputStream(new FileInputStream(f)) ); long ver = in.readLong(); if( ver != serialVersionUID ) { throw new IOException("File format has changed; I need to recalculate references."); } saved = in.readLong(); m_refersTo = (Map) in.readObject(); m_referredBy = (Map) in.readObject(); in.close(); long finish = System.currentTimeMillis(); log.debug("Read serialized data successfully in "+(finish-start)+"ms"); } finally { try { if( in != null ) in.close(); } catch( IOException ex ) {} } return saved; } /** * Serializes hashmaps to disk. The format is private, don't touch it. */ private synchronized void serializeToDisk() { ObjectOutputStream out = null; try { long start = System.currentTimeMillis(); File f = new File( m_engine.getWorkDir(), SERIALIZATION_FILE ); out = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream(f)) ); out.writeLong( serialVersionUID ); out.writeLong( System.currentTimeMillis() ); // Timestamp out.writeObject( m_refersTo ); out.writeObject( m_referredBy ); out.close(); long finish = System.currentTimeMillis(); log.debug("serialization done - took "+(finish-start)+"ms"); } catch( IOException e ) { log.error("Unable to serialize!"); try { if( out != null ) out.close(); } catch( IOException ex ) {} } } /** * After the page has been saved, updates the reference lists. */ public void postSave( WikiContext context, String content ) { WikiPage page = context.getPage(); updateReferences( page.getName(), context.getEngine().scanWikiLinks( page, content ) ); serializeToDisk(); } /** * Updates the m_referedTo and m_referredBy hashmaps when a page has been * deleted. * <P> * Within the m_refersTo map the pagename is a key. The whole key-value-set * has to be removed to keep the map clean. * Within the m_referredBy map the name is stored as a value. Since a key * can have more than one value we have to delete just the key-value-pair * referring page:deleted page. * * @param page Name of the page to remove from the maps. */ public synchronized void pageRemoved( WikiPage page ) { String pageName = page.getName(); Collection RefTo = (Collection)m_refersTo.get( pageName ); Iterator it_refTo = RefTo.iterator(); while( it_refTo.hasNext() ) { String referredPageName = (String)it_refTo.next(); Set refBy = (Set)m_referredBy.get( referredPageName ); log.debug("Before cleaning m_referredBy HashMap key:value "+referredPageName+":"+m_referredBy.get( referredPageName ));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -