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    }