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}