fCoveredNodes;
177 |
178 | /**
179 | * Instantiate a new node finder using the given root node, the given start and the given length.
180 | *
181 | * @param root the given root node
182 | * @param start the given start
183 | * @param length the given length
184 | */
185 | public MyNodeFinder(ASTNode root, int start, int length) {
186 | MyNodeFinderVisitor nodeFinderVisitor = new MyNodeFinderVisitor(start, length);
187 | root.accept(nodeFinderVisitor);
188 | this.fCoveredNode = nodeFinderVisitor.getCoveredNode();
189 | this.fCoveringNode = nodeFinderVisitor.getCoveringNode();
190 | this.fCoveredNodes = nodeFinderVisitor.getCoveredNodes();
191 | }
192 | /**
193 | * If the AST contains nodes whose range is equal to the selection, returns the innermost of those
194 | * nodes. Otherwise, returns the first node in a preorder traversal of the AST, where the complete
195 | * node range is covered by the selection.
196 | *
197 | * Example: For a {@link SimpleType} whose name is a {@link } and a selection that equals both
198 | * nodes' range, the covered node is the SimpleName
. But if the selection is expanded
199 | * to include a whitespace before or after the SimpleType
, then the covered node is
200 | * the SimpleType
.
201 | *
202 | * @return the covered node, or null
if the selection is empty or too short to cover
203 | * an entire node
204 | */
205 | public ASTNode getCoveredNode() {
206 | return this.fCoveredNode;
207 | }
208 |
209 | /**
210 | * Returns the innermost node that fully contains the selection. A node also contains the
211 | * zero-length selection on either end.
212 | *
213 | *
If more than one node covers the selection, the returned node is the last covering node
214 | * found in a preorder traversal of the AST. This implies that for a zero-length selection between
215 | * two adjacent sibling nodes, the node on the right is returned.
216 | *
217 | * @return the covering node
218 | */
219 | public ASTNode getCoveringNode() {
220 | return this.fCoveringNode;
221 | }
222 |
223 | public List getCoveredNodes() {
224 | return this.fCoveredNodes;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/core/visitor/SimpleVisitor.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.core.visitor;
2 |
3 | import org.eclipse.jdt.core.dom.ASTVisitor;
4 | import org.eclipse.jdt.core.dom.ITypeBinding;
5 | import org.eclipse.jdt.core.dom.SimpleName;
6 | import org.eclipse.jdt.core.dom.SimpleType;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class SimpleVisitor extends ASTVisitor {
12 |
13 | private List simpleTypes;
14 | private List simpleNames;
15 |
16 | public SimpleVisitor() {
17 | this.simpleTypes = new ArrayList<>();
18 | this.simpleNames = new ArrayList<>();
19 | }
20 |
21 | @Override
22 | public boolean visit(SimpleType node) {
23 | // System.out.println("------Type--------");
24 | this.simpleTypes.add(node.getName().toString());
25 | ITypeBinding binding = node.resolveBinding();
26 | if (binding != null) {
27 | if (binding.isFromSource()) {
28 | System.out.println(binding.getQualifiedName());
29 | }
30 | }
31 | return true;
32 | }
33 |
34 | @Override
35 | public boolean visit(SimpleName node) {
36 | // System.out.println("------Name--------");
37 | this.simpleNames.add(node.getIdentifier());
38 | // IBinding binding = node.resolveBinding();
39 | ITypeBinding binding = node.resolveTypeBinding();
40 | if (binding != null) {
41 | if (binding.isFromSource()) {
42 | System.out.println(binding.getQualifiedName());
43 | }
44 | }
45 | return true;
46 | }
47 |
48 | public List getSimpleTypes() {
49 | return simpleTypes;
50 | }
51 |
52 | public List getSimpleNames() {
53 | return simpleNames;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/evaluation/DataMiner.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.evaluation;
2 |
3 | import com.github.smartcommit.util.Utils;
4 | import com.mongodb.BasicDBObject;
5 | import com.mongodb.MongoClient;
6 | import com.mongodb.MongoClientURI;
7 | import com.mongodb.client.MongoCollection;
8 | import com.mongodb.client.MongoCursor;
9 | import com.mongodb.client.MongoDatabase;
10 | import org.apache.log4j.BasicConfigurator;
11 | import org.apache.log4j.Level;
12 | import org.bson.Document;
13 | import org.eclipse.jgit.api.Git;
14 | import org.eclipse.jgit.lib.Repository;
15 | import org.eclipse.jgit.revwalk.RevCommit;
16 | import org.eclipse.jgit.revwalk.RevWalk;
17 | import org.refactoringminer.api.GitService;
18 | import org.refactoringminer.util.GitServiceImpl;
19 |
20 | import java.io.BufferedWriter;
21 | import java.io.File;
22 | import java.io.FileWriter;
23 | import java.io.IOException;
24 | import java.nio.file.Path;
25 | import java.nio.file.Paths;
26 | import java.text.BreakIterator;
27 | import java.util.*;
28 | import java.util.regex.Matcher;
29 | import java.util.regex.Pattern;
30 |
31 | /** Mine atomic commits and also composite commits from specified repos */
32 | public class DataMiner {
33 | // home dir of the local machine
34 | private static final String homeDir = System.getProperty("user.home") + File.separator;
35 | // root dir of all data and results
36 | private static final String dataDir = homeDir + "/smartcommit/";
37 | private static final String mongoDBUrl = "mongodb://localhost:27017";
38 |
39 | public static void main(String[] args) {
40 | BasicConfigurator.configure();
41 | org.apache.log4j.Logger.getRootLogger().setLevel(Level.WARN);
42 |
43 | String repoDir = dataDir + "repos/";
44 |
45 | String repoName = "rocketmq";
46 | String repoPath = repoDir + repoName;
47 | // number of examined commits
48 | int numCommits = 0;
49 | // !merge && fix/close/resolve/issue && #issueid/number
50 | // composite: (# > 1 || #predicates > 1 || and/also/plus/too/other || bullet list))
51 |
52 | GitService gitService = new GitServiceImpl();
53 | List atomicCommits = new ArrayList<>();
54 | List compositeCommits = new ArrayList<>();
55 | Pattern issuePattern =
56 | Pattern.compile("#[0-9]+?\\s+"); // Other special format: jruby-XXX xstr-XXX storm-XXX
57 | Pattern bulletPattern = Pattern.compile("\\*|-\\s+");
58 | try (Repository repository = gitService.openRepository(repoPath)) {
59 | // iterate commits from master:HEAD
60 | try (RevWalk walk = gitService.createAllRevsWalk(repository, repository.getBranch())) {
61 | System.out.println("Mining repo: " + repoName + " on branch: " + repository.getBranch());
62 | for (RevCommit commit : walk) {
63 | numCommits += 1;
64 | // no merge commits
65 | if (commit.getParentCount() == 1) {
66 | String msg = commit.getFullMessage().toLowerCase();
67 | // focused on issues
68 | if (anyMatch(msg, new String[] {"issue", "#", "fix", "close", "resolve", "solve"})) {
69 |
70 | // extract issue ids
71 | Set issueIDs = new HashSet<>();
72 | Matcher issueMatcher = issuePattern.matcher(msg);
73 | while (issueMatcher.find()) {
74 | issueIDs.add(issueMatcher.group());
75 | }
76 |
77 | Matcher bulletMatcher = bulletPattern.matcher(msg);
78 | int bulletNum = 0;
79 | while (bulletMatcher.find()) {
80 | bulletNum++;
81 | }
82 |
83 | if (bulletNum > 1
84 | || containsMultipleVerbs(commit.getFullMessage())
85 | || issueIDs.size() > 1) {
86 | compositeCommits.add(commit);
87 | System.out.println("[C]" + commit.getName());
88 | } else {
89 | atomicCommits.add(commit);
90 | System.out.println("[A]" + commit.getName());
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 | System.out.println("[Total]: " + numCommits);
98 | System.out.println(
99 | "[Atomic]: "
100 | + atomicCommits.size()
101 | + "("
102 | + Utils.formatDouble((double) atomicCommits.size() * 100 / numCommits)
103 | + "%)");
104 | // save results into mongodb
105 | saveSamplesInDB(repoName, "atomic", atomicCommits);
106 | // write results into csv file
107 | // saveSamplesInCSV(atomicCommits, resultsDir + repoName + "_atomic.csv");
108 | } catch (Exception e) {
109 | e.printStackTrace();
110 | }
111 | }
112 |
113 | private static boolean containsMultipleVerbs(String msg) {
114 | List sentences = new ArrayList<>();
115 | BreakIterator iterator = BreakIterator.getSentenceInstance(Locale.US);
116 | iterator.setText(msg);
117 | int start = iterator.first();
118 | for (int end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator.next()) {
119 | sentences.add(msg.substring(start, end).toLowerCase());
120 | }
121 | String[] verbs =
122 | new String[] {
123 | "add",
124 | "fix",
125 | "change",
126 | "modif",
127 | "remove",
128 | "delete",
129 | "refactor",
130 | "format",
131 | "rename",
132 | "reformat",
133 | "patch",
134 | "clean"
135 | };
136 | // String[] words = msg.split("\\s+");
137 | int num = 0;
138 | for (String sentence : sentences) {
139 | if (anyMatch(sentence, verbs)) {
140 | num += 1;
141 | if (num > 1) {
142 | return true;
143 | }
144 | }
145 | }
146 | return false;
147 | }
148 |
149 | private static boolean anyMatch(String str, String[] keywords) {
150 | return Arrays.stream(keywords).parallel().anyMatch(str::contains);
151 | }
152 |
153 | /**
154 | * Save samples in mongodb
155 | *
156 | * @param repoName
157 | * @param dbName
158 | * @param commits
159 | */
160 | private static void saveSamplesInDB(String repoName, String dbName, List commits) {
161 | MongoClientURI connectionString = new MongoClientURI(mongoDBUrl);
162 | MongoClient mongoClient = new MongoClient(connectionString);
163 | MongoDatabase db = mongoClient.getDatabase(dbName);
164 | MongoCollection col = db.getCollection(repoName);
165 | // !!! drop the last testing results
166 | col.drop();
167 |
168 | for (RevCommit commit : commits) {
169 | Document commitDoc = new Document("repo_name", repoName);
170 | commitDoc
171 | .append("commit_id", commit.getName())
172 | .append("commit_time", commit.getAuthorIdent().getWhen())
173 | .append("committer_name", commit.getAuthorIdent().getName())
174 | .append("committer_email", commit.getAuthorIdent().getEmailAddress())
175 | .append("commit_msg", commit.getFullMessage());
176 |
177 | col.insertOne(commitDoc);
178 | }
179 | mongoClient.close();
180 | }
181 |
182 | /**
183 | * Save samples in csv files
184 | *
185 | * @param commits
186 | * @param resultFilePath
187 | * @throws IOException
188 | */
189 | private static void saveSamplesInCSV(List commits, String resultFilePath)
190 | throws IOException {
191 | File file = new File(resultFilePath);
192 |
193 | if (!file.exists() && !file.isDirectory()) {
194 | // file.mkdirs();
195 | file.createNewFile();
196 | }
197 |
198 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
199 | for (RevCommit commit : commits) {
200 | bw.write(commit.getName() + "~~" + commit.getFullMessage().trim().replaceAll("\n", ""));
201 | bw.newLine();
202 | bw.flush();
203 | }
204 | }
205 | }
206 | /**
207 | * Yet another way to list all commits
208 | *
209 | * @param repoPath
210 | */
211 | private void listCommits(String repoPath) {
212 | Path path = Paths.get(repoPath);
213 | try (Git git = Git.open(path.toFile())) {
214 | Iterable commits = git.log().all().call();
215 | Repository repository = git.getRepository();
216 | String branch = repository.getBranch();
217 | System.out.println(branch);
218 | for (Iterator iter = commits.iterator(); iter.hasNext(); ) {
219 | RevCommit commit = iter.next();
220 | System.out.println(commit.getAuthorIdent());
221 | System.out.println(commit.getFullMessage());
222 | }
223 | } catch (Exception e) {
224 | e.printStackTrace();
225 | }
226 | }
227 |
228 | private static void getAllEmails(String repoName) {
229 | System.out.println("Repo: " + repoName);
230 | MongoClientURI server = new MongoClientURI(mongoDBUrl);
231 | MongoClient serverClient = new MongoClient(server);
232 |
233 | try {
234 | MongoDatabase db = serverClient.getDatabase("smartcommit");
235 | MongoCollection col = db.getCollection("contacts");
236 | BasicDBObject condition =
237 | new BasicDBObject(
238 | "repos", new BasicDBObject("$elemMatch", new BasicDBObject("repo", repoName)));
239 | // Bson condition = Filters.elemMatch("repos", Fil);
240 |
241 | try (MongoCursor cursor = col.find(condition).iterator()) {
242 | while (cursor.hasNext()) {
243 | Document doc = cursor.next();
244 | System.out.println((doc.get("email").toString()));
245 | }
246 | }
247 |
248 | serverClient.close();
249 | } catch (Exception e) {
250 | e.printStackTrace();
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/io/DiffGraphExporter.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.io;
2 |
3 | import com.github.smartcommit.model.diffgraph.DiffEdge;
4 | import com.github.smartcommit.model.diffgraph.DiffNode;
5 | import org.jgrapht.Graph;
6 | import org.jgrapht.io.ComponentAttributeProvider;
7 | import org.jgrapht.io.ComponentNameProvider;
8 | import org.jgrapht.io.DOTExporter;
9 | import org.jgrapht.io.ExportException;
10 |
11 | import java.io.StringWriter;
12 | import java.io.Writer;
13 |
14 | public class DiffGraphExporter {
15 | public static String exportAsDotWithType(Graph graph) {
16 | try {
17 | // use helper classes to define how vertices should be rendered,
18 | // adhering to the DOT language restrictions
19 | ComponentNameProvider vertexIdProvider = diffNode -> diffNode.getId().toString();
20 | ComponentNameProvider vertexLabelProvider = diffNode -> diffNode.getIndex();
21 | ComponentAttributeProvider vertexAttributeProvider = new DiffTypeProvider();
22 |
23 | ComponentNameProvider edgeLabelProvider =
24 | edge -> edge.getType().asString() + "(" + edge.getWeight().toString() + ")";
25 | ComponentAttributeProvider edgeAttributeProvider = new DiffTypeProvider();
26 | org.jgrapht.io.GraphExporter exporter =
27 | new DOTExporter<>(
28 | vertexIdProvider,
29 | vertexLabelProvider,
30 | edgeLabelProvider,
31 | vertexAttributeProvider,
32 | edgeAttributeProvider);
33 | Writer writer = new StringWriter();
34 | exporter.exportGraph(graph, writer);
35 | return writer.toString();
36 |
37 | } catch (ExportException e) {
38 | e.printStackTrace();
39 | return "";
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/io/DiffTypeProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.io;
2 |
3 | import com.github.smartcommit.model.diffgraph.DiffEdge;
4 | import com.github.smartcommit.model.diffgraph.DiffNode;
5 | import org.jgrapht.io.Attribute;
6 | import org.jgrapht.io.AttributeType;
7 | import org.jgrapht.io.ComponentAttributeProvider;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class DiffTypeProvider implements ComponentAttributeProvider {
13 | @Override
14 | public Map getComponentAttributes(Object component) {
15 | if (component instanceof DiffNode) {
16 | DiffNode node = (DiffNode) component;
17 | Map map = new HashMap<>();
18 | map.put("color", new DiffNodeColorAttribute(node));
19 | map.put("shape", new DiffNodeShapeAttribute(node));
20 | return map;
21 | }
22 | if (component instanceof DiffEdge) {
23 | DiffEdge edge = (DiffEdge) component;
24 | Map map = new HashMap<>();
25 | map.put("type", new DiffEdgeTypeAttribute(edge));
26 | map.put("color", new DiffEdgeColorAttribute(edge));
27 | map.put("style", new DiffEdgeStyleAttribute(edge));
28 | return map;
29 | }
30 | return null;
31 | }
32 | }
33 |
34 | class DiffNodeShapeAttribute implements Attribute {
35 | private DiffNode node;
36 |
37 | public DiffNodeShapeAttribute(DiffNode node) {
38 | this.node = node;
39 | }
40 |
41 | @Override
42 | public String getValue() {
43 | return "record";
44 | }
45 |
46 | @Override
47 | public AttributeType getType() {
48 | return AttributeType.STRING;
49 | }
50 | }
51 |
52 | class DiffNodeColorAttribute implements Attribute {
53 | private DiffNode node;
54 |
55 | public DiffNodeColorAttribute(DiffNode node) {
56 | this.node = node;
57 | }
58 |
59 | @Override
60 | public String getValue() {
61 | return "blue";
62 | }
63 |
64 | @Override
65 | public AttributeType getType() {
66 | return AttributeType.STRING;
67 | }
68 | }
69 |
70 | class DiffEdgeTypeAttribute implements Attribute {
71 | private DiffEdge edge;
72 |
73 | public DiffEdgeTypeAttribute(DiffEdge edge) {
74 | this.edge = edge;
75 | }
76 |
77 | @Override
78 | public String getValue() {
79 | return edge.getType().asString();
80 | }
81 |
82 | @Override
83 | public AttributeType getType() {
84 | return AttributeType.STRING;
85 | }
86 | }
87 |
88 | class DiffEdgeColorAttribute implements Attribute {
89 | private DiffEdge edge;
90 |
91 | public DiffEdgeColorAttribute(DiffEdge edge) {
92 | this.edge = edge;
93 | }
94 |
95 | @Override
96 | public String getValue() {
97 | return edge.getType().isConstraint() ? "red" : "black";
98 | }
99 |
100 | @Override
101 | public AttributeType getType() {
102 | return AttributeType.STRING;
103 | }
104 | }
105 |
106 | class DiffEdgeStyleAttribute implements Attribute {
107 | private DiffEdge edge;
108 |
109 | public DiffEdgeStyleAttribute(DiffEdge edge) {
110 | this.edge = edge;
111 | }
112 |
113 | @Override
114 | public String getValue() {
115 | return edge.getType().isConstraint() ? "solid" : "dashed";
116 | }
117 |
118 | @Override
119 | public AttributeType getType() {
120 | return AttributeType.STRING;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/io/GraphExporter.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.io;
2 |
3 | import com.github.smartcommit.model.graph.Edge;
4 | import com.github.smartcommit.model.graph.Node;
5 | import com.github.smartcommit.util.Utils;
6 | import org.jgrapht.Graph;
7 | import org.jgrapht.io.ComponentAttributeProvider;
8 | import org.jgrapht.io.ComponentNameProvider;
9 | import org.jgrapht.io.DOTExporter;
10 | import org.jgrapht.io.ExportException;
11 |
12 | import java.io.StringWriter;
13 | import java.io.Writer;
14 |
15 | public class GraphExporter {
16 | /** Export a graph into DOT format. */
17 | public static String exportAsDot(Graph graph) {
18 | try {
19 | // use helper classes to define how vertices should be rendered,
20 | // adhering to the DOT language restrictions
21 | ComponentNameProvider vertexIdProvider = node -> node.getId().toString();
22 | ComponentNameProvider vertexLabelProvider = node -> node.getIdentifier();
23 | ComponentNameProvider edgeLabelProvider = edge -> edge.getType().asString();
24 | org.jgrapht.io.GraphExporter exporter =
25 | new DOTExporter<>(vertexIdProvider, vertexLabelProvider, edgeLabelProvider);
26 | Writer writer = new StringWriter();
27 | exporter.exportGraph(graph, writer);
28 | return writer.toString();
29 |
30 | } catch (ExportException e) {
31 | e.printStackTrace();
32 | return "";
33 | }
34 | }
35 |
36 | public static String exportAsDotWithType(Graph graph) {
37 | try {
38 | // use helper classes to define how vertices should be rendered,
39 | // adhering to the DOT language restrictions
40 | ComponentNameProvider vertexIdProvider = node -> node.getId().toString();
41 | ComponentNameProvider vertexLabelProvider =
42 | node ->
43 | node.isInDiffHunk
44 | ? node.getIdentifier() + "(" + node.diffHunkIndex + ")"
45 | : node.getIdentifier();
46 | ComponentAttributeProvider vertexAttributeProvider = new TypeProvider();
47 |
48 | ComponentNameProvider edgeLabelProvider =
49 | edge -> edge.getType().asString() + "(" + edge.getWeight() + ")";
50 | ComponentAttributeProvider edgeAttributeProvider = new TypeProvider();
51 | org.jgrapht.io.GraphExporter exporter =
52 | new DOTExporter<>(
53 | vertexIdProvider,
54 | vertexLabelProvider,
55 | edgeLabelProvider,
56 | vertexAttributeProvider,
57 | edgeAttributeProvider);
58 | Writer writer = new StringWriter();
59 | exporter.exportGraph(graph, writer);
60 | return writer.toString();
61 |
62 | } catch (ExportException e) {
63 | e.printStackTrace();
64 | return "";
65 | }
66 | }
67 |
68 | /**
69 | * Print the graph to console for debugging
70 | *
71 | * @param graph
72 | */
73 | public static void printAsDot(Graph graph) {
74 | System.out.println(exportAsDotWithType(graph));
75 | }
76 |
77 | /**
78 | * Save the exported dot to file
79 | *
80 | * @param graph
81 | */
82 | public static void saveAsDot(Graph graph, String filePath) {
83 | Utils.writeStringToFile(exportAsDotWithType(graph), filePath);
84 | }
85 |
86 | public static void printVertexAndEdge(Graph graph) {
87 | for (Node node : graph.vertexSet()) {
88 | System.out.println(node);
89 | }
90 | System.out.println("------------------------------");
91 | for (Edge edge : graph.edgeSet()) {
92 | Node source = graph.getEdgeSource(edge);
93 | Node target = graph.getEdgeTarget(edge);
94 | System.out.println(
95 | source.getIdentifier() + " " + edge.getType().asString() + " " + target.getIdentifier());
96 | }
97 | System.out.println("------------------------------");
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/io/TypeProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.io;
2 |
3 | import com.github.smartcommit.model.graph.Edge;
4 | import com.github.smartcommit.model.graph.Node;
5 | import org.jgrapht.io.Attribute;
6 | import org.jgrapht.io.AttributeType;
7 | import org.jgrapht.io.ComponentAttributeProvider;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class TypeProvider implements ComponentAttributeProvider {
13 | @Override
14 | public Map getComponentAttributes(Object component) {
15 | if (component instanceof Node) {
16 | Node node = (Node) component;
17 | Map map = new HashMap<>();
18 | map.put("type", new NodeTypeAttribute(node));
19 | map.put("color", new NodeColorAttribute(node));
20 | map.put("shape", new NodeShapeAttribute(node));
21 | return map;
22 | }
23 | if (component instanceof Edge) {
24 | Edge edge = (Edge) component;
25 | Map map = new HashMap<>();
26 | map.put("type", new EdgeTypeAttribute(edge));
27 | map.put("color", new EdgeColorAttribute(edge));
28 | map.put("style", new EdgeStyleAttribute(edge));
29 | return map;
30 | }
31 | return null;
32 | }
33 | }
34 |
35 | class NodeShapeAttribute implements Attribute {
36 | private Node node;
37 |
38 | public NodeShapeAttribute(Node node) {
39 | this.node = node;
40 | }
41 |
42 | @Override
43 | public String getValue() {
44 | switch (node.getType()) {
45 | case PACKAGE:
46 | return "folder";
47 | case CLASS:
48 | return "component";
49 | case INTERFACE:
50 | return "polygon";
51 | case ENUM:
52 | return "septagon";
53 | case ANNOTATION:
54 | return "cds";
55 | case METHOD:
56 | return "ellipse";
57 | case FIELD:
58 | return "box";
59 | case HUNK:
60 | return "diamond";
61 | default:
62 | return "";
63 | }
64 | }
65 |
66 | @Override
67 | public AttributeType getType() {
68 | return AttributeType.STRING;
69 | }
70 | }
71 |
72 | class NodeTypeAttribute implements Attribute {
73 | private Node node;
74 |
75 | public NodeTypeAttribute(Node node) {
76 | this.node = node;
77 | }
78 |
79 | @Override
80 | public String getValue() {
81 | return node.getType().asString();
82 | }
83 |
84 | @Override
85 | public AttributeType getType() {
86 | return AttributeType.STRING;
87 | }
88 | }
89 |
90 | class NodeColorAttribute implements Attribute {
91 | private Node node;
92 |
93 | public NodeColorAttribute(Node node) {
94 | this.node = node;
95 | }
96 |
97 | @Override
98 | public String getValue() {
99 | return node.isInDiffHunk ? "red" : "black";
100 | }
101 |
102 | @Override
103 | public AttributeType getType() {
104 | return AttributeType.STRING;
105 | }
106 | }
107 |
108 | class EdgeTypeAttribute implements Attribute {
109 | private Edge edge;
110 |
111 | public EdgeTypeAttribute(Edge edge) {
112 | this.edge = edge;
113 | }
114 |
115 | @Override
116 | public String getValue() {
117 | return edge.getType().asString();
118 | }
119 |
120 | @Override
121 | public AttributeType getType() {
122 | return AttributeType.STRING;
123 | }
124 | }
125 |
126 | class EdgeColorAttribute implements Attribute {
127 | private Edge edge;
128 |
129 | public EdgeColorAttribute(Edge edge) {
130 | this.edge = edge;
131 | }
132 |
133 | @Override
134 | public String getValue() {
135 | return edge.getType().isStructural() ? "black" : "blue";
136 | }
137 |
138 | @Override
139 | public AttributeType getType() {
140 | return AttributeType.STRING;
141 | }
142 | }
143 |
144 | class EdgeStyleAttribute implements Attribute {
145 | private Edge edge;
146 |
147 | public EdgeStyleAttribute(Edge edge) {
148 | this.edge = edge;
149 | }
150 |
151 | @Override
152 | public String getValue() {
153 | return edge.getType().isStructural() ? "solid" : "dashed";
154 | }
155 |
156 | @Override
157 | public AttributeType getType() {
158 | return AttributeType.STRING;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/Action.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.constant.Operation;
4 |
5 | /** One of the semantic change actions of the diff hunk */
6 | public class Action {
7 | private Operation operation;
8 | private String typeFrom = "";
9 | private String labelFrom = "";
10 |
11 | // if modify the type or label
12 | private String typeTo = "";
13 | private String labelTo = "";
14 |
15 | public Action(Operation operation, String typeFrom, String labelFrom) {
16 | this.operation = operation;
17 | this.typeFrom = typeFrom;
18 | this.labelFrom = labelFrom.trim();
19 | this.typeTo = "";
20 | this.labelTo = "";
21 | }
22 |
23 | public Action(
24 | Operation operation, String typeFrom, String labelFrom, String typeTo, String labelTo) {
25 | this.operation = operation == null ? Operation.UKN : operation;
26 | this.typeFrom = typeFrom == null ? "" : typeFrom;
27 | this.labelFrom = labelFrom == null ? "" : labelFrom.trim();
28 | this.typeTo = typeTo == null ? "" : typeTo;
29 | this.labelTo = labelTo == null ? "" : labelTo.trim();
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | StringBuilder builder = new StringBuilder();
35 | builder.append(operation);
36 | builder
37 | .append(typeFrom.isEmpty() ? "" : " " + typeFrom)
38 | .append(labelFrom.isEmpty() ? "" : " \"" + labelFrom + "\"");
39 | if (!typeFrom.equals(typeTo)) {
40 | builder.append(typeTo.isEmpty() ? "" : " To " + typeTo);
41 | if (!labelFrom.equals(labelTo)) {
42 | builder.append(labelTo.isEmpty() ? "" : ": \"" + labelTo + "\"");
43 | }
44 | } else {
45 | if (!labelFrom.equals(labelTo)) {
46 | builder.append(labelTo.isEmpty() ? "" : " To: \"" + labelTo + "\"");
47 | }
48 | }
49 |
50 | builder.append(".");
51 |
52 | return builder.toString();
53 | }
54 |
55 | @Override
56 | public boolean equals(Object obj) {
57 | if (obj == this) {
58 | return true;
59 | }
60 |
61 | if (!(obj instanceof Action)) {
62 | return false;
63 | }
64 |
65 | Action a = (Action) obj;
66 | return a.operation.equals(this.operation)
67 | && a.typeFrom.equals(this.typeFrom)
68 | && a.typeTo.equals(this.typeTo)
69 | && a.labelFrom.equals(this.labelFrom)
70 | && a.labelTo.equals(this.labelTo);
71 | }
72 |
73 | public int getOperationIndex() {
74 | return operation.index;
75 | }
76 |
77 | public Operation getOperation() {
78 | return operation;
79 | }
80 |
81 | public String getTypeFrom() {
82 | return typeFrom;
83 | }
84 |
85 | public String getLabelFrom() {
86 | return labelFrom;
87 | }
88 |
89 | public String getTypeTo() {
90 | return typeTo;
91 | }
92 |
93 | public String getLabelTo() {
94 | return labelTo;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/DiffFile.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.constant.FileStatus;
4 | import com.github.smartcommit.model.constant.FileType;
5 | import com.github.smartcommit.model.constant.Version;
6 |
7 | import java.nio.charset.Charset;
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | public class DiffFile {
14 | private String repoID;
15 | private String repoName;
16 | private String fileID;
17 | private Charset charset;
18 | private Integer index; // the index of the diff file in the current repo, start from 0
19 | private FileStatus status;
20 | private FileType fileType;
21 | private String baseRelativePath;
22 | private String currentRelativePath;
23 | private String baseContent;
24 | private String currentContent;
25 | private String description;
26 | private Map diffHunksMap;
27 | private transient List diffHunks;
28 | // lines from the raw output of git-diff (for patch generation)
29 | private List rawHeaders = new ArrayList<>();
30 |
31 | public DiffFile(
32 | Integer index,
33 | FileStatus status,
34 | FileType fileType,
35 | Charset charset,
36 | String baseRelativePath,
37 | String currentRelativePath,
38 | String baseContent,
39 | String currentContent) {
40 | this.index = index;
41 | this.status = status;
42 | this.fileType = fileType;
43 | this.charset = charset;
44 | this.baseRelativePath = baseRelativePath;
45 | this.currentRelativePath = currentRelativePath;
46 | this.baseContent = baseContent;
47 | this.currentContent = currentContent;
48 | this.description = status.label;
49 | this.diffHunks = new ArrayList<>();
50 | this.diffHunksMap = new HashMap<>();
51 | }
52 |
53 | /** Constructor to clone object for json serialization */
54 | public DiffFile(
55 | String repoID,
56 | String repoName,
57 | String fileID,
58 | Integer index,
59 | FileStatus status,
60 | FileType fileType,
61 | String baseRelativePath,
62 | String currentRelativePath,
63 | String baseContent,
64 | String currentContent,
65 | Map diffHunksMap) {
66 | this.repoID = repoID;
67 | this.repoName = repoName;
68 | this.fileID = fileID;
69 | this.index = index;
70 | this.status = status;
71 | this.description = status.label;
72 | this.fileType = fileType;
73 | this.baseRelativePath = baseRelativePath;
74 | this.currentRelativePath = currentRelativePath;
75 | this.baseContent = baseContent;
76 | this.currentContent = currentContent;
77 | this.diffHunksMap = diffHunksMap;
78 | }
79 |
80 | public String getRepoID() {
81 | return repoID;
82 | }
83 |
84 | public String getRepoName() {
85 | return repoName;
86 | }
87 |
88 | public String getFileID() {
89 | return fileID;
90 | }
91 |
92 | public Charset getCharset() {
93 | return charset;
94 | }
95 |
96 | public void setRepoID(String repoID) {
97 | this.repoID = repoID;
98 | }
99 |
100 | public void setRepoName(String repoName) {
101 | this.repoName = repoName;
102 | }
103 |
104 | public void setFileID(String fileID) {
105 | this.fileID = fileID;
106 | }
107 |
108 | public List getRawHeaders() {
109 | return rawHeaders;
110 | }
111 |
112 | public void setRawHeaders(List rawHeaders) {
113 | this.rawHeaders = rawHeaders;
114 | }
115 |
116 | public FileStatus getStatus() {
117 | return status;
118 | }
119 |
120 | public FileType getFileType() {
121 | return fileType;
122 | }
123 |
124 | public String getBaseRelativePath() {
125 | return baseRelativePath;
126 | }
127 |
128 | public String getCurrentRelativePath() {
129 | return currentRelativePath;
130 | }
131 |
132 | public String getRelativePathOf(Version version) {
133 | if (version.equals(Version.BASE)) {
134 | return getBaseRelativePath();
135 | } else if (version.equals(Version.CURRENT)) {
136 | return getCurrentRelativePath();
137 | }
138 | return "";
139 | }
140 |
141 | public String getBaseContent() {
142 | return baseContent;
143 | }
144 |
145 | public String getCurrentContent() {
146 | return currentContent;
147 | }
148 |
149 | public void setIndex(Integer index) {
150 | this.index = index;
151 | }
152 |
153 | public Integer getIndex() {
154 | return index;
155 | }
156 |
157 | public List getDiffHunks() {
158 | return diffHunks;
159 | }
160 |
161 | public Map getDiffHunksMap() {
162 | return diffHunksMap;
163 | }
164 |
165 | public void setDiffHunksMap(Map diffHunksMap) {
166 | this.diffHunksMap = diffHunksMap;
167 | }
168 |
169 | public void setDiffHunks(List diffHunks) {
170 | this.diffHunks = diffHunks;
171 | }
172 |
173 | /**
174 | * Clone the object for json serialization
175 | *
176 | * @return
177 | */
178 | public DiffFile shallowClone() {
179 | DiffFile diffFile =
180 | new DiffFile(
181 | repoID,
182 | repoName,
183 | fileID,
184 | index,
185 | status,
186 | fileType,
187 | baseRelativePath,
188 | currentRelativePath,
189 | "",
190 | "",
191 | diffHunksMap);
192 | diffFile.setRawHeaders(this.rawHeaders);
193 | return diffFile;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/DiffHunk.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.constant.ChangeType;
4 | import com.github.smartcommit.model.constant.ContentType;
5 | import com.github.smartcommit.model.constant.FileType;
6 | import com.github.smartcommit.model.constant.Version;
7 | import org.apache.commons.lang3.tuple.Pair;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | public class DiffHunk {
13 | private String repoID;
14 | private String repoName;
15 | private String fileID;
16 | private String diffHunkID;
17 | private String commitID;
18 |
19 | private Integer fileIndex; // the index of the diff file
20 | private Integer index; // the index of the diff hunk in the current file diff, start from 0
21 | private Hunk baseHunk;
22 | private Hunk currentHunk;
23 | private FileType fileType;
24 | private ChangeType changeType;
25 | private transient List astActions = new ArrayList<>();
26 | private transient List refActions = new ArrayList<>();
27 | private String description = "";
28 |
29 | // lines from the raw output of git-diff (for patch generation)
30 | private List rawDiffs = new ArrayList<>();
31 |
32 | public DiffHunk(
33 | Integer index, FileType fileType, ChangeType changeType, Hunk baseHunk, Hunk currentHunk) {
34 | this.index = index;
35 | this.fileType = fileType;
36 | this.baseHunk = baseHunk;
37 | this.currentHunk = currentHunk;
38 | this.changeType = changeType;
39 | }
40 |
41 | public DiffHunk(
42 | Integer index,
43 | FileType fileType,
44 | ChangeType changeType,
45 | Hunk baseHunk,
46 | Hunk currentHunk,
47 | String description) {
48 | this.index = index;
49 | this.fileType = fileType;
50 | this.baseHunk = baseHunk;
51 | this.currentHunk = currentHunk;
52 | this.changeType = changeType;
53 | this.description = description;
54 | }
55 |
56 | public Integer getIndex() {
57 | return index;
58 | }
59 |
60 | public Hunk getBaseHunk() {
61 | return baseHunk;
62 | }
63 |
64 | public Hunk getCurrentHunk() {
65 | return currentHunk;
66 | }
67 |
68 | public String getRepoID() {
69 | return repoID;
70 | }
71 |
72 | public String getFileID() {
73 | return fileID;
74 | }
75 |
76 | public void setFileID(String fileID) {
77 | this.fileID = fileID;
78 | }
79 |
80 | public void setRepoID(String repoID) {
81 | this.repoID = repoID;
82 | }
83 |
84 | public String getRepoName() {
85 | return repoName;
86 | }
87 |
88 | public void setRepoName(String repoName) {
89 | this.repoName = repoName;
90 | }
91 |
92 | public String getDiffHunkID() {
93 | return diffHunkID;
94 | }
95 |
96 | public void setDiffHunkID(String diffHunkID) {
97 | this.diffHunkID = diffHunkID;
98 | }
99 |
100 | public String getCommitID() {
101 | return commitID;
102 | }
103 |
104 | public void setCommitID(String commitID) {
105 | this.commitID = commitID;
106 | }
107 |
108 | public Integer getBaseStartLine() {
109 | return baseHunk.getStartLine();
110 | }
111 |
112 | public Integer getBaseEndLine() {
113 | return baseHunk.getEndLine();
114 | }
115 |
116 | public Integer getCurrentStartLine() {
117 | return currentHunk.getStartLine();
118 | }
119 |
120 | public Integer getCurrentEndLine() {
121 | return currentHunk.getEndLine();
122 | }
123 |
124 | public Pair getCodeRangeOf(Version version) {
125 | if (version.equals(Version.BASE)) {
126 | return Pair.of(getBaseStartLine(), getBaseEndLine());
127 | } else if (version.equals(Version.CURRENT)) {
128 | return Pair.of(getCurrentStartLine(), getCurrentEndLine());
129 | }
130 | return Pair.of(-1, -1);
131 | }
132 |
133 | public Integer getFileIndex() {
134 | return fileIndex;
135 | }
136 |
137 | public void setFileIndex(Integer fileIndex) {
138 | this.fileIndex = fileIndex;
139 | }
140 |
141 | public String getUniqueIndex() {
142 | return fileIndex + ":" + index;
143 | }
144 |
145 | public FileType getFileType() {
146 | return fileType;
147 | }
148 |
149 | public ChangeType getChangeType() {
150 | return changeType;
151 | }
152 |
153 | public List getRawDiffs() {
154 | return rawDiffs;
155 | }
156 |
157 | public void setRawDiffs(List rawDiffs) {
158 | this.rawDiffs = rawDiffs;
159 | }
160 |
161 | public List getAstActions() {
162 | return astActions;
163 | }
164 |
165 | public List getRefActions() {
166 | return refActions;
167 | }
168 |
169 | public void addASTAction(Action action) {
170 | if (!astActions.contains(action)) {
171 | astActions.add(action);
172 | }
173 | }
174 |
175 | public void setAstActions(List astActions) {
176 | this.astActions = astActions;
177 | }
178 |
179 | public void addRefAction(Action action) {
180 | if (!refActions.contains(action)) {
181 | refActions.add(action);
182 | }
183 | }
184 |
185 | public void setRefActions(List refActions) {
186 | this.refActions = refActions;
187 | }
188 |
189 | public String getUUID() {
190 | return fileID + ":" + diffHunkID;
191 | }
192 |
193 | public boolean containsCode() {
194 | return baseHunk.getContentType().equals(ContentType.CODE)
195 | || baseHunk.getContentType().equals(ContentType.IMPORT)
196 | || currentHunk.getContentType().equals(ContentType.CODE)
197 | || currentHunk.getContentType().equals(ContentType.IMPORT);
198 | }
199 |
200 | /**
201 | * Generate a string description from the actions
202 | *
203 | * @return
204 | */
205 | public void generateDescription() {
206 | StringBuilder builder = new StringBuilder();
207 | for (Action action : astActions) {
208 | builder.append(action.toString()).append(System.lineSeparator());
209 | }
210 | for (Action action : refActions) {
211 | builder.append(action.toString()).append(System.lineSeparator());
212 | }
213 | description = builder.toString();
214 | }
215 |
216 | public String getDescription() {
217 | if (description.isEmpty()) {
218 | generateDescription();
219 | }
220 | return description;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/EntityPool.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.entity.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public class EntityPool {
9 | private String srcDir;
10 | public Map classInfoMap;
11 | public Map interfaceInfoMap;
12 | public Map enumInfoMap;
13 | public Map enumConstantInfoMap;
14 | public Map annotationInfoMap;
15 | public Map methodInfoMap;
16 | public Map fieldInfoMap;
17 | public Map initBlockInfoMap; // initializer blocks
18 | public Map hunkInfoMap;
19 | // fileIndex : importedType : hunkInfo
20 | public Map> importInfoMap;
21 |
22 | public EntityPool(String srcDir) {
23 | this.srcDir = srcDir;
24 | classInfoMap = new HashMap<>();
25 | interfaceInfoMap = new HashMap<>();
26 | enumInfoMap = new HashMap<>();
27 | enumConstantInfoMap = new HashMap<>();
28 | annotationInfoMap = new HashMap<>();
29 | methodInfoMap = new HashMap<>();
30 | fieldInfoMap = new HashMap<>();
31 | initBlockInfoMap = new HashMap<>();
32 | hunkInfoMap = new HashMap<>();
33 | importInfoMap = new HashMap<>();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/Group.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.constant.GroupLabel;
4 |
5 | import java.util.ArrayList;
6 | import java.util.HashSet;
7 | import java.util.List;
8 | import java.util.Set;
9 |
10 | /** The output result, one group for one commit */
11 | public class Group {
12 | private String repoID;
13 | private String repoName;
14 | private String groupID;
15 | // fileIndex:diffHunkIndex
16 | private List diffHunkIndices;
17 | // fileID:diffHunkID
18 | // if fileID==diffHunkID, status is UNTRACKED, the whole file is a diff hunk
19 | private List diffHunkIDs;
20 |
21 | // system recommendation
22 | private GroupLabel intentLabel;
23 | private List recommendedCommitMsgs = new ArrayList<>();
24 | // user choice
25 | private String commitID = "";
26 | private String commitMsg = "";
27 |
28 | // record link categories for interpretability
29 | // transient?
30 | private Set linkCategories = new HashSet<>();
31 |
32 | public Group(
33 | String repoID, String repoName, String groupID, List diffHunkIDs, GroupLabel label) {
34 | this.repoID = repoID;
35 | this.repoName = repoName;
36 | this.groupID = groupID;
37 | this.diffHunkIndices = new ArrayList<>();
38 | this.diffHunkIDs = diffHunkIDs;
39 | this.intentLabel = label;
40 | }
41 |
42 | public Group(
43 | String repoID,
44 | String repoName,
45 | String groupID,
46 | List diffHunkIndices,
47 | List diffHunkIDs,
48 | GroupLabel label) {
49 | this.repoID = repoID;
50 | this.repoName = repoName;
51 | this.groupID = groupID;
52 | this.diffHunkIndices = diffHunkIndices;
53 | this.diffHunkIDs = diffHunkIDs;
54 | this.intentLabel = label;
55 | }
56 |
57 | public String getGroupID() {
58 | return groupID;
59 | }
60 |
61 | public List getDiffHunkIDs() {
62 | return diffHunkIDs;
63 | }
64 |
65 | public List getDiffHunkIndices() {
66 | return diffHunkIndices;
67 | }
68 |
69 | public GroupLabel getIntentLabel() {
70 | return intentLabel;
71 | }
72 |
73 | public List getRecommendedCommitMsgs() {
74 | return recommendedCommitMsgs;
75 | }
76 |
77 | public void setRecommendedCommitMsgs(List recommendedCommitMsgs) {
78 | this.recommendedCommitMsgs = recommendedCommitMsgs;
79 | }
80 |
81 | public void setIntentLabel(GroupLabel intentLabel) {
82 | this.intentLabel = intentLabel;
83 | }
84 |
85 | public String getCommitID() {
86 | return commitID;
87 | }
88 |
89 | public void setCommitID(String commitID) {
90 | this.commitID = commitID;
91 | }
92 |
93 | public String getCommitMsg() {
94 | return commitMsg;
95 | }
96 |
97 | public void setCommitMsg(String commitMsg) {
98 | this.commitMsg = commitMsg;
99 | }
100 |
101 | public void addByID(String diffID) {
102 | if (diffHunkIDs.contains(diffID)) {
103 | return;
104 | } else {
105 | diffHunkIDs.add(diffID);
106 | }
107 | }
108 |
109 | public void addByIndex(String diffIndex) {
110 | if (diffHunkIndices.contains(diffIndex)) {
111 | return;
112 | } else {
113 | diffHunkIndices.add(diffIndex);
114 | }
115 | }
116 |
117 | public Set getLinkCategories() {
118 | return linkCategories;
119 | }
120 |
121 | public void addLinkCategories(Set categories) {
122 | this.linkCategories.addAll(categories);
123 | }
124 |
125 | public void setDiffHunkIDs(List diffHunkIDs) {
126 | this.diffHunkIDs = diffHunkIDs;
127 | }
128 |
129 | @Override
130 | public String toString() {
131 | StringBuilder builder = new StringBuilder();
132 | // builder.append(intentLabel).append("\n");
133 | // builder.append(commitMsg).append("\n");
134 | builder.append("Changes: {");
135 | builder.append(String.join(", ", diffHunkIndices));
136 | builder.append("}\n");
137 | return builder.toString();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/Hunk.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model;
2 |
3 | import com.github.smartcommit.model.constant.ContentType;
4 | import com.github.smartcommit.model.constant.Version;
5 | import com.google.common.collect.Iterables;
6 | import org.eclipse.jdt.core.dom.ASTNode;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class Hunk {
12 | private String relativeFilePath;
13 | private Integer startLine;
14 | private Integer endLine;
15 | private Version version;
16 | private ContentType contentType;
17 | private List codeSnippet;
18 | private transient List coveredNodes;
19 |
20 | public Hunk(
21 | Version version,
22 | String relativeFilePath,
23 | Integer startLine,
24 | Integer endLine,
25 | ContentType contentType,
26 | List codeSnippet) {
27 | this.version = version;
28 | this.relativeFilePath = relativeFilePath;
29 | this.startLine = startLine;
30 | this.endLine = endLine;
31 | this.contentType = contentType;
32 | this.codeSnippet = codeSnippet;
33 | this.coveredNodes = new ArrayList<>();
34 | }
35 |
36 | public Version getVersion() {
37 | return version;
38 | }
39 |
40 | public String getRelativeFilePath() {
41 | return relativeFilePath;
42 | }
43 |
44 | public Integer getStartLine() {
45 | return startLine;
46 | }
47 |
48 | public Integer getEndLine() {
49 | return endLine;
50 | }
51 |
52 | public List getCodeSnippet() {
53 | return codeSnippet;
54 | }
55 |
56 | public ContentType getContentType() {
57 | return contentType;
58 | }
59 |
60 | public List getCoveredNodes() {
61 | return coveredNodes;
62 | }
63 |
64 | public void setCoveredNodes(List coveredNodes) {
65 | this.coveredNodes = coveredNodes;
66 | }
67 |
68 | /**
69 | * Get the length of the last line in the code snippets
70 | *
71 | * @return
72 | */
73 | public int getLastLineLength() {
74 | String lastLineRaw = Iterables.getLast(codeSnippet, null);
75 | if (lastLineRaw == null) {
76 | return 0;
77 | } else {
78 | return lastLineRaw.length();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/ChangeType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | /**
4 | * Change type of a DiffHunk
5 | */
6 | public enum ChangeType {
7 | MODIFIED("M", "Modify"),
8 | ADDED("A", "Add"),
9 | DELETED("D", "Delete");
10 |
11 | public String symbol;
12 | public String label;
13 |
14 | ChangeType(String symbol, String label) {
15 | this.symbol = symbol;
16 | this.label = label;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/ContentType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | /** Type of the content in hunk */
4 | public enum ContentType {
5 | IMPORT("ImportStatement"), // pure imports
6 | COMMENT("Comment"), // pure comment
7 | CODE("Code"), // actual code (or mixed)
8 | BLANKLINE("BlankLine"), // blank lines
9 | EMPTY("Empty"), // added/deleted
10 | BINARY("Binary"); // binary content
11 |
12 | public String label;
13 |
14 | ContentType(String label) {
15 | this.label = label;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/FileStatus.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | /**
4 | * Status of a DiffFile
5 | */
6 | public enum FileStatus {
7 | // XY: two-letter status code, where X shows the index status, Y shows the working tree status
8 | UNMODIFIED(" ", "unmodified"), // usually won't appear
9 | MODIFIED("M", "modified"),
10 | ADDED("A", "added"),
11 | DELETED("D", "deleted"),
12 | RENAMED("R", "renamed"), // RXXX (like R096: Renamed with 96% similarity)
13 | COPIED("C", "copied"), // CXXX (like C075: Copied with 75% similarity)
14 | UNMERGED("U", "unmerged"),
15 | UNTRACKED("??", "untracked"),
16 | IGNORED("!!", "ignored"); // Ignored files are not listed, unless --ignored option is in effect, in which case XY are !!.
17 |
18 | public String symbol;
19 | public String label;
20 |
21 | FileStatus(String symbol, String label) {
22 | this.symbol = symbol;
23 | this.label = label;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/FileType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | /** The type of the diff File */
4 | public enum FileType {
5 | JAVA(".java", "Java"),
6 | KT(".kt", "Kotlin"),
7 | KTS(".kts", "Kotlin-Script"),
8 | JSON(".json", "Json"),
9 | JS(".javascript", "JavaScript"),
10 | PY(".py", "Python"),
11 | CPP(".cpp", "C++"),
12 | HPP(".hpp", "C++ Header"),
13 | C(".c", "C"),
14 | H(".h", "C Header"),
15 | MD(".md", "Markdown"),
16 | TXT(".txt", "Text"),
17 | HTML(".html", "HTML"),
18 | XML(".xml", "XML"),
19 | YML(".yml", "YAML"),
20 | GRADLE(".gradle", "Gradle"),
21 | GROOVY(".groovy", "Groovy"),
22 | PROP(".properties", "Properties"),
23 |
24 | BIN(".", "Binary"), // binary file
25 | OTHER(".*", "Other"); // other plain text file
26 |
27 | public String extension;
28 | public String label;
29 |
30 | FileType(String extension, String label) {
31 | this.extension = extension;
32 | this.label = label;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/GroupLabel.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | public enum GroupLabel {
4 | FEATURE("Add or modify feature"), // new feature
5 | REFACTOR("Refactor code structure"), // refactoring
6 | FIX("Fix bug"), // fix bugs
7 | OPT("Optimize code"), // optimization for existing functions
8 |
9 | REFORMAT("Reformat code"), // blank/special character changes
10 | DOC("Update document"),
11 | CONFIG("Change config file"),
12 | RESOURCE("Change resource file"),
13 | SIMILAR("Apply some similar changes"), // systematic changes
14 | CLEAR("Clear unused code"), // clear dead code or comment
15 |
16 | TEST("Modify test cases or tested methods"),
17 | NONJAVA("Modify non-java file"),
18 | OTHER("Other changes"); // trivial changes
19 |
20 | public String label;
21 |
22 | GroupLabel(String label) {
23 | this.label = label;
24 | }
25 |
26 | public String getLabel() {
27 | return label;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/Operation.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | /** Action operation on AST or Refactoring */
4 | public enum Operation {
5 | // AST operations
6 | ADD("Add", 1),
7 | DEL("Delete", 2),
8 | UPD("Update", 3),
9 | MOV("Move", 4),
10 |
11 | // Refactoring operations
12 | // ADD("Add", 1),
13 | CHANGE("Change", 5),
14 | CONVERT("Convert", 6),
15 | EXTRACT("Extract", 7),
16 | EXTRACT_AND_MOVE("Extract And Move", 8),
17 | INLINE("Inline", 9),
18 | INTRODUCE("Introduce", 10),
19 | MERGE("Merge", 11),
20 | MODIFY("Modify", 12),
21 | MOVE("Move", 13),
22 | MOVE_AND_INLINE("Move And Inline", 14),
23 | MOVE_AND_RENAME("Move And Rename", 15),
24 | PARAMETERIZE("Parameterize", 16),
25 | PULL_UP("Pull Up", 17),
26 | PULL_DOWN("Pull Down", 18),
27 | REPLACE("Replace", 19),
28 | REORDER("Reorder", 20),
29 | RENAME("Rename", 21),
30 | REMOVE("Remove", 22),
31 | SPILT("Split", 23),
32 |
33 |
34 | UKN("Unknown", 24);
35 |
36 | public String label;
37 | public int index;
38 |
39 | Operation(String label, int index) {
40 | this.label = label;
41 | this.index = index;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return label;
47 | }
48 |
49 | public int getIndex() {
50 | return index;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/constant/Version.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.constant;
2 |
3 | public enum Version {
4 | BASE(0, "base"),
5 | CURRENT(1, "current");
6 |
7 | private int index;
8 | private String label;
9 |
10 | Version(int index, String label) {
11 | this.index = index;
12 | this.label = label;
13 | }
14 |
15 | public String asString() {
16 | return label;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/diffgraph/DiffEdge.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.diffgraph;
2 |
3 | public class DiffEdge {
4 | private Integer id;
5 | private DiffEdgeType type;
6 | private Double weight;
7 |
8 | public DiffEdge(Integer id, DiffEdgeType type, Double weight) {
9 | this.id = id;
10 | this.type = type;
11 | this.weight = weight;
12 | }
13 |
14 | public Integer getId() {
15 | return id;
16 | }
17 |
18 | public DiffEdgeType getType() {
19 | return type;
20 | }
21 |
22 | public Double getWeight() {
23 | return weight;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/diffgraph/DiffEdgeType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.diffgraph;
2 |
3 | public enum DiffEdgeType {
4 | /** hard * */
5 | DEPEND(true, "dependency", 0),
6 | /** hard * */
7 |
8 | /** soft * */
9 | SIMILAR(false, "similar", 1),
10 | CLOSE(false, "close", 1),
11 | /** soft * */
12 |
13 | /** pattern * */
14 | REFACTOR(true, "refactor", 2),
15 | MOVING(true, "moving", 2),
16 | /** pattern * */
17 |
18 | /** logical * */
19 | REFORMAT(true, "reformat", 3),
20 | TEST(false, "test", 3),
21 | /** logical * */
22 |
23 | DOC(false, "doc", 4),
24 | CONFIG(false, "config", 4),
25 | RESOURCE(false, "resource", 4),
26 | NONJAVA(false, "non-java", 4),
27 | OTHERS(false, "others", 4);
28 |
29 | Boolean fixed;
30 | String label;
31 | Integer category; // category of the link, mainly for ablation study
32 |
33 | DiffEdgeType(Boolean fixed, String label) {
34 | this.fixed = fixed;
35 | this.label = label;
36 | }
37 |
38 | DiffEdgeType(Boolean fixed, String label, Integer category) {
39 | this.fixed = fixed;
40 | this.label = label;
41 | this.category = category;
42 | }
43 |
44 | public String asString() {
45 | return this.label;
46 | }
47 |
48 | public Boolean isConstraint() {
49 | return this.fixed;
50 | }
51 |
52 | public Integer getCategory() {
53 | return this.category;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/diffgraph/DiffNode.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.diffgraph;
2 |
3 | import com.github.smartcommit.util.Utils;
4 | import org.apache.commons.lang3.tuple.Pair;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | /** Nodes in the DiffViewGraph, which stand for one diff hunk */
10 | public class DiffNode {
11 | private Integer id;
12 | private String index; // unique identifier (fileIndex:diffHunkIndex)
13 | private Integer fileIndex; // index of the changed file
14 | private Integer diffHunkIndex; // index of the diff hunk
15 | private String uuid; // fileID:diffHunkID
16 |
17 | // parent info (node id in the graph) to estimate the distance
18 | // in the order of: hunkNodeID, memberNodeID, classNodeID, packageNodeID
19 | private Map baseHierarchy;
20 | private Map currentHierarchy;
21 |
22 | public DiffNode(Integer id, String index, String uuid) {
23 | this.id = id;
24 | this.index = index;
25 | this.uuid = uuid;
26 | Pair indices = Utils.parseIndices(index);
27 | this.fileIndex = indices.getLeft();
28 | this.diffHunkIndex = indices.getRight();
29 | this.baseHierarchy = new HashMap<>();
30 | this.currentHierarchy = new HashMap<>();
31 | }
32 |
33 | public Integer getId() {
34 | return id;
35 | }
36 |
37 | public String getIndex() {
38 | return index;
39 | }
40 |
41 | public Map getBaseHierarchy() {
42 | return baseHierarchy;
43 | }
44 |
45 | public void setBaseHierarchy(Map baseHierarchy) {
46 | this.baseHierarchy = baseHierarchy;
47 | }
48 |
49 | public Map getCurrentHierarchy() {
50 | return currentHierarchy;
51 | }
52 |
53 | public void setCurrentHierarchy(Map currentHierarchy) {
54 | this.currentHierarchy = currentHierarchy;
55 | }
56 |
57 | public Integer getFileIndex() {
58 | return fileIndex;
59 | }
60 |
61 | public Integer getDiffHunkIndex() {
62 | return diffHunkIndex;
63 | }
64 |
65 | public String getUUID() {
66 | return uuid;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/AnnotationInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | public class AnnotationInfo extends DeclarationInfo{
4 | public String name;
5 | public String fullName;
6 | public String comment = "";
7 | public String content = "";
8 |
9 | public String uniqueName() {
10 | return fullName;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/AnnotationMemberInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import com.github.smartcommit.model.graph.Node;
4 |
5 | public class AnnotationMemberInfo {
6 | public String name;
7 | public String belongTo;
8 | public String type;
9 | public String defaultValue;
10 |
11 | public Node node;
12 |
13 | public String uniqueName() {
14 | return belongTo + ":" + name;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/ClassInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class ClassInfo extends DeclarationInfo {
7 |
8 | public String name;
9 | // public String belongTo;
10 | public String fullName;
11 | public String visibility = "package";
12 | public boolean isAbstract = false;
13 | public boolean isFinal = false;
14 | public boolean isAnonymous = false;
15 | public String superClassType;
16 | public List superInterfaceTypeList = new ArrayList<>();
17 | public String comment = "";
18 | public String content = "";
19 |
20 | public String uniqueName() {
21 | // return belongTo + "." + name;
22 | return fullName;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/DeclarationInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import com.github.smartcommit.model.graph.Node;
4 | import org.eclipse.jdt.core.dom.IMethodBinding;
5 |
6 | import java.util.HashSet;
7 | import java.util.Set;
8 |
9 | /**
10 | * Information collected in declarations (e.g. type, field, method/constructor, annotation member,
11 | * enum constant)
12 | */
13 | public class DeclarationInfo {
14 | // which file the entity belongs to
15 | public Integer fileIndex;
16 | // corresponding node in the graph
17 | public Node node;
18 |
19 | // def internal
20 | public Set typeDefs = new HashSet<>(); // AbstractType, including Type, Enum, Annotation
21 | public Set fieldDefs = new HashSet<>();
22 | public Set methodDefs = new HashSet<>();
23 |
24 | // use internal
25 | public Set typeUses = new HashSet<>(); // AbstractType, including Type, Enum, Annotation
26 | public Set methodCalls = new HashSet<>();
27 | public Set fieldUses = new HashSet<>();
28 | public Set paraUses = new HashSet<>();
29 | public Set localVarUses = new HashSet<>();
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/EnumConstantInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import com.github.smartcommit.model.graph.Node;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class EnumConstantInfo {
9 | public String name;
10 | public String belongTo;
11 | public List arguments = new ArrayList<>();
12 | public String comment = "";
13 |
14 | // corresponding node in the graph
15 | public Node node;
16 |
17 | public String uniqueName() {
18 | return belongTo + ":" + name;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/EnumInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | public class EnumInfo extends DeclarationInfo{
4 | public String name;
5 | public String fullName;
6 | public String visibility = "package";
7 | public String comment = "";
8 | public String content = "";
9 |
10 | public String uniqueName() {
11 | return fullName;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/FieldInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import java.util.Set;
4 |
5 | public class FieldInfo extends DeclarationInfo {
6 |
7 | public String name;
8 | public String belongTo;
9 | public String typeString;
10 | public Set types;
11 | public String visibility;
12 | public boolean isStatic;
13 | public boolean isFinal;
14 | public String comment = "";
15 |
16 | public String uniqueName() {
17 | return belongTo + ":" + name;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/HunkInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import org.eclipse.jdt.core.dom.ASTNode;
4 |
5 | import java.util.LinkedHashSet;
6 | import java.util.Set;
7 |
8 | public class HunkInfo extends DeclarationInfo {
9 | public String identifier = "-1:-1";
10 | public Integer fileIndex = -1;
11 | public Integer hunkIndex = -1;
12 | public Set coveredNodes = new LinkedHashSet<>();
13 |
14 | public HunkInfo(Integer fileIndex, Integer hunkIndex) {
15 | this.fileIndex = fileIndex;
16 | this.hunkIndex = hunkIndex;
17 | this.identifier = fileIndex + ":" + hunkIndex;
18 | }
19 |
20 | public HunkInfo(String identifier) {
21 | this.identifier = identifier;
22 | String[] indices = identifier.split(":");
23 | if (indices.length == 2) {
24 | this.fileIndex = Integer.valueOf(indices[0]);
25 | this.hunkIndex = Integer.valueOf(indices[1]);
26 | }
27 | }
28 |
29 | public String uniqueName() {
30 | return fileIndex + ":" + hunkIndex;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/InitializerInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | /** More like a method with name "static" */
4 | public class InitializerInfo extends DeclarationInfo {
5 | public boolean isStatic = false;
6 | public String comment = "";
7 | public String body = "";
8 | public String belongTo = "";
9 |
10 | public String uniqueName() {
11 | return belongTo + ":INIT";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/InterfaceInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class InterfaceInfo extends DeclarationInfo{
7 | public String name;
8 | public String fullName;
9 | public String visibility;
10 | public List superInterfaceTypeList = new ArrayList<>();
11 | public String comment = "";
12 | public String content = "";
13 |
14 | public String uniqueName() {
15 | return fullName;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/entity/MethodInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.entity;
2 |
3 | import org.eclipse.jdt.core.dom.IMethodBinding;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | public class MethodInfo extends DeclarationInfo {
9 | public String visibility;
10 | public boolean isConstructor;
11 | public boolean isAbstract;
12 | public boolean isFinal;
13 | public boolean isStatic;
14 | public boolean isSynchronized;
15 |
16 | public String name;
17 | public String belongTo;
18 | public String returnString;
19 | public Set returnTypes = new HashSet<>();
20 |
21 | public String content = "";
22 | public String comment = "";
23 | public String paramString;
24 | public Set paramTypes = new HashSet<>();
25 | public Set exceptionThrows = new HashSet<>();
26 |
27 | public IMethodBinding methodBinding;
28 |
29 | public String uniqueName() {
30 | return belongTo + ":" + name + "(" + paramString + ")";
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/graph/Edge.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.graph;
2 |
3 | public class Edge {
4 | private Integer id;
5 | private EdgeType type;
6 | private Integer weight;
7 |
8 | public Edge(Integer id, EdgeType type) {
9 | this.id = id;
10 | this.type = type;
11 | this.weight = 1;
12 | }
13 |
14 | public Integer getId() {
15 | return id;
16 | }
17 |
18 | public Integer getWeight() {
19 | return weight;
20 | }
21 |
22 | /** Increase the weight by one */
23 | public Integer increaseWeight() {
24 | this.weight += 1;
25 | return this.weight;
26 | }
27 |
28 | public void setWeight(Integer weight) {
29 | this.weight = weight;
30 | }
31 |
32 | public EdgeType getType() {
33 | return type;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/graph/EdgeType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.graph;
2 |
3 | public enum EdgeType {
4 | /** file&folder level edges * */
5 | CONTAIN(true, "contain"), // physical relation
6 | IMPORT(false, "import"),
7 | EXTEND(false, "extend"),
8 | IMPLEMENT(false, "implement"),
9 | /** inside-file edges * */
10 | // define field/terminal/constructor/inner type/constant
11 | DEFINE(true, "define"),
12 | /** across-node edges * */
13 | // inter-field/terminal edges
14 | ACCESS(false, "access_field"),
15 | // READ("reads field"),
16 | // WRITE("writes field"),
17 | // call method
18 | CALL(false, "call_method"),
19 | // declare/initialize object
20 | // DECLARE(false, "declare_object"),
21 | PARAM(false, "parameter_type"),
22 | TYPE(false, "field_type"),
23 | INITIALIZE(false, "initialize"),
24 | RETURN(false, "return_type");
25 |
26 | Boolean isStructural;
27 | String label;
28 |
29 | EdgeType(Boolean isStructural, String label) {
30 | this.isStructural = isStructural;
31 | this.label = label;
32 | }
33 |
34 | public String asString() {
35 | return this.label;
36 | }
37 |
38 | public Boolean isStructural() {
39 | return this.isStructural;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/graph/Node.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.graph;
2 |
3 | public class Node {
4 | private Integer id;
5 | private NodeType type;
6 | private String identifier;
7 | private String qualifiedName;
8 |
9 | public Boolean isInDiffHunk;
10 | // following fields are only valid is isInDiffHunk is true
11 | // diffHunkIndex = fileIndex:hunkIndex
12 | public String diffHunkIndex;
13 |
14 | public Node(Integer id, NodeType type, String identifier, String qualifiedName) {
15 | this.id = id;
16 | this.type = type;
17 | this.identifier = identifier;
18 | this.qualifiedName = qualifiedName;
19 | this.isInDiffHunk = false;
20 | this.diffHunkIndex = "";
21 | }
22 |
23 | public Integer getId() {
24 | return id;
25 | }
26 |
27 | public NodeType getType() {
28 | return type;
29 | }
30 |
31 | public String getIdentifier() {
32 | return identifier;
33 | }
34 |
35 | public String getQualifiedName() {
36 | return qualifiedName;
37 | }
38 |
39 | public String getDiffHunkIndex() {
40 | return diffHunkIndex;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/model/graph/NodeType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.model.graph;
2 |
3 | /** Node declarations */
4 | public enum NodeType {
5 | PROJECT("project"), // final root of all nodes
6 | PACKAGE("package"),
7 | COMPILATION_UNIT("compilation_unit"), // logical node to represent file
8 |
9 | // nonterminal (children classes of AbstractTypeDeclaration)
10 | CLASS("class"),
11 | ANONY_CLASS("anonymous class"),
12 | // INNER_CLASS("class"),
13 | INTERFACE("interface"),
14 |
15 | ENUM("enum"),
16 | ANNOTATION("@interface"), // annotation type declaration
17 |
18 | // terminal
19 | // CONSTRUCTOR("constructor"),
20 | FIELD("field"),
21 | METHOD("method"),
22 | ENUM_CONSTANT("enum_constant"),
23 | INITIALIZER_BLOCK("initializer_block"),
24 | ANNOTATION_MEMBER("annotation_member"),
25 |
26 | HUNK("hunk");
27 |
28 | String label;
29 |
30 | NodeType(String label) {
31 | this.label = label;
32 | }
33 |
34 | public String asString() {
35 | return this.label;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/Distance.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.util;
2 |
3 | public class Distance {
4 | public static void main(String[] args) {
5 | String s1 = "13|45";
6 | String s3 = "134|5";
7 | String s2 = "135|4";
8 | System.out.println(DLDistance(s1, s2));
9 | System.out.println(getSimilarity(s1, s2));
10 | System.out.println(LDistance("first second third", "second"));
11 | }
12 |
13 | private static int DLDistance(String s1, String s2) {
14 | int m = (s1 == null) ? 0 : s1.length();
15 | int n = (s2 == null) ? 0 : s2.length();
16 | if (m == 0) {
17 | return n;
18 | }
19 | if (n == 0) {
20 | return m;
21 | }
22 | int[] p = new int[n + 1];
23 | int[] p1 = new int[n + 1];
24 | int[] t = new int[n + 1];
25 | for (int i = 0; i < p.length; i++) {
26 | p[i] = i;
27 | }
28 | int d = 0;
29 | int cost = 0;
30 | char s1_c, s2_c;
31 | for (int i = 0; i < m; i++) {
32 | t[0] = i + 1;
33 | s1_c = s1.charAt(i);
34 | for (int j = 1; j < p.length; j++) {
35 | s2_c = s2.charAt(j - 1);
36 | cost = (s1_c == s2_c) ? 0 : 1;
37 | d = Math.min(Math.min(t[j - 1], p[j]) + 1, p[j - 1] + cost);
38 | if (i > 0 && j > 1 && s1_c == s2.charAt(j - 2) && s1.charAt(i - 1) == s2_c) {
39 | d = Math.min(d, p1[j - 2] + cost);
40 | }
41 | t[j] = d;
42 | }
43 | p1 = p;
44 | p = t;
45 | t = new int[n + 1];
46 | }
47 | return d;
48 | }
49 |
50 | public static float getSimilarity(String s1, String s2) {
51 | if (s1 == null || s2 == null) {
52 | if (s1 == s2) {
53 | return 1.0f;
54 | }
55 | return 0.0f;
56 | }
57 | float d = DLDistance(s1, s2);
58 | return 1 - (d / Math.max(s1.length(), s2.length()));
59 | }
60 |
61 | public static int LDistance(String sentence1, String sentence2) {
62 | String[] s1 = sentence1.split(" ");
63 | String[] s2 = sentence2.split(" ");
64 | int[][] solution = new int[s1.length + 1][s2.length + 1];
65 |
66 | for (int i = 0; i <= s2.length; i++) {
67 | solution[0][i] = i;
68 | }
69 |
70 | for (int i = 0; i <= s1.length; i++) {
71 | solution[i][0] = i;
72 | }
73 |
74 | int m = s1.length;
75 | int n = s2.length;
76 | for (int i = 1; i <= m; i++) {
77 | for (int j = 1; j <= n; j++) {
78 | if (s1[i - 1].equals(s2[j - 1])) solution[i][j] = solution[i - 1][j - 1];
79 | else
80 | solution[i][j] =
81 | 1
82 | + Math.min(
83 | solution[i][j - 1], Math.min(solution[i - 1][j], solution[i - 1][j - 1]));
84 | }
85 | }
86 | return solution[s1.length][s2.length];
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/GitService.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.util;
2 |
3 | import com.github.smartcommit.model.DiffFile;
4 | import com.github.smartcommit.model.DiffHunk;
5 |
6 | import java.nio.charset.Charset;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /** A list of helper functions related with Git */
11 | public interface GitService {
12 | /**
13 | * Get the diff files in the current working tree
14 | *
15 | * @return
16 | */
17 | ArrayList getChangedFilesInWorkingTree(String repoPath);
18 |
19 | /**
20 | * Get the diff files between one commit and its previous commit
21 | *
22 | * @return
23 | */
24 | ArrayList getChangedFilesAtCommit(String repoPath, String commitID);
25 |
26 | /**
27 | * Get the diff hunks in the current working tree
28 | *
29 | * @param repoPath
30 | * @return
31 | */
32 | List getDiffHunksInWorkingTree(String repoPath, List diffFiles);
33 |
34 | /**
35 | * Get the diff hunks between one commit and its previous commit
36 | *
37 | * @param repoPath
38 | * @param commitID
39 | * @return
40 | */
41 | List getDiffHunksAtCommit(String repoPath, String commitID, List diffFiles);
42 |
43 | /**
44 | * Get the file content at HEAD
45 | *
46 | * @param relativePath
47 | * @return
48 | */
49 | String getContentAtHEAD(Charset charset, String repoDir, String relativePath);
50 |
51 | /**
52 | * Get the file content at one specific commit
53 | *
54 | * @param relativePath
55 | * @returnØØ
56 | */
57 | String getContentAtCommit(Charset charset, String repoDir, String relativePath, String commitID);
58 |
59 | /**
60 | * Get the name of the author of a commit
61 | * @param repoDir
62 | * @param commitID
63 | * @return
64 | */
65 | String getCommitterName(String repoDir, String commitID);
66 |
67 | /**
68 | * Get the email of the author of a commit
69 | *
70 | * @param repoDir
71 | * @param commitID
72 | * @return
73 | */
74 | String getCommitterEmail(String repoDir, String commitID);
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/GitServiceJGit.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.util;
2 |
3 | import com.github.smartcommit.model.DiffFile;
4 | import com.github.smartcommit.model.DiffHunk;
5 |
6 | import java.nio.charset.Charset;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /** Implementation of helper functions based on jGit (the java implementation of Git). */
11 | public class GitServiceJGit implements GitService {
12 | @Override
13 | public ArrayList getChangedFilesInWorkingTree(String repoPath) {
14 | return null;
15 | }
16 |
17 | @Override
18 | public ArrayList getChangedFilesAtCommit(String repoPath, String commitID) {
19 | return null;
20 | }
21 |
22 | @Override
23 | public List getDiffHunksInWorkingTree(String repoPath, List diffFiles) {
24 | return null;
25 | }
26 |
27 | @Override
28 | public List getDiffHunksAtCommit(
29 | String repoPath, String commitID, List diffFiles) {
30 | return null;
31 | }
32 |
33 | @Override
34 | public String getContentAtHEAD(Charset charset, String repoDir, String relativePath) {
35 | return null;
36 | }
37 |
38 | @Override
39 | public String getContentAtCommit(
40 | Charset charset, String repoDir, String relativePath, String commitID) {
41 | return null;
42 | }
43 |
44 | @Override
45 | public String getCommitterName(String repoDir, String commitID) {
46 | return null;
47 | }
48 |
49 | @Override
50 | public String getCommitterEmail(String repoDir, String commitID) {
51 | return null;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/JDTParser.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.util;
2 |
3 | import com.github.smartcommit.model.DiffFile;
4 | import org.apache.commons.lang3.tuple.Pair;
5 | import org.apache.log4j.Logger;
6 | import org.eclipse.jdt.core.JavaCore;
7 | import org.eclipse.jdt.core.dom.ASTParser;
8 | import org.eclipse.jdt.core.dom.CompilationUnit;
9 |
10 | import java.util.Map;
11 |
12 | public class JDTParser {
13 | private static final Logger logger = Logger.getLogger(JDTParser.class);
14 | private String repoPath;
15 | private String jrePath;
16 |
17 | public JDTParser(String repoPath, String jrePath) {
18 | this.repoPath = repoPath;
19 | this.jrePath = jrePath;
20 | }
21 |
22 | /**
23 | * Currently for Java 8
24 | *
25 | * @param diffFile
26 | * @return
27 | */
28 | public Pair generateCUPair(DiffFile diffFile) {
29 |
30 | ASTParser parser = initASTParser();
31 | parser.setUnitName(Utils.getFileNameFromPath(diffFile.getBaseRelativePath()));
32 | parser.setSource(diffFile.getBaseContent().toCharArray());
33 | CompilationUnit oldCU = (CompilationUnit) parser.createAST(null);
34 | if (!oldCU.getAST().hasBindingsRecovery()) {
35 | logger.error("Binding not enabled: " + diffFile.getBaseRelativePath());
36 | }
37 |
38 | parser = initASTParser();
39 | parser.setUnitName(Utils.getFileNameFromPath(diffFile.getCurrentRelativePath()));
40 | parser.setSource(diffFile.getCurrentContent().toCharArray());
41 | CompilationUnit newCU = (CompilationUnit) parser.createAST(null);
42 | if (!newCU.getAST().hasBindingsRecovery()) {
43 | logger.error("Binding not enabled: " + diffFile.getCurrentRelativePath());
44 | }
45 | return Pair.of(oldCU, newCU);
46 | }
47 |
48 | /**
49 | * Init the JDT ASTParser
50 | *
51 | * @return
52 | */
53 | public ASTParser initASTParser() {
54 | // set up the parser and resolver options
55 | ASTParser parser = ASTParser.newParser(8);
56 | parser.setResolveBindings(true);
57 | parser.setKind(ASTParser.K_COMPILATION_UNIT);
58 | parser.setBindingsRecovery(true);
59 | Map options = JavaCore.getOptions();
60 | parser.setCompilerOptions(options);
61 |
62 | // set up the arguments
63 | String[] sources = {this.repoPath}; // sources to resolve symbols
64 | String[] classpath = {this.jrePath}; // local java runtime (rt.jar) path
65 | parser.setEnvironment(classpath, sources, new String[] {"UTF-8"}, true);
66 | return parser;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/NameResolver.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit.util;
2 |
3 | import org.eclipse.jdt.core.dom.*;
4 |
5 | import java.util.Set;
6 |
7 | /**
8 | * Evaluates fully qualified name of TypeDeclaration, Type and Name objects.
9 | *
10 | *
11 | */
12 | public class NameResolver {
13 |
14 | private static Set srcPathSet = null;
15 |
16 | /**
17 | * Evaluates fully qualified name of the TypeDeclaration object.
18 | *
19 | * @param decl
20 | * @return
21 | */
22 | public static String getFullName(TypeDeclaration decl) {
23 | String name = decl.getName().getIdentifier();
24 | ASTNode parent = decl.getParent();
25 | // resolve full name e.g.: A.B
26 | while (parent != null && parent.getClass() == TypeDeclaration.class) {
27 | name = ((TypeDeclaration) parent).getName().getIdentifier() + "." + name;
28 | parent = parent.getParent();
29 | }
30 | // resolve fully qualified name e.g.: some.package.A.B
31 | if (decl.getRoot().getClass() == CompilationUnit.class) {
32 | CompilationUnit root = (CompilationUnit) decl.getRoot();
33 | if (root.getPackage() != null) {
34 | PackageDeclaration pack = root.getPackage();
35 | name = pack.getName().getFullyQualifiedName() + "." + name;
36 | }
37 | }
38 | return name;
39 | }
40 |
41 | /**
42 | * Evaluates fully qualified name of the TypeDeclaration object.
43 | *
44 | * @param decl
45 | * @return
46 | */
47 | public static String getFullName(EnumDeclaration decl) {
48 | String name = decl.getName().getIdentifier();
49 | ASTNode parent = decl.getParent();
50 | // resolve full name e.g.: A.B
51 | while (parent != null && parent.getClass() == TypeDeclaration.class) {
52 | name = ((TypeDeclaration) parent).getName().getIdentifier() + "." + name;
53 | parent = parent.getParent();
54 | }
55 | // resolve fully qualified name e.g.: some.package.A.B
56 | if (decl.getRoot().getClass() == CompilationUnit.class) {
57 | CompilationUnit root = (CompilationUnit) decl.getRoot();
58 | if (root.getPackage() != null) {
59 | PackageDeclaration pack = root.getPackage();
60 | name = pack.getName().getFullyQualifiedName() + "." + name;
61 | }
62 | }
63 | return name;
64 | }
65 |
66 | public static String getFullName(AnnotationTypeDeclaration decl) {
67 | String name = decl.getName().getIdentifier();
68 | ASTNode parent = decl.getParent();
69 | // resolve fully qualified name e.g.: some.package.A.B
70 | if (decl.getRoot().getClass() == CompilationUnit.class) {
71 | CompilationUnit root = (CompilationUnit) decl.getRoot();
72 | if (root.getPackage() != null) {
73 | PackageDeclaration pack = root.getPackage();
74 | name = pack.getName().getFullyQualifiedName() + "." + name;
75 | }
76 | }
77 | return name;
78 | }
79 |
80 | /** Evaluates fully qualified name of the Type object. */
81 | public static String getFullName(Type t) {
82 | if (t == null) return null;
83 | if (t.isParameterizedType()) {
84 | ParameterizedType t0 = (ParameterizedType) t;
85 | return getFullName(t0.getType());
86 | } else if (t.isQualifiedType()) {
87 | QualifiedType t0 = (QualifiedType) t;
88 | return getFullName(t0.getQualifier()) + "." + t0.getName().getIdentifier();
89 | } else if (t.isSimpleType()) {
90 | SimpleType t0 = (SimpleType) t;
91 | return getFullName(t0.getName());
92 | } else {
93 | return "?";
94 | }
95 | }
96 |
97 | /** Evaluates fully qualified name of the Name object. */
98 | private static String getFullName(Name name) {
99 | // check if the root node is a CompilationUnit
100 | if (name.getRoot().getClass() != CompilationUnit.class) {
101 | // cannot resolve a full name, CompilationUnit root node is missing
102 | return name.getFullyQualifiedName();
103 | }
104 | // get the root node
105 | CompilationUnit root = (CompilationUnit) name.getRoot();
106 | // check if the name is declared in the same file
107 | TypeDeclVisitor tdVisitor = new TypeDeclVisitor(name.getFullyQualifiedName());
108 | root.accept(tdVisitor);
109 | if (tdVisitor.getFound()) {
110 | // the name is the use of the TypeDeclaration in the same file
111 | return getFullName(tdVisitor.getTypeDecl());
112 | }
113 | // check if the name is declared in the same package or imported
114 | PckgImprtVisitor piVisitor = new PckgImprtVisitor(name.getFullyQualifiedName());
115 | root.accept(piVisitor);
116 | if (piVisitor.getFound()) {
117 | // the name is declared in the same package or imported
118 | return piVisitor.getFullName();
119 | }
120 | // could be a class from the java.lang (String) or a param name (T, E,...)
121 | return name.getFullyQualifiedName();
122 | }
123 |
124 | public static Set getSrcPathSet() {
125 | return srcPathSet;
126 | }
127 |
128 | public static void setSrcPathSet(Set srcPathSet) {
129 | NameResolver.srcPathSet = srcPathSet;
130 | }
131 |
132 | private static class PckgImprtVisitor extends ASTVisitor {
133 | private boolean found = false;
134 | private String fullName;
135 | private String name;
136 | private String[] nameParts;
137 |
138 | PckgImprtVisitor(String aName) {
139 | super();
140 | name = aName;
141 | nameParts = name.split("\\.");
142 | }
143 |
144 | private void checkInDir(String dirName) {
145 | String name = dirName + "." + nameParts[0] + ".java";
146 | for (String fileName : srcPathSet) {
147 | fileName = fileName.replace("\\", ".").replace("/", ".");
148 | if (fileName.contains(name)) {
149 | fullName = dirName;
150 | for (String namePart : nameParts) {
151 | fullName += "." + namePart;
152 | }
153 | found = true;
154 | }
155 | }
156 | }
157 |
158 | public boolean visit(PackageDeclaration node) {
159 | String pckgName = node.getName().getFullyQualifiedName();
160 | checkInDir(pckgName);
161 | return true;
162 | }
163 |
164 | public boolean visit(ImportDeclaration node) {
165 | if (node.isOnDemand()) {
166 | String pckgName = node.getName().getFullyQualifiedName();
167 | checkInDir(pckgName);
168 | } else {
169 | String importName = node.getName().getFullyQualifiedName();
170 | if (importName.endsWith("." + nameParts[0])) {
171 | fullName = importName;
172 | for (int i = 1; i < nameParts.length; i++) {
173 | fullName += "." + nameParts[i];
174 | }
175 | found = true;
176 | }
177 | }
178 | return true;
179 | }
180 |
181 | boolean getFound() {
182 | return found;
183 | }
184 |
185 | String getFullName() {
186 | return fullName;
187 | }
188 | }
189 |
190 | private static class TypeDeclVisitor extends ASTVisitor {
191 | private boolean found = false;
192 | private TypeDeclaration typeDecl;
193 | private String name;
194 |
195 | TypeDeclVisitor(String aName) {
196 | super();
197 | name = aName;
198 | }
199 |
200 | public boolean visit(TypeDeclaration node) {
201 | if (getFullName(node).endsWith("." + name)) {
202 | found = true;
203 | typeDecl = node;
204 | }
205 | return true;
206 | }
207 |
208 | boolean getFound() {
209 | return found;
210 | }
211 |
212 | TypeDeclaration getTypeDecl() {
213 | return typeDecl;
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/DiffParser.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api;
15 |
16 | import com.github.smartcommit.util.diffparser.api.model.Diff;
17 |
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.List;
22 |
23 | /**
24 | * Interface to a parser that parses a textual diff between two text files. See the javadoc of the
25 | * implementation you want to use to see what diff format it is expecting as input.
26 | *
27 | * @author Tom Hombergs
28 | */
29 | @SuppressWarnings("UnusedDeclaration")
30 | public interface DiffParser {
31 |
32 | /**
33 | * Constructs a list of Diffs from a textual InputStream.
34 | *
35 | * @param in the input stream to parse
36 | * @return list of Diff objects parsed from the InputStream.
37 | */
38 | List parse(InputStream in);
39 |
40 | /**
41 | * Constructs a list of Diffs from a textual byte array.
42 | *
43 | * @param bytes the byte array to parse
44 | * @return list of Diff objects parsed from the byte array.
45 | */
46 | List parse(byte[] bytes);
47 |
48 | /**
49 | * Constructs a list of Diffs from a textual File
50 | *
51 | * @param file the file to parse
52 | * @return list of Diff objects parsed from the File.
53 | */
54 | List parse(File file) throws IOException;
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/UnifiedDiffParser.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api;
15 |
16 | import com.github.smartcommit.util.diffparser.api.model.Diff;
17 | import com.github.smartcommit.util.diffparser.api.model.Hunk;
18 | import com.github.smartcommit.util.diffparser.api.model.Line;
19 | import com.github.smartcommit.util.diffparser.api.model.Range;
20 | import com.github.smartcommit.util.diffparser.unified.ParserState;
21 | import com.github.smartcommit.util.diffparser.unified.ResizingParseWindow;
22 |
23 | import java.io.*;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 | /**
30 | * A parser that parses a unified diff from text into a {@link Diff} data structure.
31 | *
32 | *
An example of a unified diff this parser can handle is the following:
33 | *
34 | *
35 | * Modified: trunk/test1.txt
36 | * ===================================================================
37 | * --- /trunk/test1.txt 2013-10-23 19:41:56 UTC (rev 46)
38 | * +++ /trunk/test1.txt 2013-10-23 19:44:39 UTC (rev 47)
39 | * @@ -1,4 +1,3 @@
40 | * test1
41 | * -test1
42 | * +test234
43 | * -test1
44 | * \ No newline at end of file
45 | * @@ -5,9 +6,10 @@
46 | * -test1
47 | * -test1
48 | * +test2
49 | * +test2
50 | *
51 | *
52 | * Note that the TAB character and date after the file names are not being parsed but instead cut
53 | * off.
54 | */
55 | public class UnifiedDiffParser implements DiffParser {
56 | public static final Pattern LINE_RANGE_PATTERN =
57 | Pattern.compile("^.*-([0-9]+)(?:,([0-9]+))? \\+([0-9]+)(?:,([0-9]+))?.*$");
58 |
59 | @Override
60 | public List parse(InputStream in) {
61 | ResizingParseWindow window = new ResizingParseWindow(in);
62 | ParserState state = ParserState.INITIAL;
63 | List parsedDiffs = new ArrayList<>();
64 | Diff currentDiff = new Diff();
65 | String currentLine;
66 | while ((currentLine = window.slideForward()) != null) {
67 | ParserState lastState = state;
68 | state = state.nextState(window);
69 | switch (state) {
70 | case INITIAL:
71 | // nothing to do
72 | break;
73 | case HEADER:
74 | if ((lastState != ParserState.INITIAL)
75 | && (lastState != ParserState.HEADER)
76 | && (lastState != ParserState.END)) {
77 | parsedDiffs.add(currentDiff);
78 | currentDiff = new Diff();
79 | }
80 | parseHeader(currentDiff, currentLine);
81 | break;
82 | case FROM_FILE:
83 | parseFromFile(currentDiff, currentLine);
84 | break;
85 | case TO_FILE:
86 | parseToFile(currentDiff, currentLine);
87 | break;
88 | case HUNK_START:
89 | parseHunkStart(currentDiff, currentLine);
90 | break;
91 | case FROM_LINE:
92 | parseFromLine(currentDiff, currentLine);
93 | break;
94 | case TO_LINE:
95 | parseToLine(currentDiff, currentLine);
96 | break;
97 | case NEUTRAL_LINE:
98 | parseNeutralLine(currentDiff, currentLine);
99 | break;
100 | case END:
101 | parsedDiffs.add(currentDiff);
102 | currentDiff = new Diff();
103 | break;
104 | default:
105 | throw new IllegalStateException(String.format("Illegal parser state '%s", state));
106 | }
107 | }
108 |
109 | // Something like that may be needed to make sure no diffs are lost.
110 | if (currentDiff.getHunks().size() > 0) {
111 | parsedDiffs.add(currentDiff);
112 | currentDiff = new Diff();
113 | }
114 |
115 | return parsedDiffs;
116 | }
117 |
118 | private void parseNeutralLine(Diff currentDiff, String currentLine) {
119 | Line line = new Line(Line.LineType.NEUTRAL, currentLine);
120 | currentDiff.getLatestHunk().getRawLines().add(currentLine);
121 | currentDiff.getLatestHunk().getLines().add(line);
122 | }
123 |
124 | private void parseToLine(Diff currentDiff, String currentLine) {
125 | Line toLine = new Line(Line.LineType.TO, currentLine.substring(1));
126 | currentDiff.getLatestHunk().getRawLines().add(currentLine);
127 | currentDiff.getLatestHunk().getLines().add(toLine);
128 | }
129 |
130 | private void parseFromLine(Diff currentDiff, String currentLine) {
131 | Line fromLine = new Line(Line.LineType.FROM, currentLine.substring(1));
132 | currentDiff.getLatestHunk().getRawLines().add(currentLine);
133 | currentDiff.getLatestHunk().getLines().add(fromLine);
134 | }
135 |
136 | private void parseHunkStart(Diff currentDiff, String currentLine) {
137 | Matcher matcher = LINE_RANGE_PATTERN.matcher(currentLine);
138 | if (matcher.matches()) {
139 | String range1Start = matcher.group(1);
140 | String range1Count = (matcher.group(2) != null) ? matcher.group(2) : "1";
141 | Range fromRange = new Range(Integer.valueOf(range1Start), Integer.valueOf(range1Count));
142 |
143 | String range2Start = matcher.group(3);
144 | String range2Count = (matcher.group(4) != null) ? matcher.group(4) : "1";
145 | Range toRange = new Range(Integer.valueOf(range2Start), Integer.valueOf(range2Count));
146 |
147 | Hunk hunk = new Hunk();
148 | hunk.setFromFileRange(fromRange);
149 | hunk.setToFileRange(toRange);
150 | hunk.getRawLines().add(currentLine);
151 | currentDiff.getHunks().add(hunk);
152 | } else {
153 | throw new IllegalStateException(
154 | String.format(
155 | "No line ranges found in the following hunk start line: '%s'. Expected something "
156 | + "like '-1,5 +3,5'.",
157 | currentLine));
158 | }
159 | }
160 |
161 | private void parseToFile(Diff currentDiff, String currentLine) {
162 | currentDiff.setToFileName(cutAfterTab(currentLine.substring(4)));
163 | }
164 |
165 | private void parseFromFile(Diff currentDiff, String currentLine) {
166 | currentDiff.setFromFileName(cutAfterTab(currentLine.substring(4)));
167 | }
168 |
169 | /** Cuts a TAB and all following characters from a String. */
170 | private String cutAfterTab(String line) {
171 | Pattern p = Pattern.compile("^(.*)\\t.*$");
172 | Matcher matcher = p.matcher(line);
173 | if (matcher.matches()) {
174 | return matcher.group(1);
175 | } else {
176 | return line;
177 | }
178 | }
179 |
180 | private void parseHeader(Diff currentDiff, String currentLine) {
181 | currentDiff.getHeaderLines().add(currentLine);
182 | }
183 |
184 | @Override
185 | public List parse(byte[] bytes) {
186 | return parse(new ByteArrayInputStream(bytes));
187 | }
188 |
189 | @Override
190 | public List parse(File file) throws IOException {
191 | FileInputStream in = new FileInputStream(file);
192 | try {
193 | return parse(in);
194 | } finally {
195 | in.close();
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/model/Diff.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api.model;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | * Represents a Diff between two files.
21 | *
22 | * @author Tom Hombergs
23 | */
24 | @SuppressWarnings("UnusedDeclaration")
25 | public class Diff {
26 |
27 | private String fromFileName;
28 |
29 | private String toFileName;
30 |
31 | private List headerLines = new ArrayList<>();
32 |
33 | private List hunks = new ArrayList<>();
34 |
35 | /**
36 | * The header lines of the diff. These lines are purely informational and are not parsed.
37 | *
38 | * @return the list of header lines.
39 | */
40 | public List getHeaderLines() {
41 | return headerLines;
42 | }
43 |
44 | public void setHeaderLines(List headerLines) {
45 | this.headerLines = headerLines;
46 | }
47 |
48 | /**
49 | * Gets the name of the first file that was compared with this Diff (the file "from" which the
50 | * changes were made, i.e. the "left" file of the diff).
51 | *
52 | * @return the name of the "from"-file.
53 | */
54 | public String getFromFileName() {
55 | return fromFileName;
56 | }
57 |
58 | /**
59 | * Gets the name of the second file that was compared with this Diff (the file "to" which the
60 | * changes were made, i.e. the "right" file of the diff).
61 | *
62 | * @return the name of the "to"-file.
63 | */
64 | public String getToFileName() {
65 | return toFileName;
66 | }
67 |
68 | /**
69 | * The list if all {@link Hunk}s which contain all changes that are part of this Diff.
70 | *
71 | * @return list of all Hunks that are part of this Diff.
72 | */
73 | public List getHunks() {
74 | return hunks;
75 | }
76 |
77 | public void setFromFileName(String fromFileName) {
78 | this.fromFileName = fromFileName;
79 | }
80 |
81 | public void setToFileName(String toFileName) {
82 | this.toFileName = toFileName;
83 | }
84 |
85 | public void setHunks(List hunks) {
86 | this.hunks = hunks;
87 | }
88 |
89 | /**
90 | * Gets the last {@link Hunk} of changes that is part of this Diff.
91 | *
92 | * @return the last {@link Hunk} that has been added to this Diff.
93 | */
94 | public Hunk getLatestHunk() {
95 | return hunks.get(hunks.size() - 1);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/model/Hunk.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api.model;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | * Represents a "hunk" of changes made to a file.
21 | *
22 | *
A Hunk consists of one or more lines that either exist only in the first file ("from line"),
23 | * only in the second file ("to line") or in both files ("neutral line"). Additionally, it contains
24 | * information about which excerpts of the compared files are compared in this Hunk in the form of
25 | * line ranges.
26 | *
27 | * @author Tom Hombergs
28 | */
29 | @SuppressWarnings("UnusedDeclaration")
30 | public class Hunk {
31 |
32 | private Range fromFileRange;
33 |
34 | private Range toFileRange;
35 |
36 | private List lines = new ArrayList<>();
37 |
38 | private List rawLines = new ArrayList<>();
39 |
40 | /**
41 | * The range of line numbers that this Hunk spans in the first file of the Diff.
42 | *
43 | * @return range of line numbers in the first file (the "from" file).
44 | */
45 | public Range getFromFileRange() {
46 | return fromFileRange;
47 | }
48 |
49 | /**
50 | * The range of line numbers that this Hunk spans in the second file of the Diff.
51 | *
52 | * @return range of line numbers in the second file (the "to" file).
53 | */
54 | public Range getToFileRange() {
55 | return toFileRange;
56 | }
57 |
58 | /**
59 | * The lines that are part of this Hunk.
60 | *
61 | * @return lines of this Hunk.
62 | */
63 | public List getLines() {
64 | return lines;
65 | }
66 |
67 | public List getRawLines() {
68 | return rawLines;
69 | }
70 |
71 | public void setRawLines(List rawLines) {
72 | this.rawLines = rawLines;
73 | }
74 |
75 | public void setFromFileRange(Range fromFileRange) {
76 | this.fromFileRange = fromFileRange;
77 | }
78 |
79 | public void setToFileRange(Range toFileRange) {
80 | this.toFileRange = toFileRange;
81 | }
82 |
83 | public void setLines(List lines) {
84 | this.lines = lines;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/model/Line.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api.model;
15 |
16 | /**
17 | * Represents a line of a Diff. A line is either contained in both files ("neutral"), only in the
18 | * first file ("from"), or only in the second file ("to").
19 | *
20 | * @author Tom Hombergs
21 | */
22 | @SuppressWarnings("UnusedDeclaration")
23 | public class Line {
24 |
25 | /** All possible types a line can have. */
26 | public enum LineType {
27 |
28 | /** This line is only contained in the first file of the Diff (the "from" file). */
29 | FROM,
30 |
31 | /** This line is only contained in the second file of the Diff (the "to" file). */
32 | TO,
33 |
34 | /** This line is contained in both filed of the Diff, and is thus considered "neutral". */
35 | NEUTRAL
36 | }
37 |
38 | private final LineType lineType;
39 |
40 | private final String content;
41 |
42 | public Line(LineType lineType, String content) {
43 | this.lineType = lineType;
44 | this.content = content;
45 | }
46 |
47 | /**
48 | * The type of this line.
49 | *
50 | * @return the type of this line.
51 | */
52 | public LineType getLineType() {
53 | return lineType;
54 | }
55 |
56 | /**
57 | * The actual content of the line as String.
58 | *
59 | * @return the actual line content.
60 | */
61 | public String getContent() {
62 | return content;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/api/model/Range.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.api.model;
15 |
16 | /**
17 | * Represents a range of line numbers that spans a window on a text file.
18 | *
19 | * @author Tom Hombergs
20 | */
21 | public class Range {
22 |
23 | private final int lineStart;
24 |
25 | private final int lineCount;
26 |
27 | public Range(int lineStart, int lineCount) {
28 | this.lineStart = lineStart;
29 | this.lineCount = lineCount;
30 | }
31 |
32 | /**
33 | * The line number at which this range starts (inclusive).
34 | *
35 | * @return the line number at which this range starts.
36 | */
37 | public int getLineStart() {
38 | return lineStart;
39 | }
40 |
41 | /**
42 | * The count of lines in this range.
43 | *
44 | * @return the count of lines in this range.
45 | */
46 | public int getLineCount() {
47 | return lineCount;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/unified/ParseWindow.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.unified;
15 |
16 | public interface ParseWindow {
17 |
18 | /**
19 | * Returns the line currently focused by this window. This is actually the same line as returned
20 | * by {@link #slideForward()} but calling this method does not slide the window forward a step.
21 | *
22 | * @return the currently focused line.
23 | */
24 | String getFocusLine();
25 |
26 | /**
27 | * Returns the number of the current line within the whole document.
28 | *
29 | * @return the line number.
30 | */
31 | @SuppressWarnings("UnusedDeclaration")
32 | int getFocusLineNumber();
33 |
34 | /**
35 | * Slides the window forward one line.
36 | *
37 | * @return the next line that is in the focus of this window or null if the end of the stream has
38 | * been reached.
39 | */
40 | String slideForward();
41 |
42 | /**
43 | * Looks ahead from the current line and retrieves a line that will be the focus line after the
44 | * window has slided forward.
45 | *
46 | * @param distance the number of lines to look ahead. Must be greater or equal 0. 0 returns the
47 | * focus line. 1 returns the first line after the current focus line and so on. Note that all
48 | * lines up to the returned line will be held in memory until the window has slided past them,
49 | * so be careful not to look ahead too far!
50 | * @return the line identified by the distance parameter that lies ahead of the focus line.
51 | * Returns null if the line cannot be read because it lies behind the end of the stream.
52 | */
53 | String getFutureLine(int distance);
54 |
55 | void addLine(int pos, String line);
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/unified/ParserState.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.unified;
15 |
16 | import org.apache.log4j.Logger;
17 |
18 | import static com.github.smartcommit.util.diffparser.api.UnifiedDiffParser.LINE_RANGE_PATTERN;
19 |
20 | /**
21 | * State machine for a parser parsing a unified diff.
22 | *
23 | * @author Tom Hombergs
24 | */
25 | public enum ParserState {
26 |
27 | /** This is the initial state of the parser. */
28 | INITIAL {
29 | @Override
30 | public ParserState nextState(ParseWindow window) {
31 | String line = window.getFocusLine();
32 | if (matchesFromFilePattern(line, window.getFutureLine(1))) {
33 | logTransition(line, INITIAL, FROM_FILE);
34 | return FROM_FILE;
35 | } else {
36 | logTransition(line, INITIAL, HEADER);
37 | return HEADER;
38 | }
39 | }
40 | },
41 |
42 | /** The parser is in this state if it is currently parsing a header line. */
43 | HEADER {
44 | @Override
45 | public ParserState nextState(ParseWindow window) {
46 | String line = window.getFocusLine();
47 | if (matchesFromFilePattern(line, window.getFutureLine(1))) {
48 | logTransition(line, HEADER, FROM_FILE);
49 | return FROM_FILE;
50 | } else {
51 | logTransition(line, HEADER, HEADER);
52 | return HEADER;
53 | }
54 | }
55 | },
56 |
57 | /**
58 | * The parser is in this state if it is currently parsing the line containing the "from" file.
59 | *
60 | * Example line:
61 | * {@code --- /path/to/file.txt}
62 | */
63 | FROM_FILE {
64 | @Override
65 | public ParserState nextState(ParseWindow window) {
66 | String line = window.getFocusLine();
67 | if (matchesToFilePattern(line)) {
68 | logTransition(line, FROM_FILE, TO_FILE);
69 | return TO_FILE;
70 | } else {
71 | throw new IllegalStateException(
72 | "A FROM_FILE line ('---') must be directly followed by a TO_FILE line ('+++')!");
73 | }
74 | }
75 | },
76 |
77 | /**
78 | * The parser is in this state if it is currently parsing the line containing the "to" file.
79 | *
80 | *
Example line:
81 | * {@code +++ /path/to/file.txt}
82 | */
83 | TO_FILE {
84 | @Override
85 | public ParserState nextState(ParseWindow window) {
86 | String line = window.getFocusLine();
87 | if (matchesHunkStartPattern(line)) {
88 | logTransition(line, TO_FILE, HUNK_START);
89 | return HUNK_START;
90 | } else {
91 | throw new IllegalStateException(
92 | "A TO_FILE line ('+++') must be directly followed by a HUNK_START line ('@@')!");
93 | }
94 | }
95 | },
96 |
97 | /**
98 | * The parser is in this state if it is currently parsing a line containing the header of a hunk.
99 | *
100 | *
Example line:
101 | * {@code @@ -1,5 +2,6 @@}
102 | */
103 | HUNK_START {
104 | @Override
105 | public ParserState nextState(ParseWindow window) {
106 | String line = window.getFocusLine();
107 | if (matchesFromLinePattern(line)) {
108 | logTransition(line, HUNK_START, FROM_LINE);
109 | return FROM_LINE;
110 | } else if (matchesToLinePattern(line)) {
111 | logTransition(line, HUNK_START, TO_LINE);
112 | return TO_LINE;
113 | } else {
114 | logTransition(line, HUNK_START, NEUTRAL_LINE);
115 | return NEUTRAL_LINE;
116 | }
117 | }
118 | },
119 |
120 | /**
121 | * The parser is in this state if it is currently parsing a line containing a line that is in the
122 | * first file, but not the second (a "from" line).
123 | *
124 | *
Example line:
125 | * {@code - only the dash at the start is important}
126 | */
127 | FROM_LINE {
128 | @Override
129 | public ParserState nextState(ParseWindow window) {
130 | String line = window.getFocusLine();
131 | if (matchesFromLinePattern(line)) {
132 | logTransition(line, FROM_LINE, FROM_LINE);
133 | return FROM_LINE;
134 | } else if (matchesToLinePattern(line)) {
135 | logTransition(line, FROM_LINE, TO_LINE);
136 | return TO_LINE;
137 | } else if (matchesEndPattern(line, window)) {
138 | logTransition(line, FROM_LINE, END);
139 | return END;
140 | } else if (matchesHunkStartPattern(line)) {
141 | logTransition(line, FROM_LINE, HUNK_START);
142 | return HUNK_START;
143 | } else if (matchesNeutralPattern(line)) {
144 | logTransition(line, TO_LINE, NEUTRAL_LINE);
145 | return NEUTRAL_LINE;
146 | } else {
147 | logTransition(line, TO_LINE, HEADER);
148 | return HEADER;
149 | }
150 | }
151 | },
152 |
153 | /**
154 | * The parser is in this state if it is currently parsing a line containing a line that is in the
155 | * second file, but not the first (a "to" line).
156 | *
157 | *
Example line:
158 | * {@code + only the plus at the start is important}
159 | */
160 | TO_LINE {
161 | @Override
162 | public ParserState nextState(ParseWindow window) {
163 | String line = window.getFocusLine();
164 | if (matchesFromLinePattern(line)) {
165 | logTransition(line, TO_LINE, FROM_LINE);
166 | return FROM_LINE;
167 | } else if (matchesToLinePattern(line)) {
168 | logTransition(line, TO_LINE, TO_LINE);
169 | return TO_LINE;
170 | } else if (matchesEndPattern(line, window)) {
171 | logTransition(line, TO_LINE, END);
172 | return END;
173 | } else if (matchesHunkStartPattern(line)) {
174 | logTransition(line, TO_LINE, HUNK_START);
175 | return HUNK_START;
176 | } else if (matchesNeutralPattern(line)) {
177 | logTransition(line, TO_LINE, NEUTRAL_LINE);
178 | return NEUTRAL_LINE;
179 | } else {
180 | logTransition(line, TO_LINE, HEADER);
181 | return HEADER;
182 | }
183 | }
184 | },
185 |
186 | /**
187 | * The parser is in this state if it is currently parsing a line that is contained in both files
188 | * (a "neutral" line). This line can contain any string.
189 | */
190 | NEUTRAL_LINE {
191 | @Override
192 | public ParserState nextState(ParseWindow window) {
193 | String line = window.getFocusLine();
194 | if (matchesFromLinePattern(line)) {
195 | logTransition(line, NEUTRAL_LINE, FROM_LINE);
196 | return FROM_LINE;
197 | } else if (matchesToLinePattern(line)) {
198 | logTransition(line, NEUTRAL_LINE, TO_LINE);
199 | return TO_LINE;
200 | } else if (matchesEndPattern(line, window)) {
201 | logTransition(line, NEUTRAL_LINE, END);
202 | return END;
203 | } else if (matchesHunkStartPattern(line)) {
204 | logTransition(line, NEUTRAL_LINE, HUNK_START);
205 | return HUNK_START;
206 | } else if (matchesNeutralPattern(line)) {
207 | logTransition(line, TO_LINE, NEUTRAL_LINE);
208 | return NEUTRAL_LINE;
209 | } else {
210 | // logTransition(line, NEUTRAL_LINE, NEUTRAL_LINE);
211 | // return NEUTRAL_LINE;
212 | logTransition(line, NEUTRAL_LINE, HEADER);
213 | return HEADER;
214 | }
215 | }
216 | },
217 |
218 | /**
219 | * The parser is in this state if it is currently parsing a line that is the delimiter between two
220 | * Diffs. This line is always a new line.
221 | */
222 | END {
223 | @Override
224 | public ParserState nextState(ParseWindow window) {
225 | String line = window.getFocusLine();
226 | logTransition(line, END, INITIAL);
227 | return INITIAL;
228 | }
229 | };
230 |
231 | protected static Logger logger = Logger.getLogger(ParserState.class);
232 |
233 | /**
234 | * Returns the next state of the state machine depending on the current state and the content of a
235 | * window of lines around the line that is currently being parsed.
236 | *
237 | * @param window the window around the line currently being parsed.
238 | * @return the next state of the state machine.
239 | */
240 | public abstract ParserState nextState(ParseWindow window);
241 |
242 | protected void logTransition(String currentLine, ParserState fromState, ParserState toState) {
243 | logger.debug(String.format("%12s -> %12s: %s", fromState, toState, currentLine));
244 | }
245 |
246 | protected boolean matchesFromFilePattern(String line, String nextLine) {
247 | return line.startsWith("---") && nextLine.startsWith("+++");
248 | }
249 |
250 | protected boolean matchesToFilePattern(String line) {
251 | return line.startsWith("+++");
252 | }
253 |
254 | protected boolean matchesFromLinePattern(String line) {
255 | return line.startsWith("-");
256 | }
257 |
258 | protected boolean matchesNeutralPattern(String line) {
259 | return line.startsWith(" ") || line.startsWith("\\");
260 | }
261 |
262 | protected boolean matchesToLinePattern(String line) {
263 | return line.startsWith("+");
264 | }
265 |
266 | protected boolean matchesHunkStartPattern(String line) {
267 | return LINE_RANGE_PATTERN.matcher(line).matches();
268 | }
269 |
270 | protected boolean matchesEndPattern(String line, ParseWindow window) {
271 | if ("".equals(line.trim())) {
272 | // We have a newline which might be the delimiter between two diffs. It may just be an empty
273 | // line in the current diff or it
274 | // may be the delimiter to the next diff. This has to be disambiguated...
275 | int i = 1;
276 | String futureLine;
277 | while ((futureLine = window.getFutureLine(i)) != null) {
278 | if (matchesFromFilePattern(futureLine, window.getFutureLine(1))) {
279 | // We found the start of a new diff without another newline in between. That makes the
280 | // current line the delimiter
281 | // between this diff and the next.
282 | return true;
283 | } else if ("".equals(futureLine.trim())) {
284 | // We found another newline after the current newline without a start of a new diff in
285 | // between. That makes the
286 | // current line just a newline within the current diff.
287 | return false;
288 | } else {
289 | i++;
290 | }
291 | }
292 | // We reached the end of the stream.
293 | return true;
294 | } else {
295 | // some diff tools like "svn diff" do not put an empty line between two diffs
296 | // we add that empty line and call the method again
297 | String nextFromFileLine = window.getFutureLine(3);
298 | if (nextFromFileLine != null
299 | && matchesFromFilePattern(nextFromFileLine, window.getFutureLine(1))) {
300 | window.addLine(1, "");
301 | return matchesEndPattern(line, window);
302 | } else {
303 | return false;
304 | }
305 | }
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/main/java/com/github/smartcommit/util/diffparser/unified/ResizingParseWindow.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org)
3 | *
4 | *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * except in compliance with the License. You may obtain a copy of the License at
6 | *
7 | *
http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | *
Unless required by applicable law or agreed to in writing, software distributed under the
10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | * express or implied. See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package com.github.smartcommit.util.diffparser.unified;
15 |
16 | import java.io.*;
17 | import java.util.ArrayList;
18 | import java.util.LinkedList;
19 | import java.util.List;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | /**
24 | * A {@link ResizingParseWindow} slides through the lines of a input stream and offers methods to
25 | * get the currently focused line as well as upcoming lines. It is backed by an automatically
26 | * resizing {@link LinkedList}
27 | *
28 | * @author Tom Hombergs
29 | */
30 | @SuppressWarnings("UnusedDeclaration")
31 | public class ResizingParseWindow implements ParseWindow {
32 |
33 | private BufferedReader reader;
34 |
35 | private LinkedList lineQueue = new LinkedList<>();
36 |
37 | private int lineNumber = 0;
38 |
39 | private List ignorePatterns = new ArrayList<>();
40 |
41 | private boolean isEndOfStream = false;
42 |
43 | public ResizingParseWindow(InputStream in) {
44 | Reader unbufferedReader = new InputStreamReader(in);
45 | this.reader = new BufferedReader(unbufferedReader);
46 | }
47 |
48 | public void addIgnorePattern(String ignorePattern) {
49 | this.ignorePatterns.add(Pattern.compile(ignorePattern));
50 | }
51 |
52 | @Override
53 | public String getFutureLine(int distance) {
54 | try {
55 | resizeWindowIfNecessary(distance + 1);
56 | return lineQueue.get(distance);
57 | } catch (IndexOutOfBoundsException e) {
58 | return null;
59 | }
60 | }
61 |
62 | @Override
63 | public void addLine(int pos, String line) {
64 | lineQueue.add(pos, line);
65 | }
66 |
67 | /**
68 | * Resizes the sliding window to the given size, if necessary.
69 | *
70 | * @param newSize the new size of the window (i.e. the number of lines in the window).
71 | */
72 | private void resizeWindowIfNecessary(int newSize) {
73 | try {
74 | int numberOfLinesToLoad = newSize - this.lineQueue.size();
75 | for (int i = 0; i < numberOfLinesToLoad; i++) {
76 | String nextLine = getNextLine();
77 | if (nextLine != null) {
78 | lineQueue.addLast(nextLine);
79 | } else {
80 | throw new IndexOutOfBoundsException("End of stream has been reached!");
81 | }
82 | }
83 | } catch (IOException e) {
84 | throw new RuntimeException(e);
85 | }
86 | }
87 |
88 | @Override
89 | public String slideForward() {
90 | try {
91 | lineQueue.pollFirst();
92 | lineNumber++;
93 | if (lineQueue.isEmpty()) {
94 | String nextLine = getNextLine();
95 | if (nextLine != null) {
96 | lineQueue.addLast(nextLine);
97 | }
98 | return nextLine;
99 | } else {
100 | return lineQueue.peekFirst();
101 | }
102 | } catch (IOException e) {
103 | throw new RuntimeException(e);
104 | }
105 | }
106 |
107 | private String getNextLine() throws IOException {
108 | String nextLine = reader.readLine();
109 | while (matchesIgnorePattern(nextLine)) {
110 | nextLine = reader.readLine();
111 | }
112 |
113 | return getNextLineOrVirtualBlankLineAtEndOfStream(nextLine);
114 | }
115 |
116 | /**
117 | * Guarantees that a virtual blank line is injected at the end of the input stream to ensure the
118 | * parser attempts to transition to the {@code END} state, if necessary, when the end of stream is
119 | * reached.
120 | */
121 | private String getNextLineOrVirtualBlankLineAtEndOfStream(String nextLine) {
122 | if ((nextLine == null) && !isEndOfStream) {
123 | isEndOfStream = true;
124 | return "";
125 | }
126 |
127 | return nextLine;
128 | }
129 |
130 | private boolean matchesIgnorePattern(String line) {
131 | if (line == null) {
132 | return false;
133 | } else {
134 | for (Pattern pattern : ignorePatterns) {
135 | Matcher matcher = pattern.matcher(line);
136 | if (matcher.matches()) {
137 | return true;
138 | }
139 | }
140 | return false;
141 | }
142 | }
143 |
144 | @Override
145 | public String getFocusLine() {
146 | return lineQueue.element();
147 | }
148 |
149 | @Override
150 | public int getFocusLineNumber() {
151 | return lineNumber;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/test/java/com/github/smartcommit/MetricsTest.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import smile.validation.metric.AdjustedRandIndex;
5 |
6 | import static org.assertj.core.api.Assertions.*;
7 |
8 | public class MetricsTest {
9 | @Test
10 | public void testAdjustedRandIndex() {
11 | int[] a = new int[] {0, 0, 1, 1};
12 | int[] b = {0, 0, 1, 2};
13 | double ari = AdjustedRandIndex.of(a, b);
14 | assertThat(ari).isCloseTo(0.57, withinPercentage(10));
15 | assertThat(ari).isCloseTo(0.57, within(0.01));
16 | assertThat(ari).isCloseTo(0.57, offset(0.01));
17 | assertThat((float) ari).isCloseTo(0.57f, within(0.01f));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/com/github/smartcommit/TestContentType.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit;
2 |
3 | import com.github.smartcommit.model.constant.ContentType;
4 | import com.github.smartcommit.util.Utils;
5 | import org.apache.log4j.PropertyConfigurator;
6 | import org.junit.jupiter.api.BeforeAll;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 | public class TestContentType {
15 | @BeforeAll
16 | public static void setUpBeforeAll() {
17 | PropertyConfigurator.configure("log4j.properties");
18 | }
19 |
20 | @Test
21 | public void testAllImport() {
22 | List codeLines = new ArrayList<>();
23 | codeLines.add("import org.apache.log4j.PropertyConfigurator;");
24 | codeLines.add("import org.junit.jupiter.api.BeforeAll;");
25 | codeLines.add("import org.junit.jupiter.api.Test;");
26 | ContentType type = Utils.checkContentType(codeLines);
27 | assertThat(type).isEqualTo(ContentType.IMPORT);
28 | }
29 |
30 | @Test
31 | public void testAllComment() {
32 | List codeLines = new ArrayList<>();
33 | codeLines.add("/*import org.apache.log4j.PropertyConfigurator;");
34 | codeLines.add("*import org.junit.jupiter.api.BeforeAll;");
35 | codeLines.add("*/");
36 | codeLines.add("// jjjj");
37 | ContentType type = Utils.checkContentType(codeLines);
38 | assertThat(type).isEqualTo(ContentType.COMMENT);
39 | }
40 |
41 | @Test
42 | public void testMixed() {
43 | List codeLines = new ArrayList<>();
44 | codeLines.add("import org.apache.log4j.PropertyConfigurator;");
45 | codeLines.add("import org.junit.jupiter.api.BeforeAll;");
46 | codeLines.add("public class TestContentType {\n");
47 | codeLines.add("// jjjj");
48 | ContentType type = Utils.checkContentType(codeLines);
49 | assertThat(type).isEqualTo(ContentType.CODE);
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/src/test/java/com/github/smartcommit/TestDistance.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit;
2 |
3 | import com.github.smartcommit.evaluation.Evaluation;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | public class TestDistance {
14 | // @BeforeAll
15 | // public static void setUpBeforeAll() {
16 | // PropertyConfigurator.configure("log4j.properties");
17 | // }
18 |
19 | @Test
20 | public void testHierarchyDis() {
21 | Map hier1 = new HashMap<>();
22 | hier1.put("hunk", 5);
23 | hier1.put("member", 6);
24 | hier1.put("class", 7);
25 | hier1.put("package", 8);
26 | Map hier2 = new HashMap<>();
27 | hier2.put("hunk", 4);
28 | hier2.put("member", 6);
29 | hier2.put("class", 9);
30 | hier2.put("package", 10);
31 | assertThat(compareHierarchy(hier1, hier2)).isEqualTo(1);
32 | }
33 |
34 | private int compareHierarchy(Map hier1, Map hier2) {
35 | if (hier1.isEmpty() || hier2.isEmpty()) {
36 | return -1;
37 | }
38 | int res = 4;
39 | for (Map.Entry entry : hier1.entrySet()) {
40 | if (hier2.containsKey(entry.getKey())) {
41 | if (hier2.get(entry.getKey()).equals(entry.getValue())) {
42 | int t = -1;
43 | switch (entry.getKey()) {
44 | case "hunk":
45 | t = 0;
46 | break;
47 | case "member":
48 | t = 1;
49 | break;
50 | case "class":
51 | t = 2;
52 | break;
53 | case "package":
54 | t = 3;
55 | break;
56 | }
57 | res = Math.min(res, t);
58 | }
59 | }
60 | }
61 | return res;
62 | }
63 |
64 | @Test
65 | public void testEditDistance() {
66 | List list1 = new ArrayList<>();
67 | list1.add(0);
68 | list1.add(1);
69 | list1.add(2);
70 | list1.add(3);
71 | List list2 = new ArrayList<>();
72 | list2.add(1);
73 | list2.add(3);
74 | list2.add(0);
75 | list2.add(2);
76 | assertThat(Evaluation.editDistance(list1, list2)).isEqualTo(2);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/github/smartcommit/TestReformatting.java:
--------------------------------------------------------------------------------
1 | package com.github.smartcommit;
2 |
3 | import com.github.smartcommit.util.Utils;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | public class TestReformatting {
12 | @Test
13 | public void testWhitespace() {
14 | List s1 = new ArrayList<>();
15 | s1.add("public static String formatPath(String path) {");
16 | List s2 = new ArrayList<>();
17 | s2.add("public static String formatPath(String path) {\");");
18 | s2.add("\t\n");
19 | assertThat(Utils.convertListToStringNoFormat(s1))
20 | .isEqualTo(Utils.convertListToStringNoFormat(s2));
21 | }
22 |
23 | @Test
24 | public void testIndentation() {
25 |
26 | List s1 = new ArrayList<>();
27 | s1.add("public static String formatPath(String path) {");
28 | s1.add("}");
29 | List s2 = new ArrayList<>();
30 | s2.add(" public static String formatPath(String path) {\n\");");
31 | s2.add("\t}");
32 | assertThat(Utils.convertListToStringNoFormat(s1))
33 | .isEqualTo(Utils.convertListToStringNoFormat(s2));
34 | }
35 |
36 | @Test
37 | public void testComment() {
38 | List s1 = new ArrayList<>();
39 |
40 | s1.add(" /** Convert system-dependent path to the unified unix style */\n");
41 | s1.add(" public static String formatPath(String path) {\n");
42 |
43 | List s2 = new ArrayList<>();
44 | s2.add(" /** ");
45 | s2.add(" * Convert system-dependent path to the unified unix style ");
46 | s2.add("*/\n");
47 | s2.add(" public static String formatPath(String path) {\n");
48 |
49 | assertThat(Utils.convertListToStringNoFormat(s1))
50 | .isEqualTo(Utils.convertListToStringNoFormat(s2));
51 | }
52 |
53 | @Test
54 | public void testPunctuation() {
55 | List s1 = new ArrayList<>();
56 |
57 | s1.add("JAR(\".jar\"; \"Jar\"),");
58 | s1.add("XML(\".xml\"; \"XML\"),");
59 | s1.add("OTHER(\".*\"; \"Other\");");
60 |
61 | List s2 = new ArrayList<>();
62 | s2.add("JAR(\".jar\", \"Jar\");");
63 | s2.add("XML(\".xml\", \"XML\");");
64 | s2.add("OTHER(\".*\", \"Other\");");
65 |
66 | assertThat(Utils.convertListToStringNoFormat(s1))
67 | .isEqualTo(Utils.convertListToStringNoFormat(s2));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------