    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}