/*
* SVNDiffParser.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.views.vcs.svn;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.regex.Match;
import org.rstudio.core.client.regex.Pattern;
import org.rstudio.studio.client.workbench.views.vcs.common.diff.*;
import org.rstudio.studio.client.workbench.views.vcs.common.diff.Line.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Provides parsing of SVN diffs, which may contain property changes. Property
* changes are treated as Info lines in an ignorable chunk, whereas item diffs
* are treated as usual using UnifiedParser.
*/
public class SVNDiffParser implements DiffParser
{
private static class Section
{
private Section(boolean property, String filename, String data)
{
isProperty = property;
this.filename = filename;
this.data = data;
}
public boolean isProperty;
public String filename;
public String data;
}
public SVNDiffParser(String data)
{
Pattern sectionHeaderPattern = Pattern.create(
"^((Index: ([^\\r\\n]+)\\r?\\n=+)|(\\r?\\nProperty changes on: ([^\\r\\n]+)\\r?\\n_+))$");
ArrayList<String> sectionData = new ArrayList<String>();
ArrayList<Match> sectionMatches = new ArrayList<Match>();
int pos = 0;
int lastHeaderStart = -1;
Match m;
while (null != (m = sectionHeaderPattern.match(data, pos)))
{
sectionMatches.add(m);
if (lastHeaderStart >= 0)
sectionData.add(data.substring(lastHeaderStart, m.getIndex()));
lastHeaderStart = m.getIndex();
pos = m.getIndex() + m.getValue().length();
}
if (lastHeaderStart >= 0)
sectionData.add(data.substring(lastHeaderStart));
sections_ = new ArrayList<Section>();
for (int i = 0; i < sectionData.size(); i++)
{
sections_.add(parseSection(sectionMatches.get(i), sectionData.get(i)));
}
}
private Section parseSection(Match m, String sectionData)
{
String filename = m.getGroup(3) != null ? m.getGroup(3)
: m.getGroup(5);
if (filename == null)
{
throw new RuntimeException(
"Programmer error: Filename not found in diff section header");
}
boolean isProperty = m.getGroup(4) != null;
return new Section(isProperty, filename, sectionData);
}
@Override
public DiffFileHeader nextFilePair()
{
pendingDiffChunks_.clear();
if (sections_.size() == 0)
return null;
ArrayList<Section> sectionsToUse = new ArrayList<Section>();
sectionsToUse.add(sections_.remove(0));
String filename = sectionsToUse.get(0).filename;
while (sections_.size() > 0 && sections_.get(0).filename.equals(filename))
{
sectionsToUse.add(sections_.remove(0));
}
// Put the properties above the item diffs.
Collections.sort(sectionsToUse, new Comparator<Section>()
{
@Override
public int compare(Section a, Section b)
{
return a.isProperty == b.isProperty ? 0 :
a.isProperty ? -1 :
1;
}
});
Section lastSection = null;
for (Section section : sectionsToUse)
{
if (section.isProperty)
{
String trimmed = StringUtil.trimBlankLines(section.data);
pendingDiffChunks_.add(
createInfoChunk(StringUtil.getLineIterator(trimmed)));
}
else
{
UnifiedParser parser = new UnifiedParser(section.data, diffIndex_);
DiffFileHeader filePair = parser.nextFilePair();
if (filePair == null)
{
// Although "Index: <filename>" appeared, no diff was actually
// generated (e.g. binary file)
Pattern hrule = Pattern.create("^=+$");
Match m = hrule.match(section.data, 0);
int startAt = (m != null) ? m.getIndex() + m.getValue().length()
: 0;
Iterable<String> lines = StringUtil.getLineIterator(
StringUtil.trimBlankLines(section.data.substring(startAt)));
DiffChunk chunk = createInfoChunk(lines);
if (lastSection != null && lastSection.isProperty)
{
// This is to work around special case where a binary file is
// initially checked in. If we do nothing, in the history it
// will appear as the property changes, then without a break,
// the message that this file can't be displayed. That's the
// correct content, but we want it in the order of the message
// that the file can't be displayed, then a blank line, then
// the property changes.
pendingDiffChunks_.add(0, chunk);
pendingDiffChunks_.add(
1, createInfoChunk(StringUtil.getLineIterator("\n")));
}
else
{
pendingDiffChunks_.add(chunk);
}
}
DiffChunk chunk;
while (null != (chunk = parser.nextChunk()))
{
pendingDiffChunks_.add(chunk);
}
diffIndex_ = parser.getDiffIndex();
}
lastSection = section;
}
return new DiffFileHeader(new ArrayList<String>(), filename, filename);
}
private DiffChunk createInfoChunk(Iterable<String> lines)
{
ArrayList<Line> outLines = new ArrayList<Line>();
int chunkDiffIndex = diffIndex_++;
for (String line : lines)
{
outLines.add(new Line(Type.Info,
new boolean[] {false, false},
new int[] {-1, -1},
StringUtil.isNullOrEmpty(line) ? "\n": line,
diffIndex_++));
}
return new DiffChunk(null, null, outLines, chunkDiffIndex);
}
@Override
public DiffChunk nextChunk()
{
if (pendingDiffChunks_.size() == 0)
return null;
return pendingDiffChunks_.remove(0);
}
private final ArrayList<DiffChunk> pendingDiffChunks_ = new ArrayList<DiffChunk>();
private final ArrayList<Section> sections_;
private int diffIndex_;
}