All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.yal10n.diff.UnifiedDiff Maven / Gradle / Ivy

The newest version!
package net.sf.yal10n.diff;

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.velocity.tools.generic.EscapeTool;

/**
 * This class represents a diff produced by subversion.
 * It can convert the diff into a side-by-side comparison html format.
 */
public class UnifiedDiff
{
    private static final Pattern HUNK_START_PATTERN = Pattern.compile( "^@@ \\-(\\d+),(\\d+) \\+(\\d+),(\\d+) @@" );
    private String originalName;
    private String newName;
    private List hunks = new ArrayList();

    /**
     * Create a new {@link UnifiedDiff} with the given diff.
     * @param diffString the diff data
     */
    public UnifiedDiff( String diffString )
    {
        this( diffString, false, null );
    }

    /**
     * Creates a new {@link UnifiedDiff} with the given diff.
     * If the flag newFile is true, then not a diff is assumed,
     * simply the content of the new file is expected.
     * @param diffString the diff data or file content
     * @param newFile if false, a real diff is expected.
     * @param filename the file name. Only needed if not a real diff is provided.
     */
    public UnifiedDiff( String diffString, boolean newFile, String filename )
    {
        if ( !newFile )
        {
            parse( diffString );
        }
        else
        {
            parseNewFile( diffString, filename );
        }
    }

    private void parseNewFile( String content, String filename )
    {
        String[] lines = content.split( "\n" );
        int lineNumber = 1;
        Hunk hunk = new Hunk();
        hunk.firstLineNumber = lineNumber;
        for ( String line : lines )
        {
            hunk.newLines.put( lineNumber, line );
            hunk.indicators.put( lineNumber, '+' );
            lineNumber++;
        }
        hunk.lastLineNumber = lineNumber;
        hunks.add( hunk );
        originalName = "--";
        newName = String.valueOf( filename );
    }

    private void parse( String diff )
    {
        String[] lines = diff.split( "\n" );

        if ( lines.length < 5 )
        {
            throw new IllegalArgumentException( "The given diff is too short. " );
        }

        originalName = lines[2].substring( 4 );
        newName = lines[3].substring( 4 );

        int origLineNumber = 0;
        int newLineNumber = 0;
        Hunk currentHunk = null;

        for ( int i = 4; i < lines.length; i++ )
        {
            String line = lines[i];
            String nextLine = "\\\\";
            if ( i + 1 < lines.length )
            {
                nextLine = lines[i + 1];
            }
            if ( StringUtils.isEmpty( nextLine ) )
            {
                nextLine = "\\\\";
            }

            Matcher m = HUNK_START_PATTERN.matcher( line );
            boolean newHunkStarted = m.find();
            if ( newHunkStarted || StringUtils.isEmpty( line ) )
            {

                if ( currentHunk != null )
                {
                    currentHunk.lastLineNumber = newLineNumber;
                    calculateChangedLines( currentHunk );
                    hunks.add( currentHunk );
                    currentHunk = null;
                }

                if ( newHunkStarted )
                {
                    currentHunk = new Hunk();
                    origLineNumber = Integer.parseInt( m.group( 1 ) );
                    newLineNumber = origLineNumber;
                    currentHunk.firstLineNumber = origLineNumber;
                }
            }
            else
            {
                if ( currentHunk == null )
                {
                    // ignore this line outside of a hunk
                    continue;
                }

                char indicator = line.charAt( 0 );
                String diffLine = line.substring( 1 );

                switch ( indicator )
                {
                case ' ':
                    if ( origLineNumber < newLineNumber )
                    {
                        origLineNumber = newLineNumber;
                    }
                    else
                    {
                        newLineNumber = origLineNumber;
                    }
                    currentHunk.commonLines.put( origLineNumber, diffLine );
                    currentHunk.indicators.put( origLineNumber, ' ' );
                    origLineNumber++;
                    newLineNumber++;
                    break;
                case '-':
                    currentHunk.origLines.put( origLineNumber, diffLine );
                    currentHunk.indicators.put( origLineNumber, '-' );
                    origLineNumber++;
                    break;
                case '+':
                    currentHunk.newLines.put( newLineNumber, diffLine );
                    currentHunk.indicators.put( newLineNumber, '+' );
                    newLineNumber++;
                    break;
                default:
                    // ignore
                }
            }
        }
        if ( currentHunk != null )
        {
            currentHunk.lastLineNumber = newLineNumber;
            calculateChangedLines( currentHunk );
            hunks.add( currentHunk );
        }
    }

    private void calculateChangedLines( Hunk currentHunk )
    {
        for ( Integer line : currentHunk.origLines.keySet() )
        {
            if ( currentHunk.newLines.containsKey( line ) )
            {
                currentHunk.indicators.put( line, 'C' );
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        StringBuilder out = new StringBuilder();
        out.append( "orig: " ).append( originalName ).append( "\n" );
        out.append( " new: " ).append( newName ).append( "\n" );
        out.append( "found " + hunks.size() + " hunks\n" );
        for ( Hunk hunk : hunks )
        {
            for ( int i = hunk.firstLineNumber; i < hunk.lastLineNumber; i++ )
            {
                Character indicator = hunk.indicators.get( i );

                out.append( indicator ).append( i ).append( "  " );

                switch ( indicator )
                {
                case '-':
                    out.append( hunk.origLines.get( i ) );
                    break;
                case ' ':
                    out.append( hunk.commonLines.get( i ) );
                    break;
                case '+':
                    out.append( hunk.newLines.get( i ) );
                    break;
                case 'C':
                default:
                    out.append( hunk.origLines.get( i ) ).append( " || " ).append( hunk.newLines.get( i ) );
                    break;
                }
                out.append( "\n" );
            }
        }
        return out.toString();
    }

    /**
     * Converts this diff into a side-by-side html format.
     * @return the html code
     */
    public String asHtmlSnippet()
    {
        EscapeTool escapeTool = new EscapeTool();
        final String lightYellow = "#ffff80";
        final String lightRed = "#f08080";
        final String lightGreen = "#90ee90";
        final String lightGrey = "#d3d3d3";
        final String darkCyan = "#008b8b";

        final String firstColumnStyle = " style=\"padding: 0 0.5em 0 0.5em; text-align: right;\"";

        StringBuilder out = new StringBuilder();
        out.append( "
\n" ); out.append( "\n" ); out.append( " " ); out.append( "" ); out.append( "" ); out.append( "\n" ); for ( Hunk hunk : hunks ) { out.append( "" ); out.append( "#" ); out.append( "" ); out.append( "" ); out.append( "" ); for ( int i = hunk.firstLineNumber; i < hunk.lastLineNumber; i++ ) { Character indicator = hunk.indicators.get( i ); String currentLine = escapeTool.html( hunk.commonLines.get( i ) ); String originalLine = escapeTool.html( hunk.origLines.get( i ) ); String newLine = escapeTool.html( hunk.newLines.get( i ) ); if ( originalLine == null || originalLine.isEmpty() ) { originalLine = " "; } if ( currentLine == null || currentLine.isEmpty() ) { currentLine = " "; } if ( newLine == null || newLine.isEmpty() ) { newLine = " "; } out.append( "" ); out.append( "" ).append( i ).append( "" ); switch ( indicator ) { case '-': out.append( "" ); out.append( "" ); break; case ' ': out.append( "" ); out.append( "" ); break; case '+': out.append( "" ); out.append( "" ); break; case 'C': default: out.append( "" ); out.append( "" ); break; } out.append( "\n" ); } } out.append( "
" ).append( escapeTool.html( originalName ) ).append( "" ).append( escapeTool.html( newName ) ).append( "
Line " ).append( hunk.firstLineNumber ).append( "Line " ).append( hunk.firstLineNumber ).append( "
" ) .append( originalLine ).append( " " ).append( currentLine ).append( "" ).append( currentLine ).append( " " ) .append( newLine ).append( "" ) .append( originalLine ).append( "" ) .append( newLine ).append( "
\n" ); out.append( "
\n" ); out.append( "\n" ); out.append( "\n" ); out.append( " \n" ); out.append( " \n" ); out.append( "\n" ); out.append( "\n" ); out.append( "" ) .append( "\n" ); out.append( "\n" ); out.append( "" ) .append( "\n" ); out.append( "
Legend:
removed 
changed lines
 added
\n" ); out.append( "
\n" ); return out.toString(); } /** * Gets the original name. * * @return the original name */ public String getOriginalName() { return originalName; } /** * Gets the new name. * * @return the new name */ public String getNewName() { return newName; } /** * Gets the hunks. * * @return the hunks */ public List getHunks() { return hunks; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy