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}