001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018*/ 019 020package org.apache.wiki.diff; 021 022import org.apache.log4j.Logger; 023import org.apache.wiki.api.core.Context; 024import org.apache.wiki.api.core.Engine; 025import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 026import org.apache.wiki.i18n.InternationalizationManager; 027import org.apache.wiki.preferences.Preferences; 028import org.apache.wiki.util.TextUtil; 029import org.suigeneris.jrcs.diff.Diff; 030import org.suigeneris.jrcs.diff.DifferentiationFailedException; 031import org.suigeneris.jrcs.diff.Revision; 032import org.suigeneris.jrcs.diff.RevisionVisitor; 033import org.suigeneris.jrcs.diff.delta.AddDelta; 034import org.suigeneris.jrcs.diff.delta.ChangeDelta; 035import org.suigeneris.jrcs.diff.delta.Chunk; 036import org.suigeneris.jrcs.diff.delta.DeleteDelta; 037import org.suigeneris.jrcs.diff.myers.MyersDiff; 038 039import java.io.IOException; 040import java.text.ChoiceFormat; 041import java.text.Format; 042import java.text.MessageFormat; 043import java.text.NumberFormat; 044import java.util.Properties; 045import java.util.ResourceBundle; 046 047 048/** 049 * This is the JSPWiki 'traditional' diff. It uses an internal diff engine. 050 */ 051public class TraditionalDiffProvider implements DiffProvider { 052 053 private static final Logger log = Logger.getLogger( TraditionalDiffProvider.class ); 054 private static final String CSS_DIFF_ADDED = "<tr><td class=\"diffadd\">"; 055 private static final String CSS_DIFF_REMOVED = "<tr><td class=\"diffrem\">"; 056 private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">"; 057 private static final String CSS_DIFF_CLOSE = "</td></tr>" + Diff.NL; 058 059 /** 060 * Constructs the provider. 061 */ 062 public TraditionalDiffProvider() { 063 } 064 065 /** 066 * {@inheritDoc} 067 * @see org.apache.wiki.api.providers.WikiProvider#getProviderInfo() 068 */ 069 @Override 070 public String getProviderInfo() 071 { 072 return "TraditionalDiffProvider"; 073 } 074 075 /** 076 * {@inheritDoc} 077 * @see org.apache.wiki.api.providers.WikiProvider#initialize(org.apache.wiki.api.core.Engine, java.util.Properties) 078 */ 079 @Override 080 public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException { 081 } 082 083 /** 084 * Makes a diff using the BMSI utility package. We use our own diff printer, 085 * which makes things easier. 086 * 087 * @param ctx The WikiContext in which the diff should be made. 088 * @param p1 The first string 089 * @param p2 The second string. 090 * 091 * @return Full HTML diff. 092 */ 093 @Override 094 public String makeDiffHtml( final Context ctx, final String p1, final String p2 ) { 095 final String diffResult; 096 097 try { 098 final String[] first = Diff.stringToArray(TextUtil.replaceEntities(p1)); 099 final String[] second = Diff.stringToArray(TextUtil.replaceEntities(p2)); 100 final Revision rev = Diff.diff(first, second, new MyersDiff()); 101 102 if( rev == null || rev.size() == 0 ) { 103 // No difference 104 return ""; 105 } 106 107 final StringBuffer ret = new StringBuffer(rev.size() * 20); // Guessing how big it will become... 108 109 ret.append( "<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n" ); 110 rev.accept( new RevisionPrint( ctx, ret ) ); 111 ret.append( "</table>\n" ); 112 113 return ret.toString(); 114 } catch( final DifferentiationFailedException e ) { 115 diffResult = "makeDiff failed with DifferentiationFailedException"; 116 log.error( diffResult, e ); 117 } 118 119 return diffResult; 120 } 121 122 123 private static final class RevisionPrint implements RevisionVisitor { 124 125 private StringBuffer m_result; 126 private Context m_context; 127 private ResourceBundle m_rb; 128 129 private RevisionPrint( final Context ctx, final StringBuffer sb ) { 130 m_result = sb; 131 m_context = ctx; 132 m_rb = Preferences.getBundle( ctx, InternationalizationManager.CORE_BUNDLE ); 133 } 134 135 @Override 136 public void visit( final Revision rev ) { 137 // GNDN (Goes nowhere, does nothing) 138 } 139 140 @Override 141 public void visit( final AddDelta delta ) { 142 final Chunk changed = delta.getRevised(); 143 print( changed, m_rb.getString( "diff.traditional.added" ) ); 144 changed.toString( m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE ); 145 } 146 147 @Override 148 public void visit( final ChangeDelta delta ) { 149 final Chunk changed = delta.getOriginal(); 150 print(changed, m_rb.getString( "diff.traditional.changed" ) ); 151 changed.toString( m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE ); 152 delta.getRevised().toString( m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE ); 153 } 154 155 @Override 156 public void visit( final DeleteDelta delta ) { 157 final Chunk changed = delta.getOriginal(); 158 print( changed, m_rb.getString( "diff.traditional.removed" ) ); 159 changed.toString( m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE ); 160 } 161 162 private void print( final Chunk changed, final String type ) { 163 m_result.append( CSS_DIFF_UNCHANGED ); 164 165 final String[] choiceString = { 166 m_rb.getString("diff.traditional.oneline"), 167 m_rb.getString("diff.traditional.lines") 168 }; 169 final double[] choiceLimits = { 1, 2 }; 170 171 final MessageFormat fmt = new MessageFormat(""); 172 fmt.setLocale( Preferences.getLocale(m_context) ); 173 final ChoiceFormat cfmt = new ChoiceFormat( choiceLimits, choiceString ); 174 fmt.applyPattern( type ); 175 final Format[] formats = { NumberFormat.getInstance(), cfmt, NumberFormat.getInstance() }; 176 fmt.setFormats( formats ); 177 178 final Object[] params = { changed.first() + 1, 179 changed.size(), 180 changed.size() }; 181 m_result.append( fmt.format(params) ); 182 m_result.append( CSS_DIFF_CLOSE ); 183 } 184 } 185 186}