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
020 package org.apache.wiki.diff;
021
022 import java.io.IOException;
023 import java.text.ChoiceFormat;
024 import java.text.Format;
025 import java.text.MessageFormat;
026 import java.text.NumberFormat;
027 import java.util.Properties;
028 import java.util.ResourceBundle;
029
030 import org.apache.log4j.Logger;
031 import org.apache.wiki.WikiContext;
032 import org.apache.wiki.WikiEngine;
033 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
034 import org.apache.wiki.i18n.InternationalizationManager;
035 import org.apache.wiki.preferences.Preferences;
036 import org.apache.wiki.util.TextUtil;
037 import org.suigeneris.jrcs.diff.Diff;
038 import org.suigeneris.jrcs.diff.DifferentiationFailedException;
039 import org.suigeneris.jrcs.diff.Revision;
040 import org.suigeneris.jrcs.diff.RevisionVisitor;
041 import org.suigeneris.jrcs.diff.delta.AddDelta;
042 import org.suigeneris.jrcs.diff.delta.ChangeDelta;
043 import org.suigeneris.jrcs.diff.delta.Chunk;
044 import org.suigeneris.jrcs.diff.delta.DeleteDelta;
045 import org.suigeneris.jrcs.diff.myers.MyersDiff;
046
047
048 /**
049 * This is the JSPWiki 'traditional' diff. It uses an internal diff engine.
050 *
051 */
052 public 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 }