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 java.io.IOException; 023import java.text.ChoiceFormat; 024import java.text.Format; 025import java.text.MessageFormat; 026import java.text.NumberFormat; 027import java.util.Properties; 028import java.util.ResourceBundle; 029 030import org.apache.log4j.Logger; 031import org.apache.wiki.WikiContext; 032import org.apache.wiki.WikiEngine; 033import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 034import org.apache.wiki.i18n.InternationalizationManager; 035import org.apache.wiki.preferences.Preferences; 036import org.apache.wiki.util.TextUtil; 037import org.suigeneris.jrcs.diff.Diff; 038import org.suigeneris.jrcs.diff.DifferentiationFailedException; 039import org.suigeneris.jrcs.diff.Revision; 040import org.suigeneris.jrcs.diff.RevisionVisitor; 041import org.suigeneris.jrcs.diff.delta.AddDelta; 042import org.suigeneris.jrcs.diff.delta.ChangeDelta; 043import org.suigeneris.jrcs.diff.delta.Chunk; 044import org.suigeneris.jrcs.diff.delta.DeleteDelta; 045import org.suigeneris.jrcs.diff.myers.MyersDiff; 046 047 048/** 049 * This is the JSPWiki 'traditional' diff. It uses an internal diff engine. 050 * 051 */ 052public class TraditionalDiffProvider implements DiffProvider 053{ 054 private static final Logger log = Logger.getLogger(TraditionalDiffProvider.class); 055 056 private static final String CSS_DIFF_ADDED = "<tr><td class=\"diffadd\">"; 057 private static final String CSS_DIFF_REMOVED = "<tr><td class=\"diffrem\">"; 058 private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">"; 059 private static final String CSS_DIFF_CLOSE = "</td></tr>" + Diff.NL; 060 061 062 /** 063 * Constructs the provider. 064 */ 065 public TraditionalDiffProvider() 066 { 067 } 068 069 070 /** 071 * {@inheritDoc} 072 * @see org.apache.wiki.WikiProvider#getProviderInfo() 073 */ 074 public String getProviderInfo() 075 { 076 return "TraditionalDiffProvider"; 077 } 078 079 /** 080 * {@inheritDoc} 081 * @see org.apache.wiki.WikiProvider#initialize(org.apache.wiki.WikiEngine, java.util.Properties) 082 */ 083 public void initialize(WikiEngine engine, Properties properties) 084 throws NoRequiredPropertyException, IOException 085 { 086 } 087 088 /** 089 * Makes a diff using the BMSI utility package. We use our own diff printer, 090 * which makes things easier. 091 * 092 * @param ctx The WikiContext in which the diff should be made. 093 * @param p1 The first string 094 * @param p2 The second string. 095 * 096 * @return Full HTML diff. 097 */ 098 public String makeDiffHtml( WikiContext ctx, String p1, String p2 ) 099 { 100 String diffResult = ""; 101 102 try 103 { 104 String[] first = Diff.stringToArray(TextUtil.replaceEntities(p1)); 105 String[] second = Diff.stringToArray(TextUtil.replaceEntities(p2)); 106 Revision rev = Diff.diff(first, second, new MyersDiff()); 107 108 if( rev == null || rev.size() == 0 ) 109 { 110 // No difference 111 112 return ""; 113 } 114 115 StringBuffer ret = new StringBuffer(rev.size() * 20); // Guessing how big it will become... 116 117 ret.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"); 118 rev.accept( new RevisionPrint(ctx,ret) ); 119 ret.append("</table>\n"); 120 121 return ret.toString(); 122 } 123 catch( DifferentiationFailedException e ) 124 { 125 diffResult = "makeDiff failed with DifferentiationFailedException"; 126 log.error(diffResult, e); 127 } 128 129 return diffResult; 130 } 131 132 133 private static final class RevisionPrint 134 implements RevisionVisitor 135 { 136 private StringBuffer m_result = null; 137 private WikiContext m_context; 138 private ResourceBundle m_rb; 139 140 private RevisionPrint(WikiContext ctx,StringBuffer sb) 141 { 142 m_result = sb; 143 m_context = ctx; 144 m_rb = Preferences.getBundle( ctx, InternationalizationManager.CORE_BUNDLE ); 145 } 146 147 public void visit(Revision rev) 148 { 149 // GNDN (Goes nowhere, does nothing) 150 } 151 152 public void visit(AddDelta delta) 153 { 154 Chunk changed = delta.getRevised(); 155 print(changed, m_rb.getString( "diff.traditional.added" ) ); 156 changed.toString(m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE); 157 } 158 159 public void visit(ChangeDelta delta) 160 { 161 Chunk changed = delta.getOriginal(); 162 print(changed, m_rb.getString( "diff.traditional.changed") ); 163 changed.toString(m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE); 164 delta.getRevised().toString(m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE); 165 } 166 167 public void visit(DeleteDelta delta) 168 { 169 Chunk changed = delta.getOriginal(); 170 print(changed, m_rb.getString( "diff.traditional.removed") ); 171 changed.toString(m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE); 172 } 173 174 private void print(Chunk changed, String type) 175 { 176 m_result.append(CSS_DIFF_UNCHANGED); 177 178 String[] choiceString = 179 { 180 m_rb.getString("diff.traditional.oneline"), 181 m_rb.getString("diff.traditional.lines") 182 }; 183 double[] choiceLimits = { 1, 2 }; 184 185 MessageFormat fmt = new MessageFormat(""); 186 fmt.setLocale( Preferences.getLocale(m_context) ); 187 ChoiceFormat cfmt = new ChoiceFormat( choiceLimits, choiceString ); 188 fmt.applyPattern( type ); 189 Format[] formats = { NumberFormat.getInstance(), cfmt, NumberFormat.getInstance() }; 190 fmt.setFormats( formats ); 191 192 Object[] params = { changed.first() + 1, 193 changed.size(), 194 changed.size() }; 195 m_result.append( fmt.format(params) ); 196 m_result.append(CSS_DIFF_CLOSE); 197 } 198 } 199}