/**
* Copyright (C) 2013-2014 Olaf Lessenich
* Copyright (C) 2014-2015 University of Passau, Germany
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Contributors:
* Olaf Lessenich <lessenic@fim.uni-passau.de>
* Georg Seibt <seibt@fim.uni-passau.de>
*/
package de.fosd.jdime.stats.parser;
import java.util.Scanner;
import java.util.regex.Pattern;
/**
* Contains methods for parsing code (possibly containing conflict markers) resulting from a merge.
*/
public final class Parser {
private static final Pattern conflictStart = Pattern.compile("<<<<<<<.*");
private static final Pattern conflictSep = Pattern.compile("=======");
private static final Pattern conflictEnd = Pattern.compile(">>>>>>>.*");
private static final Pattern emptyLine = Pattern.compile("\\s*");
private static final Pattern lineComment = Pattern.compile("\\s*//.*");
private static final Pattern blockComment1Line = Pattern.compile("\\s*/\\*.*?\\*/\\s*");
private static final Pattern blockCommentStart = Pattern.compile("\\s*/\\*.*");
private static final Pattern blockCommentEnd = Pattern.compile(".*?\\*/");
/**
* Utility class.
*/
private Parser() {}
/**
* Parses the given code to a list of {@link Content} objects and counts the merged and conflicting lines
* and the number of conflicts. Comments and conflicts consisting only of commented out lines will be ignored.
*
* @param code
* the piece of code to be parsed
* @return the parse result
*/
public static ParseResult parse(String code) {
Scanner s = new Scanner(code);
ParseResult res = new ParseResult();
int linesOfCode = 0;
int conflicts = 0;
int conflictingLinesOfCode = 0;
int clocBeforeConflict = 0; // cloc = conflicting lines of code
boolean inConflict = false;
boolean inLeftComment = false; // whether we were in a comment when the left part of the conflict started
boolean inLeft = true;
boolean inComment = false;
while (s.hasNextLine()) {
String line = s.nextLine();
boolean wasConflictMarker = false;
if (!matches(emptyLine, line) && !matches(lineComment, line)) {
if (matches(blockCommentStart, line)) {
if (!matches(blockComment1Line, line)) {
inComment = true;
}
} else if (matches(blockCommentEnd, line)) {
inComment = false;
} else if (matches(conflictStart, line)) {
wasConflictMarker = true;
inConflict = true;
inLeftComment = inComment;
inLeft = true;
clocBeforeConflict = conflictingLinesOfCode;
conflicts++;
} else if (matches(conflictSep, line)) {
wasConflictMarker = true;
inComment = inLeftComment;
inLeft = false;
} else if (matches(conflictEnd, line)) {
wasConflictMarker = true;
inConflict = false;
if (clocBeforeConflict == conflictingLinesOfCode) {
conflicts--; // the conflict only contained empty lines and comments
}
} else {
if (!inComment) {
linesOfCode++;
if (inConflict) {
conflictingLinesOfCode++;
}
}
}
}
if (!wasConflictMarker) {
if (inConflict) {
res.addConflictingLine(line, inLeft);
} else {
res.addMergedLine(line);
}
}
}
res.setLinesOfCode(linesOfCode);
res.setConflicts(conflicts);
res.setConflictingLinesOfCode(conflictingLinesOfCode);
return res;
}
/**
* Returns whether the given <code>Pattern</code> matches the <code>line</code>.
*
* @param p
* the <code>Pattern</code> to use
* @param line
* the line to match against the pattern
* @return true iff the <code>Pattern</code> matched
*/
private static boolean matches(Pattern p, String line) {
return p.matcher(line).matches();
}
}