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.BufferedReader;
023import java.io.File;
024import java.io.IOException;
025import java.io.StringReader;
026import java.util.Properties;
027
028import org.apache.log4j.Logger;
029import org.apache.wiki.WikiContext;
030import org.apache.wiki.WikiEngine;
031import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
032import org.apache.wiki.util.FileUtil;
033import org.apache.wiki.util.TextUtil;
034
035/**
036 * This DiffProvider allows external command line tools to be used to generate
037 * the diff.
038 */
039public class ExternalDiffProvider implements DiffProvider
040{
041    private static final Logger log = Logger.getLogger(ExternalDiffProvider.class);
042
043    /**
044     * Determines the command to be used for 'diff'. This program must be able
045     * to output diffs in the unified format. For example 'diff -u %s1 %s2'.
046     */
047    public static final String PROP_DIFFCOMMAND    = "jspwiki.diffCommand";
048
049    private String m_diffCommand = null;
050    private String m_encoding;
051
052    private static final char DIFF_ADDED_SYMBOL    = '+';
053    private static final char DIFF_REMOVED_SYMBOL  = '-';
054
055    private static final String CSS_DIFF_ADDED     = "<tr><td bgcolor=\"#99FF99\" class=\"diffadd\">";
056    private static final String CSS_DIFF_REMOVED   = "<tr><td bgcolor=\"#FF9933\" class=\"diffrem\">";
057    private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">";
058    private static final String CSS_DIFF_CLOSE     = "</td></tr>";
059
060    //FIXME This could/should be a property for this provider, there is not guarentee that
061    //the external program generates a format suitible for the colorization code of the
062    //TraditionalDiffProvider, currently set to true for legacy compatibility.
063    //I don't think this 'feature' ever worked right, did it?...
064    private boolean m_traditionalColorization = true;
065
066    /**
067     *  Creates a new ExternalDiffProvider.
068     */
069    public ExternalDiffProvider()
070    {
071    }
072
073    /**
074     * @see org.apache.wiki.WikiProvider#getProviderInfo()
075     * {@inheritDoc}
076     */
077    public String getProviderInfo()
078    {
079        return "ExternalDiffProvider";
080    }
081
082    /**
083     * {@inheritDoc}
084     * @see org.apache.wiki.WikiProvider#initialize(org.apache.wiki.WikiEngine, java.util.Properties)
085     */
086    public void initialize( WikiEngine engine, Properties properties )
087        throws NoRequiredPropertyException, IOException
088    {
089        m_diffCommand = properties.getProperty( PROP_DIFFCOMMAND );
090
091        if( (null == m_diffCommand) || (m_diffCommand.trim().equals( "" )) )
092        {
093            throw new NoRequiredPropertyException( "ExternalDiffProvider missing required property", PROP_DIFFCOMMAND );
094        }
095
096        m_encoding = engine.getContentEncoding();
097    }
098
099
100    /**
101     * Makes the diff by calling "diff" program.
102     * {@inheritDoc}
103     */
104    public String makeDiffHtml( WikiContext ctx, String p1, String p2 )
105    {
106        File f1 = null;
107        File f2 = null;
108        String diff = null;
109
110        try
111        {
112            f1 = FileUtil.newTmpFile(p1, m_encoding);
113            f2 = FileUtil.newTmpFile(p2, m_encoding);
114
115            String cmd = TextUtil.replaceString(m_diffCommand, "%s1", f1.getPath());
116            cmd = TextUtil.replaceString(cmd, "%s2", f2.getPath());
117
118            String output = FileUtil.runSimpleCommand(cmd, f1.getParent());
119
120            // FIXME: Should this rely on the system default encoding?
121            String rawWikiDiff = new String(output.getBytes("ISO-8859-1"), m_encoding);
122
123            String htmlWikiDiff = TextUtil.replaceEntities( rawWikiDiff );
124
125            if (m_traditionalColorization) //FIXME, see comment near declaration...
126                diff = colorizeDiff(htmlWikiDiff);
127            else
128                diff = htmlWikiDiff;
129
130        }
131        catch (IOException e)
132        {
133            log.error("Failed to do file diff", e);
134        }
135        catch (InterruptedException e)
136        {
137            log.error("Interrupted", e);
138        }
139        finally
140        {
141            if( f1 != null )
142                f1.delete();
143            if( f2 != null )
144                f2.delete();
145        }
146
147        return diff;
148    }
149
150
151    /**
152     * Goes through output provided by a diff command and inserts HTML tags to
153     * make the result more legible. Currently colors lines starting with a +
154     * green, those starting with - reddish (hm, got to think of color blindness
155     * here...).
156     */
157    static String colorizeDiff(String diffText) throws IOException
158    {
159        if( diffText == null )
160            return "Invalid diff - probably something wrong with server setup.";
161
162        String line = null;
163        String start = null;
164        String stop = null;
165
166        BufferedReader in = new BufferedReader( new StringReader( diffText ) );
167        StringBuilder out = new StringBuilder();
168
169        out.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
170        while( (line = in.readLine()) != null )
171        {
172            stop = CSS_DIFF_CLOSE;
173
174            if( line.length() > 0 )
175            {
176                switch( line.charAt( 0 ) )
177                {
178                    case DIFF_ADDED_SYMBOL:
179                        start = CSS_DIFF_ADDED;
180                        break;
181                    case DIFF_REMOVED_SYMBOL:
182                        start = CSS_DIFF_REMOVED;
183                        break;
184                    default:
185                        start = CSS_DIFF_UNCHANGED;
186                }
187            }
188            else
189            {
190                start = CSS_DIFF_UNCHANGED;
191            }
192
193            out.append( start );
194            out.append( line.trim() );
195            out.append( stop + "\n" );
196        }
197
198        out.append( "</table>\n" );
199        return out.toString();
200    }
201}