package org.eclipse.compare.internal.core.patch;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;
import org.eclipse.compare.patch.IFilePatch2;
import org.eclipse.core.runtime.*;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.SimpleDateFormat;
public class PatchReader {
private static final boolean DEBUG= false;
private static final String DEV_NULL= "/dev/null";
protected static final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker";
private DateFormat[] fDateFormats= new DateFormat[] {
new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy"),
new SimpleDateFormat("yyyy/MM/dd kk:mm:ss"),
new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy", Locale.US)
};
private boolean fIsWorkspacePatch;
private boolean fIsGitPatch;
private DiffProject[] fDiffProjects;
private FilePatch2[] fDiffs;
public static final String = "### Eclipse Workspace Patch";
public static final String MULTIPROJECTPATCH_VERSION= "1.0";
public static final String MULTIPROJECTPATCH_PROJECT= "#P";
private static final Pattern GIT_PATCH_PATTERN= Pattern.compile("^diff --git a/.+ b/.+[\r\n]+$");
public PatchReader() {
}
public PatchReader(DateFormat[] dateFormats) {
this();
this.fDateFormats = dateFormats;
}
public void parse(BufferedReader reader) throws IOException {
List<FilePatch2> diffs= new ArrayList<>();
HashMap<String, DiffProject> diffProjects= new HashMap<>(4);
String line= null;
boolean reread= false;
String diffArgs= null;
String fileName= null;
String projectName= "";
this.fIsWorkspacePatch= false;
this.fIsGitPatch = false;
LineReader lr= new LineReader(reader);
lr.ignoreSingleCR();
line= lr.readLine();
if (line != null && line.startsWith(PatchReader.MULTIPROJECTPATCH_HEADER)) {
this.fIsWorkspacePatch= true;
} else {
parse(lr, line);
return;
}
while (true) {
if (!reread)
line= lr.readLine();
reread= false;
if (line == null)
break;
if (line.length() < 4)
continue;
if (line.startsWith(PatchReader.MULTIPROJECTPATCH_PROJECT)) {
projectName= line.substring(2).trim();
continue;
}
if (line.startsWith("Index: ")) {
fileName= line.substring(7).trim();
continue;
}
if (line.startsWith("diff")) {
diffArgs= line.substring(4).trim();
continue;
}
if (line.startsWith("--- ")) {
DiffProject diffProject;
if (!diffProjects.containsKey(projectName)) {
diffProject= new DiffProject(projectName);
diffProjects.put(projectName, diffProject);
} else {
diffProject= diffProjects.get(projectName);
}
line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName, diffProject);
diffArgs= fileName= null;
reread= true;
}
}
lr.close();
this.fDiffProjects= diffProjects.values().toArray(new DiffProject[diffProjects.size()]);
this.fDiffs = diffs.toArray(new FilePatch2[diffs.size()]);
}
protected FilePatch2 createFileDiff(IPath oldPath, long oldDate,
IPath newPath, long newDate) {
return new FilePatch2(oldPath, oldDate, newPath, newDate);
}
private String readUnifiedDiff(List<FilePatch2> diffs, LineReader lr, String line, String diffArgs, String fileName, DiffProject diffProject) throws IOException {
List<FilePatch2> newDiffs= new ArrayList<>();
String nextLine= readUnifiedDiff(newDiffs, lr, line, diffArgs, fileName);
for (FilePatch2 diff : newDiffs) {
diffProject.add(diff);
diffs.add(diff);
}
return nextLine;
}
public void parse(LineReader lr, String line) throws IOException {
List<FilePatch2> diffs= new ArrayList<>();
boolean reread= false;
String diffArgs= null;
String fileName= null;
List<String> headerLines = new ArrayList<>();
boolean foundDiff= false;
reread= line!=null;
while (true) {
if (!reread)
line= lr.readLine();
reread= false;
if (line == null)
break;
if (line.startsWith("Index: ")) {
fileName= line.substring(7).trim();
} else if (line.startsWith("diff")) {
if (!foundDiff && GIT_PATCH_PATTERN.matcher(line).matches())
this.fIsGitPatch= true;
foundDiff= true;
diffArgs= line.substring(4).trim();
} else if (line.startsWith("--- ")) {
line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName);
if (!headerLines.isEmpty())
setHeader(diffs.get(diffs.size() - 1), headerLines);
diffArgs= fileName= null;
reread= true;
} else if (line.startsWith("*** ")) {
line= readContextDiff(diffs, lr, line, diffArgs, fileName);
if (!headerLines.isEmpty())
setHeader(diffs.get(diffs.size() - 1), headerLines);
diffArgs= fileName= null;
reread= true;
}
if (!reread) {
headerLines.add(line);
}
}
lr.close();
this.fDiffs = diffs.toArray(new FilePatch2[diffs.size()]);
}
private void (FilePatch2 diff, List<String> headerLines) {
String header = LineReader.createString(false, headerLines);
diff.setHeader(header);
headerLines.clear();
}
protected String readUnifiedDiff(List<FilePatch2> diffs, LineReader reader, String line, String args, String fileName) throws IOException {
String[] oldArgs= split(line.substring(4));
line= reader.readLine();
if (line == null || !line.startsWith("+++ "))
return line;
String[] newArgs= split(line.substring(4));
FilePatch2 diff = createFileDiff(extractPath(oldArgs, 0, fileName),
extractDate(oldArgs, 1), extractPath(newArgs, 0, fileName),
extractDate(newArgs, 1));
diffs.add(diff);
int[] oldRange= new int[2];
int[] newRange= new int[2];
int remainingOld= -1;
int remainingNew= -1;
List<String> lines= new ArrayList<>();
boolean encounteredPlus = false;
boolean encounteredMinus = false;
boolean encounteredSpace = false;
try {
while (true) {
line= reader.readLine();
if (line == null)
return null;
if (reader.lineContentLength(line) == 0) {
continue;
}
char c= line.charAt(0);
if (remainingOld == 0 && remainingNew == 0 && c != '@' && c != '\\') {
return line;
}
switch (c) {
case '@':
if (line.startsWith("@@ ")) {
if (lines.size() > 0) {
Hunk.createHunk(diff, oldRange, newRange, lines, encounteredPlus, encounteredMinus, encounteredSpace);
lines.clear();
}
extractPair(line, '-', oldRange);
extractPair(line, '+', newRange);
remainingOld= oldRange[1];
remainingNew= newRange[1];
continue;
}
break;
case ' ':
encounteredSpace= true;
remainingOld--;
remainingNew--;
lines.add(line);
continue;
case '+':
encounteredPlus= true;
remainingNew--;
lines.add(line);
continue;
case '-':
encounteredMinus= true;
remainingOld--;
lines.add(line);
continue;
case '\\':
if (line.indexOf("newline at end") > 0) {
int lastIndex= lines.size();
if (lastIndex > 0) {
line= lines.get(lastIndex - 1);
int end= line.length() - 1;
char lc= line.charAt(end);
if (lc == '\n') {
end--;
if (end > 0 && line.charAt(end) == '\r')
end--;
} else if (lc == '\r') {
end--;
}
line= line.substring(0, end + 1);
lines.set(lastIndex - 1, line);
}
continue;
}
break;
case '#':
break;
case 'I':
if (line.indexOf("Index:") == 0)
break;
case 'd':
if (line.indexOf("diff ") == 0)
break;
case 'B':
if (line.indexOf("Binary files differ") == 0)
break;
default:
break;
}
return line;
}
} finally {
if (lines.size() > 0)
Hunk.createHunk(diff, oldRange, newRange, lines, encounteredPlus, encounteredMinus, encounteredSpace);
}
}
private String readContextDiff(List<FilePatch2> diffs, LineReader reader, String line, String args, String fileName) throws IOException {
String[] oldArgs= split(line.substring(4));
line= reader.readLine();
if (line == null || !line.startsWith("--- "))
return line;
String[] newArgs= split(line.substring(4));
FilePatch2 diff = createFileDiff(extractPath(oldArgs, 0, fileName),
extractDate(oldArgs, 1), extractPath(newArgs, 0, fileName),
extractDate(newArgs, 1));
diffs.add(diff);
int[] oldRange= new int[2];
int[] newRange= new int[2];
List<String> oldLines= new ArrayList<>();
List<String> newLines= new ArrayList<>();
List<String> lines= oldLines;
boolean encounteredPlus = false;
boolean encounteredMinus = false;
boolean encounteredSpace = false;
try {
while (true) {
line= reader.readLine();
if (line == null)
return line;
int l= line.length();
if (l == 0)
continue;
if (l > 1) {
switch (line.charAt(0)) {
case '*':
if (line.startsWith("***************")) {
if (oldLines.size() > 0 || newLines.size() > 0) {
Hunk.createHunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace);
oldLines.clear();
newLines.clear();
}
continue;
}
if (line.startsWith("*** ")) {
extractPair(line, ' ', oldRange);
if (oldRange[0] == 0) {
oldRange[1] = 0;
} else {
oldRange[1] = oldRange[1] - oldRange[0] + 1;
}
lines= oldLines;
continue;
}
break;
case ' ':
if (line.charAt(1) == ' ') {
lines.add(line);
continue;
}
break;
case '+':
if (line.charAt(1) == ' ') {
encounteredPlus = true;
lines.add(line);
continue;
}
break;
case '!':
if (line.charAt(1) == ' ') {
encounteredSpace = true;
lines.add(line);
continue;
}
break;
case '-':
if (line.charAt(1) == ' ') {
encounteredMinus = true;
lines.add(line);
continue;
}
if (line.startsWith("--- ")) {
extractPair(line, ' ', newRange);
if (newRange[0] == 0) {
newRange[1] = 0;
} else {
newRange[1] = newRange[1] - newRange[0] + 1;
}
lines= newLines;
continue;
}
break;
default:
break;
}
}
return line;
}
} finally {
if (oldLines.size() > 0 || newLines.size() > 0)
Hunk.createHunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace);
}
}
private List<String> unifyLines(List<String> oldLines, List<String> newLines) {
List<String> result= new ArrayList<>();
String[] ol= oldLines.toArray(new String[oldLines.size()]);
String[] nl= newLines.toArray(new String[newLines.size()]);
int oi= 0, ni= 0;
while (true) {
char oc= 0;
String o= null;
if (oi < ol.length) {
o= ol[oi];
oc= o.charAt(0);
}
char nc= 0;
String n= null;
if (ni < nl.length) {
n= nl[ni];
nc= n.charAt(0);
}
if (oc == 0 && nc == 0)
break;
if (oc == '-') {
do {
result.add('-' + o.substring(2));
oi++;
if (oi >= ol.length)
break;
o= ol[oi];
} while (o.charAt(0) == '-');
continue;
}
if (nc == '+') {
do {
result.add('+' + n.substring(2));
ni++;
if (ni >= nl.length)
break;
n= nl[ni];
} while (n.charAt(0) == '+');
continue;
}
if (oc == '!' && nc == '!') {
do {
result.add('-' + o.substring(2));
oi++;
if (oi >= ol.length)
break;
o= ol[oi];
} while (o.charAt(0) == '!');
do {
result.add('+' + n.substring(2));
ni++;
if (ni >= nl.length)
break;
n= nl[ni];
} while (n.charAt(0) == '!');
continue;
}
if (oc == ' ' && nc == ' ') {
do {
Assert.isTrue(o.equals(n), "non matching context lines");
result.add(' ' + o.substring(2));
oi++;
ni++;
if (oi >= ol.length || ni >= nl.length)
break;
o= ol[oi];
n= nl[ni];
} while (o.charAt(0) == ' ' && n.charAt(0) == ' ');
continue;
}
if (oc == ' ') {
do {
result.add(' ' + o.substring(2));
oi++;
if (oi >= ol.length)
break;
o= ol[oi];
} while (o.charAt(0) == ' ');
continue;
}
if (nc == ' ') {
do {
result.add(' ' + n.substring(2));
ni++;
if (ni >= nl.length)
break;
n= nl[ni];
} while (n.charAt(0) == ' ');
continue;
}
Assert.isTrue(false, "unexpected char <" + oc + "> <" + nc + ">");
}
return result;
}
private long (String[] args, int n) {
if (n < args.length) {
String line= args[n];
for (DateFormat dateFormat : this.fDateFormats) {
dateFormat.setLenient(true);
try {
Date date = dateFormat.parse(line);
return date.getTime();
} catch (ParseException ex) {
}
}
}
return IFilePatch2.DATE_UNKNOWN;
}
private IPath (String[] args, int n, String path2) {
if (n < args.length) {
String path= args[n];
if (DEV_NULL.equals(path))
return null;
int pos= path.lastIndexOf(':');
if (pos >= 0)
path= path.substring(0, pos);
if (path2 != null && !path2.equals(path)) {
if (DEBUG) System.out.println("path mismatch: " + path2);
path= path2;
}
return new Path(path);
}
return null;
}
private void (String line, char start, int[] pair) {
pair[0]= pair[1]= -1;
int startPos= line.indexOf(start);
if (startPos < 0) {
if (DEBUG) System.out.println("parsing error in extractPair: couldn't find \'" + start + "\'");
return;
}
line= line.substring(startPos+1);
int endPos= line.indexOf(' ');
if (endPos < 0) {
if (DEBUG) System.out.println("parsing error in extractPair: couldn't find end blank");
return;
}
line= line.substring(0, endPos);
int comma= line.indexOf(',');
if (comma >= 0) {
pair[0]= Integer.parseInt(line.substring(0, comma));
pair[1]= Integer.parseInt(line.substring(comma+1));
} else {
pair[0]= Integer.parseInt(line);
pair[1]= 1;
}
}
private String[] split(String line) {
List<String> l= new ArrayList<>();
StringTokenizer st= new StringTokenizer(line, "\t");
while (st.hasMoreElements()) {
String token= st.nextToken().trim();
if (token.length() > 0)
l.add(token);
}
return l.toArray(new String[l.size()]);
}
public boolean isWorkspacePatch() {
return this.fIsWorkspacePatch;
}
public boolean isGitPatch() {
return this.fIsGitPatch;
}
public DiffProject[] getDiffProjects() {
return this.fDiffProjects;
}
public FilePatch2[] getDiffs() {
return this.fDiffs;
}
public FilePatch2[] getAdjustedDiffs() {
if (!isWorkspacePatch() || this.fDiffs.length == 0)
return this.fDiffs;
List<FilePatch2> result = new ArrayList<>();
for (FilePatch2 diff : this.fDiffs) {
result.add(diff.asRelativeDiff());
}
return result.toArray(new FilePatch2[result.size()]);
}
}