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