diff --git a/.travis.yml b/.travis.yml index 98917d95d..9166262fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,10 @@ sudo: required dist: trusty before_install: ./.travis_native_dependencies.sh before_script: + - pip2.7 install --upgrade --ignore-installed --user jsontree asttokens - export PATH=$PWD/srcML-src/bin:$PATH - export PATH=$PWD/cgum:$PATH + - export PATH=$PWD/pythonparser:$PATH script: ./gradlew check after_success: - ./gradlew coveralls uploadArchives diff --git a/.travis_native_dependencies.sh b/.travis_native_dependencies.sh index f25fea54b..32f2704d6 100755 --- a/.travis_native_dependencies.sh +++ b/.travis_native_dependencies.sh @@ -2,12 +2,12 @@ # 1) installing srcML # start by boost dependency sudo apt-get update -sudo apt-get install gcc g++ libxml2-dev libxslt1-dev libarchive-dev antlr libantlr-dev libcurl4-openssl-dev libssl-dev ocaml +sudo apt-get install gcc g++ libxml2-dev libxslt1-dev libarchive-dev antlr libantlr-dev libcurl4-openssl-dev libssl-dev wget https://netix.dl.sourceforge.net/project/boost/boost/1.55.0/boost_1_55_0.tar.gz tar -xzf boost_1_55_0.tar.gz cd boost_1_55_0 ./bootstrap.sh --without-libraries=atomic,chrono,context,coroutine,exception,graph,graph_parallel,iostreams,locale,log,math,mpi,python,random,serialization,signals,test,timer,wave -sudo ./b2 link=static cxxflags="-fPIC -static -Wl,--whole-archive" threading=multi install +sudo ./b2 -d0 link=static cxxflags="-fPIC -static -Wl,--whole-archive" threading=multi install cd .. # then srcml itself wget http://131.123.42.38/lmcrs/beta/srcML-src.tar.gz @@ -17,8 +17,10 @@ cmake . make cd .. # 2) installing cgum -sudo apt-get install ocaml ocaml-native-compilers camlp4 -git clone https://github.com/GumTreeDiff/cgum.git --depth 1 +sudo apt-get install ocaml ocaml-native-compilers camlp4 +git clone --branch v1.0.0 https://github.com/GumTreeDiff/cgum.git --depth 1 cd cgum make cd .. +# 3) installing pythonparser +git clone https://github.com/GumTreeDiff/pythonparser.git --depth 1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 85937ccdb..3662e0b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog of GumTree -## v2.1.1 +## v2.1.2 +* New dockerfile to run GumTree +* Python tree generator +* JavaParser tree generator +* Several minor bugfixes + +## v2.1.1 * New integration with antlr4 grammars * Add matlab grammar @@ -16,7 +22,7 @@ * Automatically produce nightlies * Add new srcML tree generator that can deal with C++, C, C# and Java files * Add new css tree generator based on ph-css -* Fix no label bug in ruby tree generator +* Fix no label bug in ruby tree generator * Add custom options to furnish srcml and cgum paths * Build script can exclude tests requiring an external tool * Use sparkjava for the web client instead of NanoHTTPd diff --git a/README.md b/README.md index 573b42533..df7656aba 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# GumTree ![Build Status](https://travis-ci.org/GumTreeDiff/gumtree.svg?branch=master) [![Coverage Status](https://coveralls.io/repos/GumTreeDiff/gumtree/badge.svg?branch=master)](https://coveralls.io/r/GumTreeDiff/gumtree?branch=master) +# GumTree + +## Status + +* Master branch: ![Build Status](https://travis-ci.org/GumTreeDiff/gumtree.svg?branch=master) [![Coverage Status](https://coveralls.io/repos/github/GumTreeDiff/gumtree/badge.svg?branch=master)](https://coveralls.io/github/GumTreeDiff/gumtree?branch=master) +* Develop branch: ![Build Status](https://travis-ci.org/GumTreeDiff/gumtree.svg?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/GumTreeDiff/gumtree/badge.svg?branch=develop)](https://coveralls.io/github/GumTreeDiff/gumtree?branch=develop) + +## Description GumTree is a complete framework to deal with source code as trees and compute differences between them. It includes possibilities such as: * converting a source file into a language-agnostic tree format @@ -11,7 +18,11 @@ Compared to classical code differencing tools, it has two important particularit * it works on a tree structure rather than a text structure, * it can detect moved or renamed elements in addition of deleted and inserted elements. -We already deal with a wide range of languages: Java, C, JavaScript and Ruby. More languages are coming soon, if you want to help contact [me](http://www.labri.fr/perso/falleri). +## Supported languages + +We already deal with a wide range of languages: C, Java, JavaScript, Python, R, Ruby. Click [here](https://github.com/GumTreeDiff/gumtree/wiki/Languages) for more details about the language we support. + +More languages are coming soon, if you want to help contact [me](http://www.labri.fr/perso/falleri). ## Citing GumTree @@ -37,7 +48,3 @@ We are researchers, therefore if you use GumTree in an academic work we would be ## Documentation To use GumTree, you can start by consulting the [Getting Started](https://github.com/GumTreeDiff/gumtree/wiki/Getting-Started) page from our [wiki](https://github.com/GumTreeDiff/gumtree/wiki). - -## Nightlies - -You can find the latest binaries of GumTree on [Bintray](https://bintray.com/jrfaller/GumTree/nightlies/2.1.0-2017-2#files). diff --git a/benchmark/build.gradle b/benchmark/build.gradle index c186754d5..f4e29cb5b 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -1,9 +1,7 @@ plugins { - id "me.champeau.gradle.jmh" version "0.4.3" + id "me.champeau.gradle.jmh" version "0.4.5" } -apply plugin: 'me.champeau.gradle.jmh' - uploadArchives.enabled = false jar.enabled = false diff --git a/benchmark/src/main/java/com/github/gumtree/dist/ActionsCollector.java b/benchmark/src/main/java/com/github/gumtree/dist/ActionsCollector.java index 83afae175..29a55d35c 100644 --- a/benchmark/src/main/java/com/github/gumtree/dist/ActionsCollector.java +++ b/benchmark/src/main/java/com/github/gumtree/dist/ActionsCollector.java @@ -34,8 +34,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; public class ActionsCollector { @@ -62,10 +64,13 @@ else if (args[0].equals("check")) public static void collectActions() throws Exception { Run.initGenerators(); - List paths = Files.walk(Paths.get(RES_DIR)).filter( + List outputPaths = new ArrayList<>(); + try (Stream paths = Files.walk(Paths.get(RES_DIR))) { + outputPaths = paths.filter( p -> p.getFileName().toString().matches(".*_v0_.*\\.xml")).collect(Collectors.toList()); + } Files.createDirectories(Paths.get(OUTPUT_DIR)); - for (Path path : paths) { + for (Path path : outputPaths) { Path otherPath = Paths.get(path.toString().replace("_v0_","_v1_")); Path outputPath = Paths.get(path.toString().replace("_v0_","_actions_")); TreeContext src = TreeIoUtils.fromXml().generateFromFile(path.toString()); @@ -83,11 +88,14 @@ public static void collectActions() throws Exception { public static void checkActions() throws Exception { Run.initGenerators(); - List paths = Files.walk(Paths.get(RES_DIR)).filter( + List outputPaths = new ArrayList<>(); + try (Stream paths = Files.walk(Paths.get(RES_DIR))) { + outputPaths = paths.filter( p -> p.getFileName().toString().matches(".*_v0_.*\\.xml")).collect(Collectors.toList()); + } boolean dirty = false; - StringBuffer b = new StringBuffer(); - for (Path path : paths) { + StringBuilder b = new StringBuilder(); + for (Path path : outputPaths) { Path otherPath = Paths.get(path.toString().replace("_v0_","_v1_")); TreeContext src = TreeIoUtils.fromXml().generateFromFile(path.toString()); TreeContext dst = TreeIoUtils.fromXml().generateFromFile(otherPath.toString()); diff --git a/benchmark/src/main/java/com/github/gumtree/dist/BenchmarkCollector.java b/benchmark/src/main/java/com/github/gumtree/dist/BenchmarkCollector.java index 723ba75c2..d3ca199ba 100644 --- a/benchmark/src/main/java/com/github/gumtree/dist/BenchmarkCollector.java +++ b/benchmark/src/main/java/com/github/gumtree/dist/BenchmarkCollector.java @@ -29,8 +29,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; public class BenchmarkCollector { @@ -46,10 +48,14 @@ public static void main(String[] args) throws Exception { public static void collectTrees(String dir) throws Exception { Run.initGenerators(); - List paths = Files.walk(Paths.get(dir)).filter( - p -> p.getFileName().toString().matches(".*_v0\\.(java|js|rb|c)")).collect(Collectors.toList()); + List outputPaths = new ArrayList<>(); + try (Stream paths = Files.walk(Paths.get(dir))) { + outputPaths = paths.filter( + p -> p.getFileName().toString().matches(".*_v0\\.(java|js|rb|c)") + ).collect(Collectors.toList()); + } Files.createDirectories(Paths.get(OUTPUT_DIR)); - for (Path path : paths) { + for (Path path : outputPaths) { Path otherPath = Paths.get(path.toString().replace("_v0.","_v1.")); String oldName = path.toString().replaceAll("[^a-zA-Z0-9_]", "_"); String newName = otherPath.toString().replaceAll("[^a-zA-Z0-9_]", "_"); diff --git a/build.gradle b/build.gradle index b5df67e38..cb2bcbc65 100644 --- a/build.gradle +++ b/build.gradle @@ -1,213 +1,207 @@ -buildscript { - repositories { - mavenCentral() - } - - dependencies { - classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3' - } -} -apply plugin: 'com.github.kt3k.coveralls' -apply plugin: 'jacoco' +plugins { + id 'jacoco' + id 'com.github.kt3k.coveralls' version '2.6.3' +} allprojects { - apply plugin: 'idea' + apply plugin: 'idea' - group = 'com.github.gumtreediff' - version = '2.1.1' + group = 'com.github.gumtreediff' + version = '2.1.2' - repositories { - mavenCentral() - jcenter() - } - apply plugin: 'idea' + repositories { + mavenCentral() + jcenter() + } } ext.isRelease = !project.version.endsWith("SNAPSHOT") subprojects { - apply plugin: 'java' - sourceCompatibility = '1.8' - targetCompatibility = '1.8' - - apply plugin: 'checkstyle' - checkstyle { - toolVersion = '7.0' - configFile = rootProject.file('gumtree_checkstyle.xml') - configProperties = [suppressionFile: "${rootProject.file('checkstyle_ignore.xml')}"] - ignoreFailures = false - showViolations = true - } - - dependencies { - testCompile 'junit:junit:[4,5)' - } - - test { - testLogging { - exceptionFormat = 'full' - } - } + apply plugin: 'java' + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + + apply plugin: 'checkstyle' + checkstyle { + toolVersion = '8.8' + configFile = rootProject.file('gumtree_checkstyle.xml') + configProperties = [suppressionFile: "${rootProject.file('checkstyle_ignore.xml')}"] + ignoreFailures = false + showViolations = true + } + + dependencies { + annotationProcessor 'org.atteo.classindex:classindex:3.4' + implementation 'org.atteo.classindex:classindex:3.4' + testImplementation 'junit:junit:[4,5)' + } + + test { + testLogging { + exceptionFormat = 'full' + } + } } -configure(subprojects.findAll {it.name != 'core'}) { - dependencies { - compile project(':core') - } +configure(subprojects.findAll { it.name != 'core' }) { + dependencies { + implementation project(':core') + } } configure(subprojects.findAll { it.name.startsWith('gen.antlr3') }) { - apply plugin: 'antlr' + apply plugin: 'antlr' - dependencies { - antlr 'org.antlr:antlr:3.5.2' - } + dependencies { + antlr 'org.antlr:antlr:3.5.2' + } - if (it.name.startsWith('gen.antlr3-')) { - dependencies { - compile project(':gen.antlr3') - } - } + if (it.name.startsWith('gen.antlr3-')) { + dependencies { + implementation project(':gen.antlr3') + } + } } configure(subprojects.findAll { it.name.startsWith('gen.antlr4') }) { - apply plugin: 'antlr' + apply plugin: 'antlr' - dependencies { - antlr 'org.antlr:antlr4:4.5' - } + dependencies { + antlr 'org.antlr:antlr4:4.5' + } - if (it.name.startsWith('gen.antlr4-')) { - dependencies { - compile project(':gen.antlr4') - } - } + if (it.name.startsWith('gen.antlr4-')) { + dependencies { + implementation project(':gen.antlr4') + } + } } def jacocoProjectsNames = ['core', 'gen.jdt', 'gen.c', 'gen.ruby', 'gen.js', 'gen.srcml', 'gen.css'] -def jacocoProjects = subprojects.findAll { it.name in jacocoProjectsNames} +def jacocoProjects = subprojects.findAll { it.name in jacocoProjectsNames } configure(jacocoProjects) { - apply plugin: 'jacoco' - jacoco { - toolVersion = '0.7.7.201606060606' - } - - jacocoTestReport { - reports { - html.enabled = true - xml.enabled = true - csv.enabled = false - } - } + apply plugin: 'jacoco' + jacoco { + toolVersion = '0.7.7.201606060606' + } + + jacocoTestReport { + reports { + html.enabled = true + xml.enabled = true + csv.enabled = false + } + } } task jacocoRootReport(type: JacocoReport, group: 'Coverage reports') { - description = 'Generates an aggregate report from all subprojects' - dependsOn(jacocoProjects.test) - - additionalSourceDirs = files(jacocoProjects.sourceSets.main.allSource.srcDirs) - sourceDirectories = files(jacocoProjects.sourceSets.main.allSource.srcDirs) - classDirectories = files(jacocoProjects.sourceSets.main.output) - executionData = files(jacocoProjects.jacocoTestReport.executionData) - - reports { - html.enabled = true - xml.enabled = true - } + description = 'Generates an aggregate report from all subprojects' + dependsOn(jacocoProjects.test) + + additionalSourceDirs = files(jacocoProjects.sourceSets.main.allSource.srcDirs) + sourceDirectories = files(jacocoProjects.sourceSets.main.allSource.srcDirs) + classDirectories = files(jacocoProjects.sourceSets.main.output) + executionData = files(jacocoProjects.jacocoTestReport.executionData) + + reports { + html.enabled = true + xml.enabled = true + } } coveralls { - sourceDirs = jacocoProjects.sourceSets.main.allSource.srcDirs.flatten() - jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" + sourceDirs = jacocoProjects.sourceSets.main.allSource.srcDirs.flatten() + jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" } tasks.coveralls { - group = 'Coverage reports' - description = 'Uploads the aggregated coverage report to Coveralls' + group = 'Coverage reports' + description = 'Uploads the aggregated coverage report to Coveralls' - dependsOn jacocoRootReport - onlyIf { System.env.'CI' && !JavaVersion.current().isJava9Compatible() } + dependsOn jacocoRootReport + onlyIf { System.env.'CI' && !JavaVersion.current().isJava9Compatible() } } evaluationDependsOnChildren(); configure(subprojects) { - if (project.hasProperty('skipNative') && it.hasProperty('isNative') && it.isNative == true) - it.test.enabled = false + if (project.hasProperty('skipNative') && it.hasProperty('isNative') && it.isNative == true) + it.test.enabled = false } if (project.hasProperty('mvn')) { - configure(subprojects.findAll { !(it.name in ['dist', 'benchmark']) }) { subproject -> - apply plugin: 'maven' - apply plugin: 'signing' - - task javadocJar(type: Jar) { - classifier = 'javadoc' - from javadoc - } - - task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource - } - - artifacts { - archives javadocJar, sourcesJar - } - - signing { - sign configurations.archives - } - - uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - - snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - - pom.project { - name "GumTree ${subproject.name}" - description subproject.description - url 'https://github.com/GumTreeDiff/gumtree/' - packaging 'jar' - - scm { - connection 'scm:git:https://github.com/GumTreeDiff/gumtree/' - developerConnection 'scm:git:https://github.com/GumTreeDiff/gumtree/' - url 'https://github.com/GumTreeDiff/gumtree/' - } - - licenses { - license { - name 'GNU Lesser General Public License v3.0' - url 'https://www.gnu.org/copyleft/lesser.html' - } - } - - developers { - developer { - id 'jre' - name 'Jean-Rémy Falleri' - email 'jr.falleri@gmail.com' - } - - developer { - id 'flop' - name 'Floréal Morandat' - email 'florealm@gmail.com' - } - } - } - } - } - } - } + configure(subprojects.findAll { !(it.name in ['dist', 'benchmark']) }) { subproject -> + apply plugin: 'maven' + apply plugin: 'signing' + + task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc + } + + task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allSource + } + + artifacts { + archives javadocJar, sourcesJar + } + + signing { + sign configurations.archives + } + + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + pom.project { + name "GumTree ${subproject.name}" + description subproject.description + url 'https://github.com/GumTreeDiff/gumtree/' + packaging 'jar' + + scm { + connection 'scm:git:https://github.com/GumTreeDiff/gumtree/' + developerConnection 'scm:git:https://github.com/GumTreeDiff/gumtree/' + url 'https://github.com/GumTreeDiff/gumtree/' + } + + licenses { + license { + name 'GNU Lesser General Public License v3.0' + url 'https://www.gnu.org/copyleft/lesser.html' + } + } + + developers { + developer { + id 'jre' + name 'Jean-Rémy Falleri' + email 'jr.falleri@gmail.com' + } + + developer { + id 'flop' + name 'Floréal Morandat' + email 'florealm@gmail.com' + } + } + } + } + } + } + } } diff --git a/client.diff/build.gradle b/client.diff/build.gradle index 4303dc56c..817d93c47 100644 --- a/client.diff/build.gradle +++ b/client.diff/build.gradle @@ -1,10 +1,12 @@ description = 'GumTree diff client' dependencies { - compile project(':client') - compile 'com.sparkjava:spark-core:2.6.0' + implementation project(':client') + implementation 'com.sparkjava:spark-core:2.7.1' + implementation 'net.sf.trove4j:trove4j:3.0.3' + implementation 'org.slf4j:slf4j-nop:1.7.25' // exclude servlet-api 2.0 because it causes a bug with spark-core - compile('org.rendersnake:rendersnake:1.9.0') { + implementation('org.rendersnake:rendersnake:1.9.0') { exclude group: 'javax.servlet', module: 'servlet-api' } } diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/AbstractDiffClient.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/AbstractDiffClient.java index 09922761b..63eaa9620 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/AbstractDiffClient.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/AbstractDiffClient.java @@ -40,7 +40,7 @@ public abstract class AbstractDiffClient e public static class Options implements Option.Context { public String matcher; - public ArrayList generators = new ArrayList<>(); + public String generator = null; public String src; public String dst; @@ -56,7 +56,7 @@ protected void process(String name, String[] args) { new Option("-g", "Preferred generator to use (can be used more than once).", 1) { @Override protected void process(String name, String[] args) { - generators.add(args[0]); + generator = args[0]; } }, new Option.Help(this) { @@ -124,10 +124,10 @@ protected TreeContext getDstTreeContext() { private TreeContext getTreeContext(String file) { try { TreeContext t; - if (opts.generators.isEmpty()) + if (opts.generator == null) t = Generators.getInstance().getTree(file); else - t = Generators.getInstance().getTree(opts.generators.get(0), file); + t = Generators.getInstance().getTree(opts.generator, file); return t; } catch (IOException e) { e.printStackTrace(); diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/dot/DotDiff.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/dot/DotDiff.java new file mode 100644 index 000000000..3cd7b08a0 --- /dev/null +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/dot/DotDiff.java @@ -0,0 +1,99 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2011-2015 Jean-Rémy Falleri + * Copyright 2011-2015 Floréal Morandat + */ + +package com.github.gumtreediff.client.diff.dot; + +import com.github.gumtreediff.client.Register; +import com.github.gumtreediff.client.diff.AbstractDiffClient; +import com.github.gumtreediff.client.diff.swing.MappingsPanel; +import com.github.gumtreediff.io.TreeIoUtils; +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; +import com.github.gumtreediff.tree.TreeContext; + +import javax.swing.*; +import java.io.StringWriter; +import java.io.Writer; + +@Register(description = "A dot diff client", options = AbstractDiffClient.Options.class) +public final class DotDiff extends AbstractDiffClient { + + public DotDiff(String[] args) { + super(args); + } + + @Override + public void run() { + final Matcher matcher = matchTrees(); + try { + StringWriter writer = new StringWriter(); + writer.write("digraph G {\n"); + writer.write("node [style=filled];\n"); + writer.write("subgraph cluster_src {\n"); + writeTree(getSrcTreeContext(), writer, matcher); + writer.write("}\n"); + writer.write("subgraph cluster_dst {\n"); + writeTree(getDstTreeContext(), writer, matcher); + writer.write("}\n"); + for (Mapping m: matcher.getMappingsAsSet()) { + writer.write(String.format("%s -> %s [style=dashed]\n;", + getDotId(getSrcTreeContext(), m.getFirst()), getDotId(getDstTreeContext(), m.getSecond()))); + } + writer.write("}\n"); + System.out.println(writer.toString()); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private void writeTree(TreeContext context, Writer writer, Matcher matcher) throws Exception { + for (ITree tree : context.getRoot().getTrees()) { + String fillColor = "red"; + if (matcher.getMappings().hasSrc(tree) || matcher.getMappings().hasDst(tree)) + fillColor = "blue"; + writer.write(String.format("%s [label=\"%s\", color=%s];\n", + getDotId(context, tree), getDotLabel(context, tree), fillColor)); + if (tree.getParent() != null) + writer.write(String.format("%s -> %s;\n", + getDotId(context, tree.getParent()), getDotId(context, tree))); + } + + } + + private String getDotId(TreeContext context, ITree tree) { + return "n_" + context.hashCode() + "_" + tree.getId(); + } + + private String getDotLabel(TreeContext context, ITree tree) { + String label = tree.toPrettyString(context); + if (label.contains("\"") || label.contains("\\s")) + label = label.replaceAll("\"", "").replaceAll("\\s", "").replaceAll("\\\\", ""); + if (label.length() > 30) + label = label.substring(0, 30); + return label; + } + + @Override + protected Options newOptions() { + return new Options(); + } +} \ No newline at end of file diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/MappingsPanel.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/MappingsPanel.java index be8e4109a..21ee446c9 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/MappingsPanel.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/MappingsPanel.java @@ -26,6 +26,10 @@ import java.awt.GridLayout; import java.io.FileReader; import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -94,16 +98,32 @@ public MappingsPanel(String srcPath, String dstPath, TreeContext src, TreeContex add(split); try { - txtSrc.getUI().getEditorKit(txtSrc).read(new FileReader(srcPath), txtSrc.getDocument(), 0); - txtDst.getUI().getEditorKit(txtDst).read(new FileReader(dstPath), txtDst.getDocument(), 0); + txtSrc + .getUI() + .getEditorKit(txtSrc) + .read(new StringReader(readFileAsString(srcPath)), txtSrc.getDocument(), 0); + txtDst + .getUI() + .getEditorKit(txtDst) + .read(new StringReader(readFileAsString(dstPath)), txtDst.getDocument(), 0); } catch (IOException | BadLocationException e) { e.printStackTrace(); } - setPreferredSize(new Dimension(1024, 768)); openNodes(); } + private static String readFileAsString(String filePath) { + String content = ""; + try { + content = new String(Files.readAllBytes(Paths.get(filePath))); + content = content.replaceAll("\r\n", "\r\n "); + } catch (IOException e) { + e.printStackTrace(); + } + return content; + } + private void openNodes() { for (ITree t: classifyTrees.getSrcDelTrees()) openNode(panSrc, t); for (ITree t: classifyTrees.getDstAddTrees()) openNode(panDst, t); diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingDiff.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingDiff.java index 3b6531570..9c90f538e 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingDiff.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingDiff.java @@ -38,6 +38,7 @@ public SwingDiff(String[] args) { public void run() { final Matcher matcher = matchTrees(); javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { JFrame frame = new JFrame("GumTree"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingTree.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingTree.java index 5bbeb0fd7..6ba558cf5 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingTree.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/SwingTree.java @@ -31,6 +31,7 @@ public final class SwingTree { public static void main(String[] args) throws IOException { final TreeContext t = Generators.getInstance().getTree(args[0]); javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { createAndShow(t); } diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/TreePanel.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/TreePanel.java index 5682da2eb..0f3a4fb1b 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/TreePanel.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/swing/TreePanel.java @@ -55,6 +55,8 @@ public TreePanel(final TreeContext tree, TreeCellRenderer renderer) { jtree = new JTree(top) { private static final long serialVersionUID = 1L; + + @Override public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { @@ -102,6 +104,7 @@ private void createNodes(DefaultMutableTreeNode parent, ITree tree) { DefaultMutableTreeNode node = new DefaultMutableTreeNode(tree); trees.put(tree, node); parent.add(node); - for (ITree child: tree.getChildren()) createNodes(node, child); + for (ITree child: tree.getChildren()) + createNodes(node, child); } } diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/DirectoryComparatorView.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/DirectoryComparatorView.java index 9a6e9b326..292a4fd16 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/DirectoryComparatorView.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/DirectoryComparatorView.java @@ -140,7 +140,7 @@ public void renderOn(HtmlCanvas html) throws IOException { } - private class AddedOrDeletedFiles implements Renderable { + private static class AddedOrDeletedFiles implements Renderable { private Set files; diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/HtmlDiffs.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/HtmlDiffs.java index ae405258c..203534184 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/HtmlDiffs.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/HtmlDiffs.java @@ -31,6 +31,8 @@ import gnu.trove.map.hash.TIntIntHashMap; import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; import java.util.List; public final class HtmlDiffs { @@ -124,7 +126,7 @@ public void produce() throws IOException { } StringWriter w1 = new StringWriter(); - BufferedReader r = new BufferedReader(new FileReader(fSrc)); + BufferedReader r = Files.newBufferedReader(fSrc.toPath(), Charset.forName("UTF-8")); int cursor = 0; while (r.ready()) { @@ -139,7 +141,7 @@ public void produce() throws IOException { srcDiff = w1.toString(); StringWriter w2 = new StringWriter(); - r = new BufferedReader(new FileReader(fDst)); + r = Files.newBufferedReader(fDst.toPath(), Charset.forName("UTF-8")); cursor = 0; while (r.ready()) { diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/TagIndex.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/TagIndex.java index 2f77f8917..6a28517b9 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/TagIndex.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/TagIndex.java @@ -53,7 +53,7 @@ public void addEndTag(int pos, String tag) { public String getEndTags(int pos) { if (!endTags.containsKey(pos)) return ""; - StringBuffer b = new StringBuffer(); + StringBuilder b = new StringBuilder(); for (String s: endTags.get(pos)) b.append(s); return b.toString(); } @@ -61,7 +61,7 @@ public String getEndTags(int pos) { public String getStartTags(int pos) { if (!startTags.containsKey(pos)) return ""; - StringBuffer b = new StringBuffer(); + StringBuilder b = new StringBuilder(); for (String s: startTags.get(pos)) b.append(s); return b.toString(); diff --git a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/WebDiff.java b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/WebDiff.java index 7d93ec4ca..3877870db 100644 --- a/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/WebDiff.java +++ b/client.diff/src/main/java/com/github/gumtreediff/client/diff/web/WebDiff.java @@ -46,13 +46,13 @@ public WebDiff(String[] args) { } static class Options extends AbstractDiffClient.Options { - protected int defaultPort = Integer.parseInt(System.getProperty("gumtree.client.web.port", "4567")); + protected int defaultPort = Integer.parseInt(System.getProperty("gt.webdiff.port", "4567")); boolean stdin = true; @Override public Option[] values() { return Option.Context.addValue(super.values(), - new Option("--port", String.format("set server port (default to)", defaultPort), 1) { + new Option("--port", String.format("set server port (default to %d)", defaultPort), 1) { @Override protected void process(String name, String[] args) { int p = Integer.parseInt(args[0]); diff --git a/client/build.gradle b/client/build.gradle index 3c7f0a452..27a6bd80f 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -1,5 +1 @@ description = 'GumTree abstract client module.' - -dependencies { - compile 'org.reflections:reflections:0.9.11' -} diff --git a/client/src/main/java/com/github/gumtreediff/client/Client.java b/client/src/main/java/com/github/gumtreediff/client/Client.java index 3c1e8d3f7..a1bc27955 100644 --- a/client/src/main/java/com/github/gumtreediff/client/Client.java +++ b/client/src/main/java/com/github/gumtreediff/client/Client.java @@ -20,8 +20,9 @@ package com.github.gumtreediff.client; -import java.io.IOException; +import org.atteo.classindex.IndexSubclasses; +@IndexSubclasses public abstract class Client { @SuppressWarnings("unused") public Client(String[] args) {} diff --git a/client/src/main/java/com/github/gumtreediff/client/Option.java b/client/src/main/java/com/github/gumtreediff/client/Option.java index e584e1f69..83f680e99 100644 --- a/client/src/main/java/com/github/gumtreediff/client/Option.java +++ b/client/src/main/java/com/github/gumtreediff/client/Option.java @@ -106,12 +106,13 @@ protected boolean hasOption(String arg) { return key.equals(arg); } - protected abstract void process(String name, String[] args); + protected abstract void process(String name, String[] args); public String formatHelpText() { return String.format("%s%s\t%s", key, (paramCount > 0 ? " <" + paramCount + ">" : ""), description); } + @Override public String toString() { return key; } diff --git a/client/src/main/java/com/github/gumtreediff/client/Register.java b/client/src/main/java/com/github/gumtreediff/client/Register.java index cd3c55e44..406af3ac3 100644 --- a/client/src/main/java/com/github/gumtreediff/client/Register.java +++ b/client/src/main/java/com/github/gumtreediff/client/Register.java @@ -21,6 +21,7 @@ package com.github.gumtreediff.client; import com.github.gumtreediff.gen.Registry; +import org.atteo.classindex.IndexAnnotated; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -29,6 +30,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@IndexAnnotated public @interface Register { String name() default no_value; String description() default ""; diff --git a/client/src/main/java/com/github/gumtreediff/client/Run.java b/client/src/main/java/com/github/gumtreediff/client/Run.java index 6f04f6694..1db5f8eff 100644 --- a/client/src/main/java/com/github/gumtreediff/client/Run.java +++ b/client/src/main/java/com/github/gumtreediff/client/Run.java @@ -23,13 +23,10 @@ import com.github.gumtreediff.gen.Generators; import com.github.gumtreediff.gen.Registry; import com.github.gumtreediff.gen.TreeGenerator; -import org.reflections.Reflections; +import org.atteo.classindex.ClassIndex; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; public class Run { @@ -42,7 +39,7 @@ public Option[] values() { @Override protected void process(String name, String[] args) { - String key = args[0].startsWith("gumtree.") ? args[0] : "gumtree." + args[0]; + String key = args[0].startsWith("gt.") ? args[0] : "gt." + args[0]; System.setProperty(key, args[1]); } }, @@ -53,27 +50,23 @@ protected void process(String name, String[] args) { } public static void initGenerators() { - Reflections reflections = new Reflections("com.github.gumtreediff.gen"); - - reflections.getSubTypesOf(TreeGenerator.class).forEach( + ClassIndex.getSubclasses(TreeGenerator.class).forEach( gen -> { com.github.gumtreediff.gen.Register a = gen.getAnnotation(com.github.gumtreediff.gen.Register.class); if (a != null) Generators.getInstance().install(gen, a); - }); + }); } public static void initClients() { - Reflections reflections = new Reflections("com.github.gumtreediff.client"); - - reflections.getSubTypesOf(Client.class).forEach( + ClassIndex.getSubclasses(Client.class).forEach( cli -> { com.github.gumtreediff.client.Register a = cli.getAnnotation(com.github.gumtreediff.client.Register.class); - if (a != null) - Clients.getInstance().install(cli, a); - }); + if (a != null) + Clients.getInstance().install(cli, a); + }); } static { diff --git a/client/src/main/java/com/github/gumtreediff/client/Serializer.java b/client/src/main/java/com/github/gumtreediff/client/Serializer.java index 826b8ec2f..c3f12e668 100644 --- a/client/src/main/java/com/github/gumtreediff/client/Serializer.java +++ b/client/src/main/java/com/github/gumtreediff/client/Serializer.java @@ -30,7 +30,9 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @Register(name = "parse", description = "Parse file and dump result") public class Serializer extends Client { @@ -39,6 +41,7 @@ public class Serializer extends Client { static class Options implements Option.Context { protected OutputFormat format = OutputFormat.JSON; + protected String generator = null; protected String output = null; protected String[] files; @@ -58,7 +61,13 @@ protected void process(String name, String[] args) { new Option("-o", "Output filename (or directory if more than one file), defaults to stdout", 1) { @Override protected void process(String name, String[] args) { - + output = args[0]; + } + }, + new Option("-g", "Preferred generator to use.", 1) { + @Override + protected void process(String name, String[] args) { + generator = args[0]; } } }; @@ -119,7 +128,7 @@ public void run() throws IOException { for (String file : opts.files) { try { - TreeContext tc = Generators.getInstance().getTree(file); + TreeContext tc = getTreeContext(file); opts.format.getSerializer(tc).writeTo(opts.output == null ? System.out : new FileOutputStream(opts.output)); @@ -128,4 +137,18 @@ public void run() throws IOException { } } } + + private TreeContext getTreeContext(String file) { + try { + TreeContext t; + if (opts.generator == null) + t = Generators.getInstance().getTree(file); + else + t = Generators.getInstance().getTree(opts.generator, file); + return t; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } } \ No newline at end of file diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 000000000..1ec88a898 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project maintainer at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/core/build.gradle b/core/build.gradle index 73c0f7424..c3c0eabc7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,10 +1,10 @@ description = 'GumTree core module.' dependencies { - compile 'com.github.mpkorstanje:simmetrics-core:3.2.3' - compile 'net.sf.trove4j:trove4j:3.0.3' - compile 'com.google.code.gson:gson:2.8.2' - compile 'org.jgrapht:jgrapht-core:1.0.1' + implementation 'com.github.mpkorstanje:simmetrics-core:3.2.3' + implementation 'net.sf.trove4j:trove4j:3.0.3' + implementation 'com.google.code.gson:gson:2.8.2' + implementation 'org.jgrapht:jgrapht-core:1.0.1' } allprojects { @@ -13,4 +13,4 @@ allprojects { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/github/gumtreediff/actions/RootAndLeavesClassifier.java b/core/src/main/java/com/github/gumtreediff/actions/RootAndLeavesClassifier.java index 846807eb2..2e615922d 100644 --- a/core/src/main/java/com/github/gumtreediff/actions/RootAndLeavesClassifier.java +++ b/core/src/main/java/com/github/gumtreediff/actions/RootAndLeavesClassifier.java @@ -73,13 +73,6 @@ public void classify() { fSrcDelTrees.add(t); } srcDelTrees = fSrcDelTrees; - - Set fSrcMvTrees = new HashSet<>(); // FIXME check why it's unused - for (ITree t: srcDelTrees) { - if (!srcDelTrees.contains(t.getParent())) - fSrcDelTrees.add(t); - } - srcDelTrees = fSrcDelTrees; } } diff --git a/core/src/main/java/com/github/gumtreediff/actions/RootsClassifier.java b/core/src/main/java/com/github/gumtreediff/actions/RootsClassifier.java index 991f3c43d..005039d94 100644 --- a/core/src/main/java/com/github/gumtreediff/actions/RootsClassifier.java +++ b/core/src/main/java/com/github/gumtreediff/actions/RootsClassifier.java @@ -45,6 +45,7 @@ public RootsClassifier(TreeContext src, TreeContext dst, Matcher m) { super(src, dst, m); } + @Override public void classify() { for (Action a: actions) { if (a instanceof Delete) srcDelTrees.add(a.getNode()); diff --git a/core/src/main/java/com/github/gumtreediff/actions/model/Update.java b/core/src/main/java/com/github/gumtreediff/actions/model/Update.java index d2fa5de03..cac98777e 100644 --- a/core/src/main/java/com/github/gumtreediff/actions/model/Update.java +++ b/core/src/main/java/com/github/gumtreediff/actions/model/Update.java @@ -43,7 +43,7 @@ public String getValue() { @Override public String toString() { - return getName() + " " + node.toString() + " from " + node.getLabel() + " to " + value; + return getName() + " " + node.toShortString() + " from " + node.getLabel() + " to " + value; } @Override diff --git a/core/src/main/java/com/github/gumtreediff/gen/ExternalProcessTreeGenerator.java b/core/src/main/java/com/github/gumtreediff/gen/ExternalProcessTreeGenerator.java new file mode 100644 index 000000000..98b1c9bca --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/gen/ExternalProcessTreeGenerator.java @@ -0,0 +1,71 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2019 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; + +public abstract class ExternalProcessTreeGenerator extends TreeGenerator { + + public String readStandardOutput(Reader r) throws IOException { + // TODO avoid recreating file if supplied reader is already a file + File f = dumpReaderInTempFile(r); + ProcessBuilder b = new ProcessBuilder(getCommandLine(f.getAbsolutePath())); + b.directory(f.getParentFile()); + Process p = b.start(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), "UTF-8"));) { + StringBuilder buf = new StringBuilder(); + String line = null; + while ((line = br.readLine()) != null) + buf.append(line + System.lineSeparator()); + p.waitFor(); + if (p.exitValue() != 0) + throw new RuntimeException(buf.toString()); + r.close(); + p.destroy(); + return buf.toString(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + f.delete(); + } + } + + private File dumpReaderInTempFile(Reader r) throws IOException { + File f = File.createTempFile("gumtree", ""); + try ( + Writer w = Files.newBufferedWriter(f.toPath(), Charset.forName("UTF-8")); + ) { + char[] buf = new char[8192]; + while (true) + { + int length = r.read(buf); + if (length < 0) + break; + w.write(buf, 0, length); + } + } + return f; + } + + protected abstract String[] getCommandLine(String file); + +} diff --git a/core/src/main/java/com/github/gumtreediff/gen/Generators.java b/core/src/main/java/com/github/gumtreediff/gen/Generators.java index d2d23cf11..f25fe66a3 100644 --- a/core/src/main/java/com/github/gumtreediff/gen/Generators.java +++ b/core/src/main/java/com/github/gumtreediff/gen/Generators.java @@ -72,7 +72,7 @@ protected boolean handle(String key) { @Override public String toString() { - return String.format("%s: %s", Arrays.toString(accept), clazz.getCanonicalName()); + return String.format("%d\t%s: %s", priority, Arrays.toString(accept), clazz.getCanonicalName()); } }; } diff --git a/core/src/main/java/com/github/gumtreediff/gen/Register.java b/core/src/main/java/com/github/gumtreediff/gen/Register.java index d3fa52369..9e20d13cc 100644 --- a/core/src/main/java/com/github/gumtreediff/gen/Register.java +++ b/core/src/main/java/com/github/gumtreediff/gen/Register.java @@ -20,12 +20,15 @@ package com.github.gumtreediff.gen; +import org.atteo.classindex.IndexAnnotated; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) +@IndexAnnotated @Target(ElementType.TYPE) public @interface Register { String id(); diff --git a/core/src/main/java/com/github/gumtreediff/gen/Registry.java b/core/src/main/java/com/github/gumtreediff/gen/Registry.java index b10a9e4ec..2f0d467a2 100644 --- a/core/src/main/java/com/github/gumtreediff/gen/Registry.java +++ b/core/src/main/java/com/github/gumtreediff/gen/Registry.java @@ -34,7 +34,7 @@ public abstract class Registry { return cmp; }); - public class Priority { + public static class Priority { public static final int MAXIMUM = 0; public static final int HIGH = 25; public static final int MEDIUM = 50; diff --git a/core/src/main/java/com/github/gumtreediff/gen/SyntaxException.java b/core/src/main/java/com/github/gumtreediff/gen/SyntaxException.java new file mode 100644 index 000000000..c4f6b13dd --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/gen/SyntaxException.java @@ -0,0 +1,30 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2019 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen; + +import java.io.Reader; + +public class SyntaxException extends RuntimeException { + + public SyntaxException(TreeGenerator g, Reader r) { + super(String.format("Syntax error on source code %s using generator %s", r, g)); + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/gen/TreeGenerator.java b/core/src/main/java/com/github/gumtreediff/gen/TreeGenerator.java index 3e7870d3a..f72059401 100644 --- a/core/src/main/java/com/github/gumtreediff/gen/TreeGenerator.java +++ b/core/src/main/java/com/github/gumtreediff/gen/TreeGenerator.java @@ -21,9 +21,14 @@ package com.github.gumtreediff.gen; import com.github.gumtreediff.tree.TreeContext; +import org.atteo.classindex.IndexSubclasses; import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +@IndexSubclasses public abstract class TreeGenerator { protected abstract TreeContext generate(Reader r) throws IOException; @@ -35,15 +40,15 @@ public TreeContext generateFromReader(Reader r) throws IOException { } public TreeContext generateFromFile(String path) throws IOException { - return generateFromReader(new FileReader(path)); + return generateFromReader(Files.newBufferedReader(Paths.get(path), Charset.forName("UTF-8"))); } public TreeContext generateFromFile(File file) throws IOException { - return generateFromReader(new FileReader(file)); + return generateFromReader(Files.newBufferedReader(file.toPath(), Charset.forName("UTF-8"))); } public TreeContext generateFromStream(InputStream stream) throws IOException { - return generateFromReader(new InputStreamReader(stream)); + return generateFromReader(new InputStreamReader(stream, "UTF-8")); } public TreeContext generateFromString(String content) throws IOException { diff --git a/core/src/main/java/com/github/gumtreediff/io/IndentingXMLStreamWriter.java b/core/src/main/java/com/github/gumtreediff/io/IndentingXMLStreamWriter.java index 3793c51e0..b12ad0bc2 100644 --- a/core/src/main/java/com/github/gumtreediff/io/IndentingXMLStreamWriter.java +++ b/core/src/main/java/com/github/gumtreediff/io/IndentingXMLStreamWriter.java @@ -91,6 +91,7 @@ public IndentingXMLStreamWriter(XMLStreamWriter out) { /** newLine followed by copies of indent. */ private char[] linePrefix = null; + @Override public void setIndent(String indent) { if (!indent.equals(this.indent)) { this.indent = indent; @@ -98,10 +99,12 @@ public void setIndent(String indent) { } } + @Override public String getIndent() { return indent; } + @Override public void setNewLine(String newLine) { if (!newLine.equals(this.newLine)) { this.newLine = newLine; @@ -122,64 +125,75 @@ public static String getLineSeparator() { return NORMAL_END_OF_LINE; } + @Override public String getNewLine() { return newLine; } + @Override public void writeStartDocument() throws XMLStreamException { beforeMarkup(); out.writeStartDocument(); afterMarkup(); } + @Override public void writeStartDocument(String version) throws XMLStreamException { beforeMarkup(); out.writeStartDocument(version); afterMarkup(); } + @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { beforeMarkup(); out.writeStartDocument(encoding, version); afterMarkup(); } + @Override public void writeDTD(String dtd) throws XMLStreamException { beforeMarkup(); out.writeDTD(dtd); afterMarkup(); } + @Override public void writeProcessingInstruction(String target) throws XMLStreamException { beforeMarkup(); out.writeProcessingInstruction(target); afterMarkup(); } + @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { beforeMarkup(); out.writeProcessingInstruction(target, data); afterMarkup(); } + @Override public void writeComment(String data) throws XMLStreamException { beforeMarkup(); out.writeComment(data); afterMarkup(); } + @Override public void writeEmptyElement(String localName) throws XMLStreamException { beforeMarkup(); out.writeEmptyElement(localName); afterMarkup(); } + @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { beforeMarkup(); out.writeEmptyElement(namespaceURI, localName); afterMarkup(); } + @Override public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { beforeMarkup(); @@ -187,18 +201,21 @@ public void writeEmptyElement(String prefix, String localName, String namespaceU afterMarkup(); } + @Override public void writeStartElement(String localName) throws XMLStreamException { beforeStartElement(); out.writeStartElement(localName); afterStartElement(); } + @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { beforeStartElement(); out.writeStartElement(namespaceURI, localName); afterStartElement(); } + @Override public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { beforeStartElement(); @@ -206,32 +223,38 @@ public void writeStartElement(String prefix, String localName, String namespaceU afterStartElement(); } + @Override public void writeCharacters(String text) throws XMLStreamException { out.writeCharacters(text); afterData(); } + @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { out.writeCharacters(text, start, len); afterData(); } + @Override public void writeCData(String data) throws XMLStreamException { out.writeCData(data); afterData(); } + @Override public void writeEntityRef(String name) throws XMLStreamException { out.writeEntityRef(name); afterData(); } + @Override public void writeEndElement() throws XMLStreamException { beforeEndElement(); out.writeEndElement(); afterEndElement(); } + @Override public void writeEndDocument() throws XMLStreamException { try { while (depth > 0) { diff --git a/core/src/main/java/com/github/gumtreediff/io/StreamWriterDelegate.java b/core/src/main/java/com/github/gumtreediff/io/StreamWriterDelegate.java index 4425c0626..48dde7044 100644 --- a/core/src/main/java/com/github/gumtreediff/io/StreamWriterDelegate.java +++ b/core/src/main/java/com/github/gumtreediff/io/StreamWriterDelegate.java @@ -50,134 +50,166 @@ protected StreamWriterDelegate(XMLStreamWriter out) { protected XMLStreamWriter out; + @Override public Object getProperty(String name) throws IllegalArgumentException { return out.getProperty(name); } + @Override public NamespaceContext getNamespaceContext() { return out.getNamespaceContext(); } + @Override public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { out.setNamespaceContext(context); } + @Override public void setDefaultNamespace(String uri) throws XMLStreamException { out.setDefaultNamespace(uri); } + @Override public void writeStartDocument() throws XMLStreamException { out.writeStartDocument(); } + @Override public void writeStartDocument(String version) throws XMLStreamException { out.writeStartDocument(version); } + @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { out.writeStartDocument(encoding, version); } + @Override public void writeDTD(String dtd) throws XMLStreamException { out.writeDTD(dtd); } + @Override public void writeProcessingInstruction(String target) throws XMLStreamException { out.writeProcessingInstruction(target); } + @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { out.writeProcessingInstruction(target, data); } + @Override public void writeComment(String data) throws XMLStreamException { out.writeComment(data); } + @Override public void writeEmptyElement(String localName) throws XMLStreamException { out.writeEmptyElement(localName); } + @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { out.writeEmptyElement(namespaceURI, localName); } + @Override public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { out.writeEmptyElement(prefix, localName, namespaceURI); } + @Override public void writeStartElement(String localName) throws XMLStreamException { out.writeStartElement(localName); } + @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { out.writeStartElement(namespaceURI, localName); } + @Override public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { out.writeStartElement(prefix, localName, namespaceURI); } + @Override public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { out.writeDefaultNamespace(namespaceURI); } + @Override public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { out.writeNamespace(prefix, namespaceURI); } + @Override public String getPrefix(String uri) throws XMLStreamException { return out.getPrefix(uri); } + @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { out.setPrefix(prefix, uri); } + @Override public void writeAttribute(String localName, String value) throws XMLStreamException { out.writeAttribute(localName, value); } + @Override public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { out.writeAttribute(namespaceURI, localName, value); } + @Override public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { out.writeAttribute(prefix, namespaceURI, localName, value); } + @Override public void writeCharacters(String text) throws XMLStreamException { out.writeCharacters(text); } + @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { out.writeCharacters(text, start, len); } + @Override public void writeCData(String data) throws XMLStreamException { out.writeCData(data); } + @Override public void writeEntityRef(String name) throws XMLStreamException { out.writeEntityRef(name); } + @Override public void writeEndElement() throws XMLStreamException { out.writeEndElement(); } + @Override public void writeEndDocument() throws XMLStreamException { out.writeEndDocument(); } + @Override public void flush() throws XMLStreamException { out.flush(); } + @Override public void close() throws XMLStreamException { out.close(); } diff --git a/core/src/main/java/com/github/gumtreediff/io/TreeIoUtils.java b/core/src/main/java/com/github/gumtreediff/io/TreeIoUtils.java index 4eeadb41f..38d9ae3b0 100644 --- a/core/src/main/java/com/github/gumtreediff/io/TreeIoUtils.java +++ b/core/src/main/java/com/github/gumtreediff/io/TreeIoUtils.java @@ -37,6 +37,10 @@ import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayDeque; import java.util.Iterator; import java.util.Map.Entry; import java.util.Stack; @@ -123,11 +127,12 @@ public abstract static class AbstractSerializer { public void writeTo(OutputStream writer) throws Exception { // FIXME Since the stream is already open, we should not close it, however due to semantic issue // it should stay like this - try (OutputStreamWriter os = new OutputStreamWriter(writer)) { + try (OutputStreamWriter os = new OutputStreamWriter(writer, "UTF-8")) { writeTo(os); } } + @Override public String toString() { try (StringWriter s = new StringWriter()) { writeTo(s); @@ -138,13 +143,13 @@ public String toString() { } public void writeTo(String file) throws Exception { - try (FileWriter w = new FileWriter(file)) { + try (Writer w = Files.newBufferedWriter(Paths.get(file), Charset.forName("UTF-8"))) { writeTo(w); } } public void writeTo(File file) throws Exception { - try (FileWriter w = new FileWriter(file)) { + try (Writer w = Files.newBufferedWriter(file.toPath(), Charset.forName("UTF-8"))) { writeTo(w); } } @@ -162,6 +167,7 @@ public TreeSerializer(TreeContext ctx) { protected abstract TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer) throws Exception; + @Override public void writeTo(Writer writer) throws Exception { TreeFormatter formatter = newFormatter(context, serializers, writer); try { @@ -266,7 +272,7 @@ public FormatException(Exception cause) { } @Override - public Exception getCause() { + public synchronized Exception getCause() { return cause; } } @@ -629,7 +635,7 @@ protected TreeContext generate(Reader source) throws IOException { XMLInputFactory fact = XMLInputFactory.newInstance(); TreeContext context = new TreeContext(); try { - Stack trees = new Stack<>(); + ArrayDeque trees = new ArrayDeque<>(); XMLEventReader r = fact.createXMLEventReader(source); while (r.hasNext()) { XMLEvent e = r.nextEvent(); @@ -651,12 +657,12 @@ protected TreeContext generate(Reader source) throws IOException { if (trees.isEmpty()) context.setRoot(t); else - t.setParentAndUpdateChildren(trees.peek()); - trees.push(t); + t.setParentAndUpdateChildren(trees.peekFirst()); + trees.addFirst(t); } else if (e instanceof EndElement) { if (!((EndElement)e).getName().getLocalPart().equals("tree")) // FIXME need to deal with options continue; - trees.pop(); + trees.removeFirst(); } } context.validate(); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatcher.java index 2f8303878..fcead5cdc 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatcher.java @@ -31,6 +31,7 @@ public CompositeMatcher(ITree src, ITree dst, MappingStore store, Matcher[] matc this.matchers = matchers; } + @Override public void match() { for (Matcher matcher : matchers) { matcher.match(); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatchers.java b/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatchers.java index 3bd64ece6..3244aa1f4 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatchers.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/CompositeMatchers.java @@ -43,6 +43,16 @@ public ClassicGumtree(ITree src, ITree dst, MappingStore store) { } } + @Register(id = "gumtree-topdown", defaultMatcher = true, priority = Registry.Priority.HIGH) + public static class GumtreeTopDown extends CompositeMatcher { + + public GumtreeTopDown(ITree src, ITree dst, MappingStore store) { + super(src, dst, store, new Matcher[]{ + new GreedySubtreeMatcher(src, dst, store) + }); + } + } + @Register(id = "gumtree-complete") public static class CompleteGumtreeMatcher extends CompositeMatcher { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/Matcher.java b/core/src/main/java/com/github/gumtreediff/matchers/Matcher.java index 86d0bac54..537b9f9a1 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/Matcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/Matcher.java @@ -21,12 +21,14 @@ package com.github.gumtreediff.matchers; import com.github.gumtreediff.tree.ITree; +import org.atteo.classindex.IndexSubclasses; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; +@IndexSubclasses public abstract class Matcher { public static final Logger LOGGER = Logger.getLogger("com.github.gumtreediff.matchers"); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/Matchers.java b/core/src/main/java/com/github/gumtreediff/matchers/Matchers.java index 75e8d3de9..231196e61 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/Matchers.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/Matchers.java @@ -38,6 +38,7 @@ private Matchers() { install(CompositeMatchers.ClassicGumtree.class); install(CompositeMatchers.ChangeDistiller.class); install(CompositeMatchers.XyMatcher.class); + install(CompositeMatchers.GumtreeTopDown.class); } private void install(Class clazz) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java b/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java new file mode 100644 index 000000000..88a51496a --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java @@ -0,0 +1,135 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ +package com.github.gumtreediff.matchers; + +import com.github.gumtreediff.matchers.CompositeMatcher; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerBottomUpMatcher; +import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerLeavesMatcher; +import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerParallelLeavesMatcher; +import com.github.gumtreediff.matchers.heuristic.gt.GreedyBottomUpMatcher; +import com.github.gumtreediff.matchers.heuristic.gt.GreedySubtreeMatcher; +import com.github.gumtreediff.matchers.optimal.rted.RtedMatcher; +import com.github.gumtreediff.matchers.optimizations.CrossMoveMatcherThetaF; +import com.github.gumtreediff.matchers.optimizations.IdenticalSubtreeMatcherThetaA; +import com.github.gumtreediff.matchers.optimizations.InnerNodesMatcherThetaD; +import com.github.gumtreediff.matchers.optimizations.LcsOptMatcherThetaB; +import com.github.gumtreediff.matchers.optimizations.LeafMoveMatcherThetaE; +import com.github.gumtreediff.matchers.optimizations.UnmappedLeavesMatcherThetaC; +import com.github.gumtreediff.tree.ITree; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.concurrent.ExecutorService; + +public class OptimizedVersions { + + public static class CdabcdefSeq extends CompositeMatcher { + + /** + * Instantiates the sequential ChangeDistiller version with Theta A-F. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public CdabcdefSeq(ITree src, ITree dst, MappingStore store) { + super(src, dst, store, + new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store), + new ChangeDistillerLeavesMatcher(src, dst, store), + new ChangeDistillerBottomUpMatcher(src, dst, store), + new LcsOptMatcherThetaB(src, dst, store), + new UnmappedLeavesMatcherThetaC(src, dst, store), + new InnerNodesMatcherThetaD(src, dst, store), + new LeafMoveMatcherThetaE(src, dst, store), + new CrossMoveMatcherThetaF(src, dst, store) }); + } + } + + public static class CdabcdefPar extends CompositeMatcher { + + /** + * Instantiates the parallel ChangeDistiller version with Theta A-F. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public CdabcdefPar(ITree src, ITree dst, MappingStore store) { + super(src, dst, store, + new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store), + new ChangeDistillerParallelLeavesMatcher(src, dst, store), + new ChangeDistillerBottomUpMatcher(src, dst, store), + new LcsOptMatcherThetaB(src, dst, store), + new UnmappedLeavesMatcherThetaC(src, dst, store), + new InnerNodesMatcherThetaD(src, dst, store), + new LeafMoveMatcherThetaE(src, dst, store), + new CrossMoveMatcherThetaF(src, dst, store) + + }); + } + } + + public static class Gtbcdef extends CompositeMatcher { + + /** + * Instantiates GumTree with Theta B-F. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public Gtbcdef(ITree src, ITree dst, MappingStore store) { + super(src, dst, store, + new Matcher[] { new GreedySubtreeMatcher(src, dst, store), + new GreedyBottomUpMatcher(src, dst, store), + new LcsOptMatcherThetaB(src, dst, store), + new UnmappedLeavesMatcherThetaC(src, dst, store), + new InnerNodesMatcherThetaD(src, dst, store), + new LeafMoveMatcherThetaE(src, dst, store), + new CrossMoveMatcherThetaF(src, dst, store) }); + } + } + + public static class Rtedacdef extends CompositeMatcher { + + /** + * Instantiates RTED with Theta A-F. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public Rtedacdef(ITree src, ITree dst, MappingStore store) { + super(src, dst, store, + new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store), + new RtedMatcher(src, dst, store), + new LcsOptMatcherThetaB(src, dst, store), + new UnmappedLeavesMatcherThetaC(src, dst, store), + new InnerNodesMatcherThetaD(src, dst, store), + new LeafMoveMatcherThetaE(src, dst, store), + new CrossMoveMatcherThetaF(src, dst, store) + + }); + } + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/Register.java b/core/src/main/java/com/github/gumtreediff/matchers/Register.java index 9835bd5c2..c657ae5a7 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/Register.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/Register.java @@ -21,6 +21,7 @@ package com.github.gumtreediff.matchers; import com.github.gumtreediff.gen.Registry; +import org.atteo.classindex.IndexAnnotated; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -29,6 +30,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@IndexAnnotated public @interface Register { String id(); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/XyBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/XyBottomUpMatcher.java index 4a50c8dbe..462b54eb1 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/XyBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/XyBottomUpMatcher.java @@ -36,12 +36,13 @@ */ public class XyBottomUpMatcher extends Matcher { - private static final double SIM_THRESHOLD = Double.parseDouble(System.getProperty("gumtree.match.xy.sim", "0.5")); + private static final double SIM_THRESHOLD = Double.parseDouble(System.getProperty("gt.xym.sim", "0.5")); public XyBottomUpMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void match() { for (ITree src: this.src.postOrder()) { if (src.isRoot()) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerBottomUpMatcher.java index 28f072b9b..842523ff2 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerBottomUpMatcher.java @@ -29,9 +29,11 @@ public class ChangeDistillerBottomUpMatcher extends Matcher { - public static final double STRUCT_SIM_THRESHOLD_1 = 0.6D; + public static final double STRUCT_SIM_THRESHOLD_1 = Double.parseDouble(System.getProperty("gt.cd.ssim1", "0.6")); - public static final double STRUCT_SIM_THRESHOLD_2 = 0.4D; + public static final double STRUCT_SIM_THRESHOLD_2 = Double.parseDouble(System.getProperty("gt.cd.ssim2", "0.4")); + + public static final int MAX_NUMBER_OF_LEAVES = Integer.parseInt(System.getProperty("gt.cd.ml", "4")); public ChangeDistillerBottomUpMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); @@ -46,8 +48,8 @@ public void match() { if (isMappingAllowed(currentSrcTree, currentDstTree) && !(currentSrcTree.isLeaf() || currentDstTree.isLeaf())) { double similarity = chawatheSimilarity(currentSrcTree, currentDstTree); - if ((numberOfLeaves > 4 && similarity >= STRUCT_SIM_THRESHOLD_1) - || (numberOfLeaves <= 4 && similarity >= STRUCT_SIM_THRESHOLD_2)) { + if ((numberOfLeaves > MAX_NUMBER_OF_LEAVES && similarity >= STRUCT_SIM_THRESHOLD_1) + || (numberOfLeaves <= MAX_NUMBER_OF_LEAVES && similarity >= STRUCT_SIM_THRESHOLD_2)) { addMapping(currentSrcTree, currentDstTree); break; } diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerLeavesMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerLeavesMatcher.java index 1705d3d1c..9b80b364f 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerLeavesMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerLeavesMatcher.java @@ -31,7 +31,7 @@ public class ChangeDistillerLeavesMatcher extends Matcher { - public static final double LABEL_SIM_THRESHOLD = 0.5D; + public static final double LABEL_SIM_THRESHOLD = Double.parseDouble(System.getProperty("gt.cd.lsim", "0.5")); public ChangeDistillerLeavesMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); @@ -39,7 +39,7 @@ public ChangeDistillerLeavesMatcher(ITree src, ITree dst, MappingStore store) { @Override public void match() { - List leavesMappings = new LinkedList<>(); + List leavesMappings = new ArrayList<>(); List dstLeaves = retainLeaves(TreeUtils.postOrder(dst)); for (Iterator srcLeaves = TreeUtils.leafIterator( TreeUtils.postOrderIterator(src)); srcLeaves.hasNext();) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java new file mode 100644 index 000000000..c8d12ffd5 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java @@ -0,0 +1,191 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ +package com.github.gumtreediff.matchers.heuristic.cd; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; +import com.github.gumtreediff.tree.TreeUtils; + +import org.simmetrics.StringMetrics; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Parallel variant of the ChangeDistiller leaves matcher. + */ +public class ChangeDistillerParallelLeavesMatcher extends Matcher { + + private class ChangeDistillerCallableResult { + public final List leafMappings; + public final HashMap simMap; + + public ChangeDistillerCallableResult(List leafMappings, + HashMap simMap) { + this.leafMappings = leafMappings; + this.simMap = simMap; + } + } + + private class ChangeDistillerLeavesMatcherCallable + implements Callable { + + HashMap cacheResults = new HashMap<>(); + private int cores; + private List dstLeaves; + List leafMappings = new LinkedList<>(); + HashMap simMap = new HashMap<>(); + private List srcLeaves; + private int start; + + public ChangeDistillerLeavesMatcherCallable(List srcLeaves, List dstLeaves, + int cores, int start) { + this.srcLeaves = srcLeaves; + this.dstLeaves = dstLeaves; + this.cores = cores; + this.start = start; + } + + @Override + public ChangeDistillerCallableResult call() throws Exception { + for (int i = start; i < srcLeaves.size(); i += cores) { + ITree srcLeaf = srcLeaves.get(i); + for (ITree dstLeaf : dstLeaves) { + if (isMappingAllowed(srcLeaf, dstLeaf)) { + double sim = 0f; + // TODO: Use a unique string instead of @@ + if (cacheResults.containsKey(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel())) { + sim = cacheResults.get(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel()); + } else { + sim = StringMetrics.qGramsDistance().compare(srcLeaf.getLabel(), dstLeaf.getLabel()); + cacheResults.put(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel(), sim); + } + if (sim > LABEL_SIM_THRESHOLD) { + Mapping mapping = new Mapping(srcLeaf, dstLeaf); + leafMappings.add(new Mapping(srcLeaf, dstLeaf)); + simMap.put(mapping, sim); + } + } + } + } + return new ChangeDistillerCallableResult(leafMappings, simMap); + } + + } + + private class LeafMappingComparator implements Comparator { + HashMap simMap = null; + + public LeafMappingComparator(HashMap simMap) { + this.simMap = simMap; + } + + @Override + public int compare(Mapping m1, Mapping m2) { + return Double.compare(sim(m1), sim(m2)); + } + + public double sim(Mapping mapping) { + + return simMap.get(mapping); + } + + } + + private static final double LABEL_SIM_THRESHOLD = 0.5D; + + public ChangeDistillerParallelLeavesMatcher(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + } + + + /** + * Match. + */ + @Override + public void match() { + List dstLeaves = retainLeaves(TreeUtils.postOrder(dst)); + List srcLeaves = retainLeaves(TreeUtils.postOrder(src)); + + List leafMappings = new LinkedList<>(); + HashMap simMap = new HashMap<>(); + int cores = Runtime.getRuntime().availableProcessors(); + ExecutorService service = Executors.newFixedThreadPool(cores); + @SuppressWarnings("unchecked") + Future[] futures = new Future[cores]; + for (int i = 0; i < cores; i++) { + futures[i] = + service.submit(new ChangeDistillerLeavesMatcherCallable(srcLeaves, dstLeaves, cores, i)); + } + for (int i = 0; i < cores; i++) { + try { + ChangeDistillerCallableResult result = futures[i].get(); + leafMappings.addAll(result.leafMappings); + simMap.putAll(result.simMap); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + + } + service.shutdown(); + try { + service.awaitTermination(10, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Set srcIgnored = new HashSet<>(); + Set dstIgnored = new HashSet<>(); + Collections.sort(leafMappings, new LeafMappingComparator(simMap)); + while (leafMappings.size() > 0) { + Mapping best = leafMappings.remove(0); + if (!(srcIgnored.contains(best.getFirst()) || dstIgnored.contains(best.getSecond()))) { + addMapping(best.getFirst(), best.getSecond()); + srcIgnored.add(best.getFirst()); + dstIgnored.add(best.getSecond()); + } + } + } + + private List retainLeaves(List trees) { + Iterator tit = trees.iterator(); + while (tit.hasNext()) { + ITree tree = tit.next(); + if (!tree.isLeaf()) { + tit.remove(); + } + } + return trees; + } +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractBottomUpMatcher.java index 5f4088aac..6e3b6b9f8 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractBottomUpMatcher.java @@ -33,6 +33,7 @@ import java.util.Set; public abstract class AbstractBottomUpMatcher extends Matcher { + //TODO make final? public static int SIZE_THRESHOLD = Integer.parseInt(System.getProperty("gt.bum.szt", "1000")); public static final double SIM_THRESHOLD = @@ -138,6 +139,7 @@ public boolean isMappingAllowed(ITree src, ITree dst) { && !(isSrcMatched(src) || isDstMatched(dst)); } + @Override protected void addMapping(ITree src, ITree dst) { mappedSrc.putTree(src); mappedDst.putTree(dst); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractMappingComparator.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractMappingComparator.java index ebb4b5162..e3a5d4e94 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractMappingComparator.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractMappingComparator.java @@ -44,6 +44,7 @@ public AbstractMappingComparator(List ambiguousMappings, MappingStore m this.ambiguousMappings = ambiguousMappings; } + @Override public int compare(Mapping m1, Mapping m2) { if (similarities.get(m2).compareTo(similarities.get(m1)) != 0) { return Double.compare(similarities.get(m2), similarities.get(m1)); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java index 773ed1d1f..617ec2f28 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java @@ -31,7 +31,9 @@ public abstract class AbstractSubtreeMatcher extends Matcher { - public static int MIN_HEIGHT = Integer.parseInt(System.getProperty("gt.stm.mh", System.getProperty("gumtree.match.gt.minh", "1"))); + public static int MIN_HEIGHT = Integer.parseInt( + System.getProperty("gt.stm.mh", System.getProperty("gumtree.match.gt.minh", "1")) + ); public AbstractSubtreeMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); @@ -44,6 +46,7 @@ private void popLarger(PriorityTreeList srcTrees, PriorityTreeList dstTrees) { dstTrees.open(); } + @Override public void match() { MultiMappingStore multiMappings = new MultiMappingStore(); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CliqueSubtreeMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CliqueSubtreeMatcher.java index 15e77ab90..d46d9efc2 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CliqueSubtreeMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CliqueSubtreeMatcher.java @@ -118,6 +118,7 @@ public MappingComparator(List mappings) { simMap.put(mapping, sims(mapping.getFirst(), mapping.getSecond())); } + @Override public int compare(Mapping m1, Mapping m2) { double[] sims1 = simMap.get(m1); double[] sims2 = simMap.get(m2); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CompleteBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CompleteBottomUpMatcher.java index c704f80f2..a346a9886 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CompleteBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/CompleteBottomUpMatcher.java @@ -38,6 +38,7 @@ public CompleteBottomUpMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void match() { for (ITree t: src.postOrder()) { if (t.isRoot()) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/FirstMatchBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/FirstMatchBottomUpMatcher.java index 44a9292f6..563e280e1 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/FirstMatchBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/FirstMatchBottomUpMatcher.java @@ -35,6 +35,7 @@ public FirstMatchBottomUpMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void match() { match(removeMatched(src, true), removeMatched(dst, false)); } diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedyBottomUpMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedyBottomUpMatcher.java index 7a695d036..b0aa6e458 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedyBottomUpMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedyBottomUpMatcher.java @@ -33,12 +33,11 @@ */ public class GreedyBottomUpMatcher extends AbstractBottomUpMatcher { - public static double SIM_THRESHOLD = Double.parseDouble(System.getProperty("gumtree.match.bu.sim", "0.3")); - public GreedyBottomUpMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void match() { for (ITree t: src.postOrder()) { if (t.isRoot()) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java index b3fcd8e2c..84a3aabac 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java @@ -33,9 +33,10 @@ public GreedySubtreeMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void filterMappings(MultiMappingStore multiMappings) { // Select unique mappings first and extract ambiguous mappings. - List ambiguousList = new LinkedList<>(); + List ambiguousList = new ArrayList<>(); Set ignored = new HashSet<>(); for (ITree src: multiMappings.getSrcs()) { if (multiMappings.isSrcUnique(src)) diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/HungarianSubtreeMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/HungarianSubtreeMatcher.java index 9c1040be0..a0b6d7f8d 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/HungarianSubtreeMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/HungarianSubtreeMatcher.java @@ -33,6 +33,7 @@ public HungarianSubtreeMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); } + @Override public void filterMappings(MultiMappingStore multiMappings) { List ambiguousList = new ArrayList<>(); Set ignored = new HashSet<>(); @@ -74,7 +75,7 @@ private double cost(ITree src, ITree dst) { return 111D - sim(src, dst); } - private class MultiMappingComparator implements Comparator { + private static class MultiMappingComparator implements Comparator { @Override public int compare(MultiMappingStore m1, MultiMappingStore m2) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/ParentsMappingComparator.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/ParentsMappingComparator.java index 02259f854..458eb7b80 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/ParentsMappingComparator.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/ParentsMappingComparator.java @@ -34,6 +34,7 @@ public ParentsMappingComparator(List ambiguousMappings, MappingStore ma similarities.put(ambiguousMapping, similarity(ambiguousMapping.getFirst(), ambiguousMapping.getSecond())); } + @Override protected double similarity(ITree src, ITree dst) { return 100D * parentsJaccardSimilarity(src, dst) + 10D * posInParentSimilarity(src, dst) + numberingSimilarity(src , dst); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/SiblingsMappingComparator.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/SiblingsMappingComparator.java index 2cad7bb50..1a00c6c3d 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/SiblingsMappingComparator.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/SiblingsMappingComparator.java @@ -37,6 +37,7 @@ public SiblingsMappingComparator(List ambiguousMappings, MappingStore m similarities.put(ambiguousMapping, similarity(ambiguousMapping.getFirst(), ambiguousMapping.getSecond())); } + @Override protected double similarity(ITree src, ITree dst) { return 100D * siblingsJaccardSimilarity(src.getParent(), dst.getParent()) + 10D * posInParentSimilarity(src, dst) + numberingSimilarity(src , dst); diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/LabelDictionary.java b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/LabelDictionary.java index baeb23a59..a12f54eae 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/LabelDictionary.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/LabelDictionary.java @@ -15,76 +15,76 @@ package com.github.gumtreediff.matchers.optimal.rted; +import java.util.HashMap; import java.util.Hashtable; import java.util.Map; /** - * This provides a way of using small int values to represent String labels, + * This provides a way of using small int values to represent String labels, * as opposed to storing the labels directly. - * + * * @author Denilson Barbosa, Nikolaus Augsten from approxlib, available at http://www.inf.unibz.it/~augsten/src/ */ public class LabelDictionary { - public static final int KEY_DUMMY_LABEL = -1; - private int count; - private Map strInt; - private Map intStr; - private boolean newLabelsAllowed = true; + public static final int KEY_DUMMY_LABEL = -1; + private int count; + private Map strInt; + private Map intStr; + private boolean newLabelsAllowed = true; - /** - * Creates a new blank dictionary. - */ - public LabelDictionary() { - count = 0; - strInt = new Hashtable(); - intStr = new Hashtable(); - } - - - /** - * Adds a new label to the dictionary if it has not been added yet. - * Returns the ID of the new label in the dictionary. - * - * @param label add this label to the dictionary if it does not exist yet - * @return ID of label in the dictionary - */ - public int store(String label) { - if (strInt.containsKey(label)) { - return (strInt.get(label).intValue()); - } else if (!newLabelsAllowed) { - return KEY_DUMMY_LABEL; - } else { // store label - Integer intKey = new Integer(count++); - strInt.put(label, intKey); - intStr.put(intKey, label); - - return intKey.intValue(); - } - } - - /** - * Returns the label with a given ID in the dictionary. - * - * @param labelID - * @return the label with the specified labelID, or null if this dictionary contains no label for labelID - */ - public String read(int labelID) { - return intStr.get(new Integer(labelID)); - } - - /** - * @return true iff new labels can be stored into this label dictinoary - */ - public boolean isNewLabelsAllowed() { - return newLabelsAllowed; -} + /** + * Creates a new blank dictionary. + */ + public LabelDictionary() { + count = 0; + strInt = new HashMap<>(); + intStr = new HashMap<>(); + } + + + /** + * Adds a new label to the dictionary if it has not been added yet. + * Returns the ID of the new label in the dictionary. + * + * @param label add this label to the dictionary if it does not exist yet + * @return ID of label in the dictionary + */ + public int store(String label) { + if (strInt.containsKey(label)) { + return (strInt.get(label).intValue()); + } else if (!newLabelsAllowed) { + return KEY_DUMMY_LABEL; + } else { // store label + Integer intKey = count++; + strInt.put(label, intKey); + intStr.put(intKey, label); + return intKey.intValue(); + } + } + + /** + * Returns the label with a given ID in the dictionary. + * + * @param labelID + * @return the label with the specified labelID, or null if this dictionary contains no label for labelID + */ + public String read(int labelID) { + return intStr.get(Integer.valueOf(labelID)); + } + + /** + * @return true iff new labels can be stored into this label dictinoary + */ + public boolean isNewLabelsAllowed() { + return newLabelsAllowed; + } + + /** + * @param newLabelsAllowed the newLabelsAllowed to set + */ + public void setNewLabelsAllowed(boolean newLabelsAllowed) { + this.newLabelsAllowed = newLabelsAllowed; + } - /** - * @param newLabelsAllowed the newLabelsAllowed to set - */ - public void setNewLabelsAllowed(boolean newLabelsAllowed) { - this.newLabelsAllowed = newLabelsAllowed; - } - } diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedAlgorithm.java b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedAlgorithm.java index c6e6f269e..3bec8b9a5 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedAlgorithm.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedAlgorithm.java @@ -15,9 +15,7 @@ package com.github.gumtreediff.matchers.optimal.rted; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; +import java.util.*; import com.github.gumtreediff.tree.ITree; @@ -1063,7 +1061,7 @@ public void setCustomStrategy(int strategy, boolean ifSwitch) { * in ted1 and A[1]=ted2.node is the postorderID in ted2. The * postorderID of the empty node (insertion, deletion) is zero. */ - public LinkedList computeEditMapping() { + public ArrayDeque computeEditMapping() { // initialize tree and forest distance arrays double[][] treedist = new double[size1 + 1][size2 + 1]; @@ -1088,18 +1086,18 @@ public LinkedList computeEditMapping() { forestDist(it1, it2, size1, size2, treedist, forestdist); // empty edit mapping - LinkedList editMapping = new LinkedList(); + ArrayDeque editMapping = new ArrayDeque<>(); // empty stack of tree Pairs - LinkedList treePairs = new LinkedList(); + ArrayDeque treePairs = new ArrayDeque<>(); // push the pair of trees (ted1,ted2) to stack - treePairs.push(new int[] { size1, size2 }); + treePairs.addFirst(new int[] { size1, size2 }); while (!treePairs.isEmpty()) { // get next tree pair to be processed - int[] treePair = treePairs.pop(); + int[] treePair = treePairs.removeFirst(); int lastRow = treePair[0]; int lastCol = treePair[1]; @@ -1118,12 +1116,12 @@ public LinkedList computeEditMapping() { if ((row > firstRow) && (forestdist[row - 1][col] + costDel == forestdist[row][col])) { // node with postorderID row is deleted from ted1 - editMapping.push(new int[] { row, 0 }); + editMapping.addFirst(new int[] { row, 0 }); row--; } else if ((col > firstCol) && (forestdist[row][col - 1] + costIns == forestdist[row][col])) { // node with postorderID col is inserted into ted2 - editMapping.push(new int[] { 0, col }); + editMapping.addFirst(new int[] { 0, col }); col--; } else { // node with postorderID row in ted1 is renamed to node col @@ -1132,12 +1130,12 @@ public LinkedList computeEditMapping() { if ((it1.getInfo(POST2_LLD, row - 1) == it1.getInfo(POST2_LLD, lastRow - 1)) && (it2.getInfo(POST2_LLD, col - 1) == it2.getInfo(POST2_LLD, lastCol - 1))) { // if both subforests are trees, map nodes - editMapping.push(new int[] { row, col }); + editMapping.addFirst(new int[] { row, col }); row--; col--; } else { // pop subtree pair - treePairs.push(new int[] { row, col }); + treePairs.addFirst(new int[] { row, col }); // continue with forest to the left of the popped // subtree pair diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedMatcher.java index b81195832..d0c2d55f6 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimal/rted/RtedMatcher.java @@ -25,6 +25,7 @@ import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeUtils; +import java.util.ArrayDeque; import java.util.List; public class RtedMatcher extends Matcher { @@ -39,7 +40,7 @@ public void match() { a.init(src, dst); a.computeOptimalStrategy(); a.nonNormalizedTreeDist(); - List arrayMappings = a.computeEditMapping(); + ArrayDeque arrayMappings = a.computeEditMapping(); List srcs = TreeUtils.postOrder(src); List dsts = TreeUtils.postOrder(dst); for (int[] m: arrayMappings) { diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimal/zs/ZsMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/optimal/zs/ZsMatcher.java index 5c29b5559..cf3d8c8bd 100644 --- a/core/src/main/java/com/github/gumtreediff/matchers/optimal/zs/ZsMatcher.java +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimal/zs/ZsMatcher.java @@ -20,23 +20,17 @@ package com.github.gumtreediff.matchers.optimal.zs; -import com.github.gumtreediff.matchers.MappingStore; -import com.github.gumtreediff.matchers.Matcher; -import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.matchers.MappingStore; import com.github.gumtreediff.matchers.Matcher; import com.github.gumtreediff.tree.ITree; import org.simmetrics.StringMetrics; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; +import java.util.*; public class ZsMatcher extends Matcher { - private ZsTree src; - private ZsTree dst; + private ZsTree zsSrc; + private ZsTree zsDst; private double[][] treeDist; private double[][] forestDist; @@ -51,18 +45,18 @@ private static ITree getFirstLeaf(ITree t) { public ZsMatcher(ITree src, ITree dst, MappingStore store) { super(src, dst, store); - this.src = new ZsTree(src); - this.dst = new ZsTree(dst); + this.zsSrc = new ZsTree(src); + this.zsDst = new ZsTree(dst); } private double[][] computeTreeDist() { - treeDist = new double[src.nodeCount + 1][dst.nodeCount + 1]; - forestDist = new double[src.nodeCount + 1][dst.nodeCount + 1]; + treeDist = new double[zsSrc.nodeCount + 1][zsDst.nodeCount + 1]; + forestDist = new double[zsSrc.nodeCount + 1][zsDst.nodeCount + 1]; - for (int i = 1; i < src.kr.length; i++) { - for (int j = 1; j < dst.kr.length; j++) { - forestDist(src.kr[i], dst.kr[j]); + for (int i = 1; i < zsSrc.kr.length; i++) { + for (int j = 1; j < zsDst.kr.length; j++) { + forestDist(zsSrc.kr[i], zsDst.kr[j]); } } @@ -71,16 +65,16 @@ private double[][] computeTreeDist() { } private void forestDist(int i, int j) { - forestDist[src.lld(i) - 1][dst.lld(j) - 1] = 0; - for (int di = src.lld(i); di <= i; di++) { - double costDel = getDeletionCost(src.tree(di)); - forestDist[di][dst.lld(j) - 1] = forestDist[di - 1][dst.lld(j) - 1] + costDel; - for (int dj = dst.lld(j); dj <= j; dj++) { - double costIns = getInsertionCost(dst.tree(dj)); - forestDist[src.lld(i) - 1][dj] = forestDist[src.lld(i) - 1][dj - 1] + costIns; - - if ((src.lld(di) == src.lld(i) && (dst.lld(dj) == dst.lld(j)))) { - double costUpd = getUpdateCost(src.tree(di), dst.tree(dj)); + forestDist[zsSrc.lld(i) - 1][zsDst.lld(j) - 1] = 0; + for (int di = zsSrc.lld(i); di <= i; di++) { + double costDel = getDeletionCost(zsSrc.tree(di)); + forestDist[di][zsDst.lld(j) - 1] = forestDist[di - 1][zsDst.lld(j) - 1] + costDel; + for (int dj = zsDst.lld(j); dj <= j; dj++) { + double costIns = getInsertionCost(zsDst.tree(dj)); + forestDist[zsSrc.lld(i) - 1][dj] = forestDist[zsSrc.lld(i) - 1][dj - 1] + costIns; + + if ((zsSrc.lld(di) == zsSrc.lld(i) && (zsDst.lld(dj) == zsDst.lld(j)))) { + double costUpd = getUpdateCost(zsSrc.tree(di), zsDst.tree(dj)); forestDist[di][dj] = Math.min(Math.min(forestDist[di - 1][dj] + costDel, forestDist[di][dj - 1] + costIns), forestDist[di - 1][dj - 1] + costUpd); @@ -88,7 +82,7 @@ private void forestDist(int i, int j) { } else { forestDist[di][dj] = Math.min(Math.min(forestDist[di - 1][dj] + costDel, forestDist[di][dj - 1] + costIns), - forestDist[src.lld(di) - 1][dst.lld(dj) - 1] + forestDist[zsSrc.lld(di) - 1][zsDst.lld(dj) - 1] + treeDist[di][dj]); } } @@ -101,13 +95,13 @@ public void match() { boolean rootNodePair = true; - LinkedList treePairs = new LinkedList(); + ArrayDeque treePairs = new ArrayDeque<>(); // push the pair of trees (ted1,ted2) to stack - treePairs.push(new int[] { src.nodeCount, dst.nodeCount }); + treePairs.addFirst(new int[] { zsSrc.nodeCount, zsDst.nodeCount }); while (!treePairs.isEmpty()) { - int[] treePair = treePairs.pop(); + int[] treePair = treePairs.removeFirst(); int lastRow = treePair[0]; int lastCol = treePair[1]; @@ -119,8 +113,8 @@ public void match() { rootNodePair = false; // compute mapping for current forest distance matrix - int firstRow = src.lld(lastRow) - 1; - int firstCol = dst.lld(lastCol) - 1; + int firstRow = zsSrc.lld(lastRow) - 1; + int firstCol = zsDst.lld(lastCol) - 1; int row = lastRow; int col = lastCol; @@ -137,10 +131,11 @@ public void match() { } else { // node with postorderID row in ted1 is renamed to node col // in ted2 - if ((src.lld(row) - 1 == src.lld(lastRow) - 1) && (dst.lld(col) - 1 == dst.lld(lastCol) - 1)) { + if ((zsSrc.lld(row) - 1 == zsSrc.lld(lastRow) - 1) + && (zsDst.lld(col) - 1 == zsDst.lld(lastCol) - 1)) { // if both subforests are trees, map nodes - ITree tSrc = src.tree(row); - ITree tDst = dst.tree(col); + ITree tSrc = zsSrc.tree(row); + ITree tDst = zsDst.tree(col); if (tSrc.getType() == tDst.getType()) addMapping(tSrc, tDst); else @@ -149,12 +144,12 @@ public void match() { col--; } else { // pop subtree pair - treePairs.push(new int[] { row, col }); + treePairs.addFirst(new int[] { row, col }); // continue with forest to the left of the popped // subtree pair - row = src.lld(row) - 1; - col = dst.lld(col) - 1; + row = zsSrc.lld(row) - 1; + col = zsDst.lld(col) - 1; } } } @@ -179,7 +174,7 @@ private double getUpdateCost(ITree n1, ITree n2) { return Double.MAX_VALUE; } - private final class ZsTree { + private static final class ZsTree { private int start; // internal array position of leafmost leaf descendant of the root node diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java new file mode 100644 index 000000000..bbe80301c --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java @@ -0,0 +1,169 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ + +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; + + +/** + * This implements the cross move matcher Theta F. + * + */ +public class CrossMoveMatcherThetaF extends Matcher { + + private class BfsComparator implements Comparator { + + private HashMap positionSrc; + private HashMap positionDst; + + private HashMap getHashSet(ITree tree) { + HashMap map = new HashMap<>(); + ArrayList list = new ArrayList<>(); + LinkedList workList = new LinkedList<>(); + workList.add(tree); + while (!workList.isEmpty()) { + ITree node = workList.removeFirst(); + list.add(node); + workList.addAll(node.getChildren()); + } + for (int i = 0; i < list.size(); i++) { + map.put(list.get(i).getId(), i); + } + return map; + } + + public BfsComparator(ITree src, ITree dst) { + positionSrc = getHashSet(src); + positionDst = getHashSet(dst); + } + + @Override + public int compare(Mapping o1, Mapping o2) { + if (o1.first.getId() != o2.first.getId()) { + return Integer.compare(positionSrc.get(o1.first.getId()), + positionSrc.get(o2.first.getId())); + } + return Integer.compare(positionDst.get(o1.second.getId()), + positionDst.get(o2.second.getId())); + } + + } + + /** + * Instantiates a new matcher for Theta F. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public CrossMoveMatcherThetaF(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + } + + @Override + protected void addMapping(ITree src, ITree dst) { + assert (src != null); + assert (dst != null); + super.addMapping(src, dst); + } + + /** + * Match. + */ + @Override + public void match() { + thetaF(); + } + + private void thetaF() { + LinkedList workList = new LinkedList<>(mappings.asSet()); + Collections.sort(workList, new BfsComparator(src, dst)); + for (Mapping pair : workList) { + ITree parentOld = pair.getFirst().getParent(); + ITree parentNew = pair.getSecond().getParent(); + if (mappings.hasSrc(parentOld) && mappings.getDst(parentOld) != parentNew) { + if (mappings.hasDst(parentNew) && mappings.getSrc(parentNew) != parentOld) { + ITree parentOldOther = mappings.getSrc(parentNew); + ITree parentNewOther = mappings.getDst(parentOld); + if (parentOld.getLabel().equals(parentNewOther.getLabel()) + && parentNew.getLabel().equals(parentOldOther.getLabel())) { + boolean done = false; + for (ITree childOldOther : parentOldOther.getChildren()) { + if (mappings.hasSrc(childOldOther)) { + ITree childNewOther = mappings.getDst(childOldOther); + if (pair.getFirst().getLabel().equals(childNewOther.getLabel()) + && childOldOther.getLabel() + .equals(pair.getSecond().getLabel()) + || !(pair.getFirst().getLabel() + .equals(pair.getSecond().getLabel()) + || childOldOther.getLabel() + .equals(childNewOther.getLabel()))) { + if (childNewOther.getParent() == parentNewOther) { + if (childOldOther.getType() == pair.getFirst().getType()) { + mappings.unlink(pair.getFirst(), pair.getSecond()); + mappings.unlink(childOldOther, childNewOther); + addMapping(pair.getFirst(), childNewOther); + addMapping(childOldOther, pair.getSecond()); + // done = true; + } + } + } + } + } + if (!done) { + for (ITree childNewOther : parentNewOther.getChildren()) { + if (mappings.hasDst(childNewOther)) { + ITree childOldOther = mappings.getSrc(childNewOther); + if (childOldOther.getParent() == parentOldOther) { + if (childNewOther.getType() == pair.getSecond().getType()) { + if (pair.getFirst().getLabel() + .equals(childNewOther.getLabel()) + && childOldOther.getLabel() + .equals(pair.getSecond().getLabel()) + || !(pair.getFirst().getLabel() + .equals(pair.getSecond().getLabel()) + || childOldOther.getLabel().equals( + childNewOther.getLabel()))) { + mappings.unlink(pair.getFirst(), pair.getSecond()); + mappings.unlink(childOldOther, childNewOther); + addMapping(childOldOther, pair.getSecond()); + addMapping(pair.getFirst(), childNewOther); + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java new file mode 100644 index 000000000..e7ca47874 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java @@ -0,0 +1,149 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * This implements the identical subtree optimization Theta A. + * + */ + +public class IdenticalSubtreeMatcherThetaA extends Matcher { + + public IdenticalSubtreeMatcherThetaA(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + + } + + @SuppressWarnings({ "checkstyle:AvoidEscapedUnicodeCharacters" }) + private String getHash(ITree node, HashMap quickFind, + HashMap stringMap) { + String tmp = node.getType() + node.getLabel(); + for (ITree child : node.getChildren()) { + tmp += getHash(child, quickFind, stringMap); + } + tmp += "\\u2620"; + quickFind.put(node, tmp.hashCode()); + stringMap.put(node, tmp); + return tmp; + } + + private List getNodeStream(ITree root) { + LinkedList nodes = new LinkedList<>(); + LinkedList workList = new LinkedList<>(); + workList.add(root); + while (!workList.isEmpty()) { + ITree node = workList.removeFirst(); + nodes.add(node); + for (int i = node.getChildren().size() - 1; i >= 0; i--) { + workList.addFirst(node.getChildren().get(i)); + } + } + return nodes; + } + + + /** + * Match with Theta A. + */ + @Override + public void match() { + newUnchangedMatching(); + + } + + private void newUnchangedMatching() { + HashMap quickFind = new HashMap<>(); + HashMap stringMap = new HashMap<>(); + getHash(src, quickFind, stringMap); + getHash(dst, quickFind, stringMap); + HashMap> nodeMapOld = new HashMap<>(); + List streamOld = getNodeStream(src); + List streamNew = getNodeStream(dst); + for (ITree node : streamOld) { + String hashString = stringMap.get(node); + LinkedList nodeList = nodeMapOld.get(hashString); + if (nodeList == null) { + nodeList = new LinkedList<>(); + nodeMapOld.put(hashString, nodeList); + } + nodeList.add(node); + } + HashMap> nodeMapNew = new HashMap<>(); + + for (ITree node : streamNew) { + String hashString = stringMap.get(node); + LinkedList nodeList = nodeMapNew.get(hashString); + if (nodeList == null) { + nodeList = new LinkedList<>(); + nodeMapNew.put(hashString, nodeList); + } + nodeList.add(node); + } + + HashSet pairs = new HashSet<>(); + LinkedList workList = new LinkedList<>(); + workList.add(src); + + while (!workList.isEmpty()) { + ITree node = workList.removeFirst(); + LinkedList oldList = nodeMapOld.get(stringMap.get(node)); + assert (oldList != null); + LinkedList newList = nodeMapNew.get(stringMap.get(node)); + if (oldList.size() == 1 && newList != null && newList.size() == 1) { + if (node.getChildren().size() > 0) { + assert (stringMap.get(node).equals(stringMap.get(newList.getFirst()))); + pairs.add(new Mapping(node, newList.getFirst())); + oldList.remove(node); + newList.removeFirst(); + + } + } else { + workList.addAll(node.getChildren()); + } + } + for (Mapping mapping : pairs) { + List stream1 = getNodeStream(mapping.getFirst()); + List stream2 = getNodeStream(mapping.getSecond()); + stream1 = new ArrayList<>(stream1); + stream2 = new ArrayList<>(stream2); + assert (stream1.size() == stream2.size()); + for (int i = 0; i < stream1.size(); i++) { + ITree oldNode = stream1.get(i); + ITree newNode = stream2.get(i); + assert (oldNode.getType() == newNode.getType()); + assert (oldNode.getLabel().equals(newNode.getLabel())); + this.addMapping(oldNode, newNode); + } + } + + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java new file mode 100644 index 000000000..9de6919e8 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java @@ -0,0 +1,162 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.Map.Entry; + +/** + * This implements the unmapped leaves optimization (Theta C), the inner node repair optimization + * (Theta D) and the leaf move optimization (Theta E). + * + */ +public class InnerNodesMatcherThetaD extends Matcher { + + private class ChangeMapComparator + implements Comparator>> { + + @Override + public int compare(Entry> o1, + Entry> o2) { + + return Integer.compare(o1.getKey().getId(), o2.getKey().getId()); + } + + } + + /** + * Instantiates a new matcher for Theta A-E. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public InnerNodesMatcherThetaD(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + } + + @Override + protected void addMapping(ITree src, ITree dst) { + assert (src != null); + assert (dst != null); + super.addMapping(src, dst); + } + + private boolean allowedMatching(ITree key, ITree maxNodePartner) { + while (key != null) { + if (key == maxNodePartner) { + return false; + } + key = key.getParent(); + } + return true; + } + + + /** + * Match. + */ + @Override + public void match() { + thetaD(); + } + + private void thetaD() { + IdentityHashMap> parentCount = + new IdentityHashMap<>(); + for (Mapping pair : mappings.asSet()) { + ITree parent = pair.first.getParent(); + ITree parentPartner = pair.second.getParent(); + if (parent != null && parentPartner != null) { + IdentityHashMap countMap = parentCount.get(parent); + if (countMap == null) { + countMap = new IdentityHashMap<>(); + parentCount.put(parent, countMap); + } + Integer count = countMap.get(parentPartner); + if (count == null) { + count = new Integer(0); + } + countMap.put(parentPartner, count + 1); + } + } + + LinkedList>> list = + new LinkedList<>(parentCount.entrySet()); + Collections.sort(list, new ChangeMapComparator()); + + for (Entry> countEntry : list) { + int max = Integer.MIN_VALUE; + int maxCount = 0; + ITree maxNode = null; + for (Entry newNodeEntry : countEntry.getValue().entrySet()) { + if (newNodeEntry.getValue() > max) { + max = newNodeEntry.getValue(); + maxCount = 1; + maxNode = newNodeEntry.getKey(); + } else if (newNodeEntry.getValue() == max) { + maxCount++; + } + } + if (maxCount == 1) { + if (mappings.getDst(countEntry.getKey()) != null + && mappings.getSrc(maxNode) != null) { + ITree partner = mappings.getDst(countEntry.getKey()); + ITree maxNodePartner = mappings.getSrc(maxNode); + if (partner != maxNode) { + if (max > countEntry.getKey().getChildren().size() / 2 + || countEntry.getKey().getChildren().size() == 1) { + ITree parentPartner = mappings.getDst(countEntry.getKey().getParent()); + + if (parentPartner != null && parentPartner == partner.getParent()) { + continue; + } + if (allowedMatching(countEntry.getKey(), maxNodePartner)) { + if (countEntry.getKey().getType() == maxNode.getType()) { + if (maxNodePartner != null) { + mappings.unlink(maxNodePartner, maxNode); + } + if (partner != null) { + mappings.unlink(countEntry.getKey(), partner); + } + addMapping(countEntry.getKey(), maxNode); + } + if (maxNodePartner != null) { + if (maxNodePartner.getType() == partner.getType()) { + addMapping(maxNodePartner, partner); + } + } + } + } + } + } + } + } + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java new file mode 100644 index 000000000..1a3959e13 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java @@ -0,0 +1,207 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ + +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * This implements the lcs optimization Theta B. + * + */ + +public class LcsOptMatcherThetaB extends Matcher { + + /** + * Instantiates a new lcs matcher. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public LcsOptMatcherThetaB(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + + } + + private void advancedLcsMatching() { + List allNodesSrc = src.getTrees(); + List allNodesDst = dst.getTrees(); + Set unmatchedNodes1 = new HashSet<>(); + Set unmatchedNodes2 = new HashSet<>(); + for (ITree node : allNodesSrc) { + if (!mappings.hasSrc(node)) { + unmatchedNodes1.add(node); + } + } + for (ITree node : allNodesDst) { + if (!mappings.hasDst(node)) { + unmatchedNodes2.add(node); + } + } + if (unmatchedNodes1.size() > 0 && unmatchedNodes2.size() > 0) { + ArrayList workList = new ArrayList<>(); + getUnmatchedNodeListInPostOrder(src, workList); + HashSet checkedParent = new HashSet<>(); + for (ITree node : workList) { + if (!unmatchedNodes1.contains(node)) { + continue; + } + ITree parent = node.getParent(); + if (parent == null) { + continue; + } + + ITree partner = null; + if (parent == src) { + partner = dst; + } else { + partner = mappings.getDst(parent); + } + + while (parent != null && partner == null) { + parent = parent.getParent(); + partner = mappings.getDst(parent); + } + if (parent != null && partner != null) { + if (checkedParent.contains(parent)) { + // System.out.println("continue checked"); + continue; + } + checkedParent.add(parent); + ArrayList list1 = new ArrayList<>(); + ArrayList list2 = new ArrayList<>(); + getNodeListInPostOrder(parent, list1); + getNodeListInPostOrder(partner, list2); + List lcsMatch = lcs(list1, list2, unmatchedNodes1, unmatchedNodes2); + for (Mapping match : lcsMatch) { + if (!mappings.hasSrc(match.first) && !mappings.hasDst(match.second)) { + addMapping(match.first, match.second); + unmatchedNodes1.remove(match.first); + unmatchedNodes2.remove(match.second); + } + } + } + } + } + } + + private void backtrack(ArrayList list1, ArrayList list2, + LinkedList resultList, int[][] matrix, int ipar, int jpar, + Set unmatchedNodes1, Set unmatchedNodes2) { + assert (ipar >= 0); + assert (jpar >= 0); + while (ipar > 0 && jpar > 0) { + if (testCondition(list1.get(ipar - 1), list2.get(jpar - 1), unmatchedNodes1, + unmatchedNodes2)) { + if (!mappings.hasSrc(list1.get(ipar - 1))) { + resultList.add(new Mapping(list1.get(ipar - 1), list2.get(jpar - 1))); + } + } + if (matrix[ipar][jpar - 1] > matrix[ipar - 1][jpar]) { + jpar--; + } else { + ipar--; + } + } + } + + private void getNodeListInPostOrder(ITree tree, ArrayList nodes) { + if (tree != null) { + for (ITree child : tree.getChildren()) { + getNodeListInPostOrder(child, nodes); + } + nodes.add(tree); + } + } + + private void getUnmatchedNodeListInPostOrder(ITree tree, ArrayList nodes) { + if (tree != null) { + for (ITree child : tree.getChildren()) { + getNodeListInPostOrder(child, nodes); + } + if (!mappings.hasSrc(tree) && !mappings.hasDst(tree)) { + nodes.add(tree); + } + } + } + + private List lcs(ArrayList list1, ArrayList list2, + Set unmatchedNodes1, Set unmatchedNodes2) { + int[][] matrix = new int[list1.size() + 1][list2.size() + 1]; + for (int i = 1; i < list1.size() + 1; i++) { + for (int j = 1; j < list2.size() + 1; j++) { + if (testCondition(list1.get(i - 1), list2.get(j - 1), unmatchedNodes1, + unmatchedNodes2)) { + matrix[i][j] = matrix[i - 1][j - 1] + 1; + } else { + matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]); + } + } + } + LinkedList resultList = new LinkedList<>(); + backtrack(list1, list2, resultList, matrix, list1.size(), list2.size(), unmatchedNodes1, + unmatchedNodes2); + return resultList; + } + + + /** + * Match with Theta B. + */ + @Override + public void match() { + advancedLcsMatching(); + + } + + /** + * Compare two nodes to test lcs condition. + * + * @param node1 the node1 + * @param node2 the node2 + * @param unmatchedNodes1 the unmatched nodes1 + * @param unmatchedNodes2 the unmatched nodes2 + * @return true, if successful + */ + public boolean testCondition(ITree node1, ITree node2, Set unmatchedNodes1, + Set unmatchedNodes2) { + if (node1.getType() != node2.getType()) { + return false; + } + if (mappings.hasSrc(node1) && mappings.getDst(node1) == node2) { + return true; + } + if (unmatchedNodes1.contains(node1) && unmatchedNodes2.contains(node2)) { + return true; + } + return false; + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java new file mode 100644 index 000000000..ad1f73896 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java @@ -0,0 +1,282 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ + +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.Mapping; +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +/** + * This implements the unmapped leaves optimization (Theta C), the inner node repair optimization + * (Theta D) and the leaf move optimization (Theta E). + * + */ +public class LeafMoveMatcherThetaE extends Matcher { + + private class MappingComparator implements Comparator { + + @Override + public int compare(Mapping o1, Mapping o2) { + if (o1.first.getId() != o2.first.getId()) { + return Integer.compare(o1.first.getId(), o2.first.getId()); + } + return Integer.compare(o1.second.getId(), o2.second.getId()); + } + + } + + /** + * Instantiates a new matcher for Theta A-E. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public LeafMoveMatcherThetaE(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + } + + @Override + protected void addMapping(ITree src, ITree dst) { + assert (src != null); + assert (dst != null); + super.addMapping(src, dst); + } + + /** + * Match. + */ + @Override + public void match() { + thetaE(); + } + + private void thetaE() { + LinkedList workList = new LinkedList<>(); + LinkedList workListTmp = null; + LinkedList changeMap = new LinkedList<>(); + + for (Mapping pair : mappings.asSet()) { + if (pair.first.isLeaf() && pair.second.isLeaf()) { + if (!pair.first.getLabel().equals(pair.second.getLabel())) { + workList.add(pair); + } + } + + } + while (!workList.isEmpty()) { + Collections.sort(workList, new MappingComparator()); + workListTmp = new LinkedList<>(); + for (Mapping pair : workList) { + ITree firstParent = pair.first.getParent(); + if (!mappings.hasDst(firstParent)) { + continue; + } + ITree secondParent = mappings.getDst(pair.first.getParent()); + reevaluateLeaves(firstParent, secondParent, pair, changeMap); + } + Collections.sort(changeMap, new MappingComparator()); + for (Mapping entry : changeMap) { + if (!mappings.hasSrc(entry.first) && !mappings.hasDst(entry.second)) { + addMapping(entry.first, entry.second); + } + if (!entry.first.getLabel().equals(entry.second.getLabel()) && entry.first.isLeaf() + && entry.second.isLeaf()) { + workListTmp.add(new Mapping(entry.first, entry.second)); + } + } + changeMap.clear(); + workList = workListTmp; + } + + workList = new LinkedList<>(); + workListTmp = null; + + for (Mapping pair : mappings.asSet()) { + if (pair.first.isLeaf() && pair.second.isLeaf()) { + if (!pair.first.getLabel().equals(pair.second.getLabel())) { + workList.add(pair); + } + } + + } + while (!workList.isEmpty()) { + Collections.sort(workList, new MappingComparator()); + workListTmp = new LinkedList<>(); + for (Mapping pair : workList) { + ITree firstParent = pair.first.getParent(); + ITree secondParent = pair.second.getParent(); + reevaluateLeaves(firstParent, secondParent, pair, changeMap); + } + Collections.sort(changeMap, new MappingComparator()); + for (Mapping entry : changeMap) { + if (!mappings.hasSrc(entry.first) && !mappings.hasDst(entry.second)) { + addMapping(entry.first, entry.second); + } + if (!entry.first.getLabel().equals(entry.second.getLabel()) && entry.first.isLeaf() + && entry.second.isLeaf()) { + workListTmp.add(new Mapping(entry.first, entry.second)); + } + } + changeMap.clear(); + workList = workListTmp; + } + } + + private void reevaluateLeaves(ITree firstParent, ITree secondParent, Mapping pair, + List changeMap) { + + int count = 0; + ITree foundDstNode = null; + ITree foundPosDstNode = null; + int pos = firstParent.getChildren().indexOf(pair.first); + + for (int i = 0; i < secondParent.getChildren().size(); i++) { + ITree child = secondParent.getChildren().get(i); + if (child.getType() == pair.first.getType() + && child.getLabel().equals(pair.first.getLabel())) { + count++; + foundDstNode = child; + if (i == pos) { + foundPosDstNode = child; + } + } + } + Mapping addedMappingKey = null; + + if ((count == 1 && foundDstNode != null) || foundPosDstNode != null) { + if (count != 1 && foundPosDstNode != null) { + foundDstNode = foundPosDstNode; + } + if (mappings.hasDst(foundDstNode)) { + + ITree foundSrc = mappings.getSrc(foundDstNode); + if (!foundSrc.getLabel().equals(foundDstNode.getLabel())) { + mappings.unlink(pair.first, pair.second); + mappings.unlink(foundSrc, foundDstNode); + changeMap.add(new Mapping(pair.first, foundDstNode)); + addedMappingKey = new Mapping(foundSrc, foundDstNode); + if (foundDstNode != pair.second && foundSrc != pair.first) { + changeMap.add(new Mapping(foundSrc, pair.second)); + } + } + } else { + + mappings.unlink(pair.first, pair.second); + if (pair.first.getLabel().equals(foundDstNode.getLabel())) { + LinkedList toRemove = new LinkedList<>(); + for (Mapping mapPair : changeMap) { + if (mapPair.first == pair.first) { + if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) { + toRemove.add(mapPair); + } + } else if (mapPair.second == foundDstNode) { + if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) { + toRemove.add(mapPair); + } + } + } + changeMap.removeAll(toRemove); + } + changeMap.add(new Mapping(pair.first, foundDstNode)); + for (ITree child : firstParent.getChildren()) { + if (child.isLeaf() && !mappings.hasDst(child) + && child.getType() == pair.second.getType() + && child.getLabel().equals(pair.second.getLabel())) { + addMapping(child, pair.second); + break; + } + } + } + } + ITree foundSrcNode = null; + ITree foundPosSrcNode = null; + pos = secondParent.getChildren().indexOf(pair.second); + for (int i = 0; i < firstParent.getChildren().size(); i++) { + ITree child = firstParent.getChildren().get(i); + if (child.getType() == pair.second.getType() + && child.getLabel().equals(pair.second.getLabel())) { + count++; + foundSrcNode = child; + if (i == pos) { + foundPosSrcNode = child; + } + } + } + if ((count == 1 && foundSrcNode != null) || foundPosSrcNode != null) { + if (count != 1 && foundPosSrcNode != null) { + foundSrcNode = foundPosSrcNode; + } else if (foundSrcNode == null) { + foundSrcNode = foundPosSrcNode; + } + if (addedMappingKey != null) { + changeMap.remove(addedMappingKey); + } + if (mappings.hasSrc(foundSrcNode)) { + ITree foundDst = mappings.getSrc(foundSrcNode); + if (foundDst != null && foundSrcNode != null + && !foundDst.getLabel().equals(foundSrcNode.getLabel())) { + mappings.unlink(pair.first, pair.second); + mappings.unlink(foundSrcNode, foundDst); + changeMap.add(new Mapping(foundSrcNode, pair.second)); + if (addedMappingKey == null && foundDst != null) { + if (foundSrcNode != pair.first && foundDst != pair.second) { + changeMap.add(new Mapping(pair.first, foundDst)); + } + } + } + } else { + mappings.unlink(pair.first, pair.second); + if (foundSrcNode.getLabel().equals(pair.second.getLabel())) { + LinkedList toRemove = new LinkedList<>(); + for (Mapping mapPair : changeMap) { + if (mapPair.first == foundSrcNode) { + if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) { + toRemove.add(mapPair); + } + } else if (mapPair.second == pair.second) { + if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) { + toRemove.add(mapPair); + } + } + } + changeMap.removeAll(toRemove); + } + changeMap.add(new Mapping(foundSrcNode, pair.second)); + for (ITree child : secondParent.getChildren()) { + if (child.isLeaf() && !mappings.hasSrc(child) + && child.getType() == pair.first.getType() + && child.getLabel().equals(pair.first.getLabel())) { + addMapping(pair.first, child); + break; + } + } + } + } + } + +} diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java new file mode 100644 index 000000000..469d874d1 --- /dev/null +++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java @@ -0,0 +1,255 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ + +package com.github.gumtreediff.matchers.optimizations; + +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.tree.ITree; + +import java.util.LinkedList; +import java.util.List; + +/** + * This implements the unmapped leaves optimization (Theta C). + * + */ +public class UnmappedLeavesMatcherThetaC extends Matcher { + + /** + * Instantiates a new matcher for Theta C. + * + * @param src the src + * @param dst the dst + * @param store the store + */ + public UnmappedLeavesMatcherThetaC(ITree src, ITree dst, MappingStore store) { + super(src, dst, store); + } + + @Override + protected void addMapping(ITree src, ITree dst) { + assert (src != null); + assert (dst != null); + super.addMapping(src, dst); + } + + /** + * Match. + */ + @Override + public void match() { + thetaC(); + } + + private void thetaC() { + List allNodesSrc = src.getTrees(); + List allNodesDst = dst.getTrees(); + List unmatchedNodes1 = new LinkedList<>(); + List unmatchedNodes2 = new LinkedList<>(); + + for (ITree node : allNodesSrc) { + if (!mappings.hasSrc(node)) { + unmatchedNodes1.add(node); + } + } + for (ITree node : allNodesDst) { + if (!mappings.hasDst(node)) { + unmatchedNodes2.add(node); + } + } + for (ITree node : unmatchedNodes1) { + if (node.getChildren().size() == 0) { + + ITree parent = node.getParent(); + if (mappings.getDst(parent) != null) { + ITree partner = mappings.getDst(parent); + int pos = parent.getChildren().indexOf(node); + if (pos < partner.getChildren().size()) { + ITree child = partner.getChildren().get(pos); + if (child.getType() == node.getType()) { + if (child.getLabel().equals(node.getLabel())) { + ITree childPartner = mappings.getSrc(child); + if (childPartner != null) { + if (!childPartner.getLabel().equals(node.getLabel())) { + mappings.unlink(childPartner, child); + addMapping(node, child); + } + } else { + addMapping(node, child); + + } + } else { + ITree childPartner = mappings.getSrc(child); + if (childPartner != null) { + if (mappings.getDst(childPartner.getParent()) == null) { + if (!childPartner.getLabel().equals(child.getLabel())) { + mappings.unlink(childPartner, child); + addMapping(node, child); + } + } + } else { + addMapping(node, child); + } + } + } else { + if (child.getChildren().size() == 1) { + child = child.getChildren().get(0); + if (child.getType() == node.getType() + && child.getLabel().equals(node.getLabel())) { + ITree childPartner = mappings.getSrc(child); + if (childPartner != null) { + if (!childPartner.getLabel().equals(node.getLabel())) { + mappings.unlink(childPartner, child); + addMapping(node, child); + } else if (mappings + .getDst(childPartner.getParent()) == null) { + mappings.unlink(childPartner, child); + addMapping(node, child); + } + } + } + } else { + for (int i = 0; i < partner.getChildren().size(); i++) { + ITree possibleMatch = partner.getChildren().get(i); + if (possibleMatch.getType() == node.getType() + && possibleMatch.getLabel().equals(node.getLabel())) { + ITree possibleMatchSrc = mappings.getSrc(possibleMatch); + if (possibleMatchSrc == null) { + addMapping(node, possibleMatch); + break; + } else { + if (!possibleMatchSrc.getLabel() + .equals(possibleMatch.getLabel())) { + mappings.unlink(possibleMatchSrc, possibleMatch); + addMapping(node, possibleMatch); + break; + } + } + } + } + } + } + } + } + } + } + for (ITree node : unmatchedNodes2) { + if (mappings.hasSrc(node)) { + continue; + } + if (node.getChildren().size() == 0) { + ITree parent = node.getParent(); + if (mappings.getSrc(parent) != null) { + ITree partner = mappings.getSrc(parent); + int pos = parent.getChildren().indexOf(node); + if (pos < partner.getChildren().size()) { + ITree child = partner.getChildren().get(pos); + if (child.getType() == node.getType()) { + if (child.getLabel().equals(node.getLabel())) { + ITree tree = mappings.getDst(child); + if (tree != null) { + if (!tree.getLabel().equals(node.getLabel())) { + mappings.unlink(child, tree); + addMapping(child, node); + } + } else { + addMapping(child, node); + } + } else { + ITree childPartner = mappings.getDst(child); + if (childPartner != null) { + if (mappings.getSrc(childPartner.getParent()) == null) { + if (!childPartner.getLabel().equals(child.getLabel())) { + mappings.unlink(child, childPartner); + addMapping(child, node); + } + } + } else { + addMapping(child, node); + + } + } + } else { + if (child.getChildren().size() == 1) { + child = child.getChildren().get(0); + if (child.getType() == node.getType() + && child.getLabel().equals(node.getLabel())) { + ITree childPartner = mappings.getDst(child); + if (childPartner != null) { + if (!childPartner.getLabel().equals(node.getLabel())) { + mappings.unlink(child, childPartner); + addMapping(child, node); + } else if (mappings + .getSrc(childPartner.getParent()) == null) { + mappings.unlink(childPartner, child); + addMapping(node, child); + } + } + } + } else { + for (int i = 0; i < partner.getChildren().size(); i++) { + ITree possibleMatch = partner.getChildren().get(i); + if (possibleMatch.getType() == node.getType() + && possibleMatch.getLabel().equals(node.getLabel())) { + ITree possibleMatchDst = mappings.getDst(possibleMatch); + if (possibleMatchDst == null) { + addMapping(possibleMatch, node); + break; + } else { + if (!possibleMatchDst.getLabel() + .equals(possibleMatch.getLabel())) { + mappings.unlink(possibleMatch, possibleMatchDst); + addMapping(possibleMatch, node); + break; + } + } + } + } + } + } + } + } else if (unmatchedNodes2.contains(parent)) { + ITree oldParent = parent; + parent = parent.getParent(); + if (mappings.getSrc(parent) != null) { + ITree partner = mappings.getSrc(parent); + int pos = parent.getChildren().indexOf(oldParent); + if (pos < partner.getChildren().size()) { + ITree child = partner.getChildren().get(pos); + if (child.getType() == node.getType() + && child.getLabel().equals(node.getLabel())) { + ITree tree = mappings.getDst(child); + if (tree != null) { + if (!tree.getLabel().equals(node.getLabel())) { + mappings.unlink(child, tree); + addMapping(child, node); + } + } else { + addMapping(child, node); + } + } + } + } + } + } + } + } +} diff --git a/core/src/main/java/com/github/gumtreediff/tree/IterableEnumeration.java b/core/src/main/java/com/github/gumtreediff/tree/IterableEnumeration.java index f41b221f1..fabbbae8f 100644 --- a/core/src/main/java/com/github/gumtreediff/tree/IterableEnumeration.java +++ b/core/src/main/java/com/github/gumtreediff/tree/IterableEnumeration.java @@ -27,6 +27,7 @@ public abstract class IterableEnumeration implements Iterable { public static Iterable make(Enumeration en) { return new Iterable() { + @Override public Iterator iterator() { return new Iterator() { @Override diff --git a/core/src/main/java/com/github/gumtreediff/tree/Tree.java b/core/src/main/java/com/github/gumtreediff/tree/Tree.java index 9c060f6f5..e82aed2ac 100644 --- a/core/src/main/java/com/github/gumtreediff/tree/Tree.java +++ b/core/src/main/java/com/github/gumtreediff/tree/Tree.java @@ -57,7 +57,7 @@ public Tree(int type, String label) { } // Only used for cloning ... - private Tree(Tree other) { + protected Tree(Tree other) { this.type = other.type; this.label = other.getLabel(); this.id = other.getId(); diff --git a/core/src/main/java/com/github/gumtreediff/tree/hash/HashUtils.java b/core/src/main/java/com/github/gumtreediff/tree/hash/HashUtils.java index 9b8fe0a14..28bf9c621 100644 --- a/core/src/main/java/com/github/gumtreediff/tree/hash/HashUtils.java +++ b/core/src/main/java/com/github/gumtreediff/tree/hash/HashUtils.java @@ -52,9 +52,9 @@ public static String outSeed(ITree t) { public static int md5(String s) { try { MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] digest = md.digest(s.getBytes()); + byte[] digest = md.digest(s.getBytes("UTF-8")); return byteArrayToInt(digest); - } catch (NoSuchAlgorithmException e) { + } catch (Exception e) { e.printStackTrace(); } return ITree.NO_VALUE; diff --git a/core/src/main/java/com/github/gumtreediff/tree/hash/RollingHashGenerator.java b/core/src/main/java/com/github/gumtreediff/tree/hash/RollingHashGenerator.java index 34dee74b0..d8b722a1e 100644 --- a/core/src/main/java/com/github/gumtreediff/tree/hash/RollingHashGenerator.java +++ b/core/src/main/java/com/github/gumtreediff/tree/hash/RollingHashGenerator.java @@ -28,6 +28,7 @@ public abstract class RollingHashGenerator implements HashGenerator { + @Override public void hash(ITree t) { for (ITree n: t.postOrder()) if (n.isLeaf()) diff --git a/core/src/main/java/com/github/gumtreediff/tree/hash/StaticHashGenerator.java b/core/src/main/java/com/github/gumtreediff/tree/hash/StaticHashGenerator.java index 3b738ffe8..c66a7176f 100644 --- a/core/src/main/java/com/github/gumtreediff/tree/hash/StaticHashGenerator.java +++ b/core/src/main/java/com/github/gumtreediff/tree/hash/StaticHashGenerator.java @@ -27,6 +27,7 @@ public abstract class StaticHashGenerator implements HashGenerator { + @Override public void hash(ITree t) { for (ITree n: t.postOrder()) n.setHash(nodeHash(n)); diff --git a/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java b/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java new file mode 100644 index 000000000..731902e7b --- /dev/null +++ b/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java @@ -0,0 +1,81 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2015-2016 Georg Dotzler + * Copyright 2015-2016 Marius Kamp + */ + +package com.github.gumtreediff.test; + +import com.github.gumtreediff.matchers.MappingStore; +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.matchers.OptimizedVersions; +import com.github.gumtreediff.tree.ITree; +import com.github.gumtreediff.utils.Pair; +import com.github.gumtreediff.tree.TreeContext; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestOptimizedMatchers { + + @Test + public void testRtedabcdefMatcher() { + Pair trees = TreeLoader.getZsSlidePair(); + ITree src = trees.getFirst().getRoot(); + ITree dst = trees.getSecond().getRoot(); + Matcher matcher = new OptimizedVersions.Rtedacdef(src, dst, new MappingStore()); + matcher.match(); + assertEquals(5, matcher.getMappingsAsSet().size()); + assertTrue(matcher.getMappings().has(src, dst)); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1))); + } + + @Test + public void testCdabcdefParMatcher() { + Pair trees = TreeLoader.getZsSlidePair(); + ITree src = trees.getFirst().getRoot(); + ITree dst = trees.getSecond().getRoot(); + Matcher matcher = new OptimizedVersions.CdabcdefPar(src, dst, new MappingStore()); + matcher.match(); + assertEquals(5, matcher.getMappingsAsSet().size()); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1))); + assertTrue(matcher.getMappings().has(src.getChild(0), dst)); + } + + @Test + public void testGtbcdefMatcher() { + Pair trees = TreeLoader.getZsSlidePair(); + ITree src = trees.getFirst().getRoot(); + ITree dst = trees.getSecond().getRoot(); + Matcher matcher = new OptimizedVersions.Rtedacdef(src, dst, new MappingStore()); + matcher.match(); + assertEquals(5, matcher.getMappingsAsSet().size()); + assertTrue(matcher.getMappings().has(src, dst)); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0))); + assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1))); + } + +} diff --git a/core/src/test/java/com/github/gumtreediff/test/TestTree.java b/core/src/test/java/com/github/gumtreediff/test/TestTree.java index fa7c9e600..232e68e91 100644 --- a/core/src/test/java/com/github/gumtreediff/test/TestTree.java +++ b/core/src/test/java/com/github/gumtreediff/test/TestTree.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertTrue; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -46,7 +46,7 @@ public void testIdComparator() { @Test public void testGetParents() { ITree tree = TreeLoader.getDummySrc(); - List trees = new LinkedList<>(tree.getTrees()); + List trees = new ArrayList<>(tree.getTrees()); ITree n = trees.get(2); assertTrue(n.getLabel().equals("c")); List parents = n.getParents(); diff --git a/dist/build.gradle b/dist/build.gradle index b3b57233c..8d18aad86 100644 --- a/dist/build.gradle +++ b/dist/build.gradle @@ -1,42 +1,37 @@ -import java.text.SimpleDateFormat -import java.util.Date - apply plugin: 'application' mainClassName = 'com.github.gumtreediff.client.Run' applicationName = 'gumtree' +archivesBaseName = 'gumtree' jar.enabled = false -distZip.archiveName = (isRelease) ? 'gumtree.zip' : 'gumtree-' + buildTime() + '-' + version + '.zip' uploadArchives.enabled = false distTar.enabled = false -description = 'GumTree whole distribution.' +description = 'GumTree distribution.' dependencies { - compile project(':client') - compile project(':client.diff') - compile project(':gen.antlr3') - compile project(':gen.antlr3-antlr') - compile project(':gen.antlr3-json') - compile project(':gen.antlr3-php') - compile project(':gen.antlr3-r') - compile project(':gen.antlr3-xml') - compile project(':gen.c') - compile project(':gen.css') - compile project(':gen.jdt') - compile project(':gen.js') - compile project(':gen.ruby') - compile project(':gen.srcml') + implementation project(':client') + implementation project(':client.diff') + implementation project(':gen.antlr3') + implementation project(':gen.antlr3-antlr') + implementation project(':gen.antlr3-json') + implementation project(':gen.antlr3-php') + implementation project(':gen.antlr3-r') + implementation project(':gen.antlr3-xml') + implementation project(':gen.c') + implementation project(':gen.css') + implementation project(':gen.javaparser') + implementation project(':gen.jdt') + implementation project(':gen.js') + implementation project(':gen.python') + implementation project(':gen.ruby') + implementation project(':gen.srcml') } +build.finalizedBy(installDist) + run { if ( project.hasProperty("params") ) { args Eval.me(params) } -} - -def buildTime() { - def df = new SimpleDateFormat("yyyyMMdd") - df.setTimeZone(TimeZone.getTimeZone("UTC")) - return df.format(new Date()) -} +} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..02e3abd92 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:xenial + +# installs all required packages +RUN apt-get update \ + && apt-get install -y software-properties-common python-software-properties \ + && echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections \ + && add-apt-repository ppa:webupd8team/java -y \ + && apt-get update \ + && apt-get install -y oracle-java8-installer oracle-java8-set-default libarchive13 libcurl3 ocaml ocaml-native-compilers camlp4 git build-essential zip python-pip\ + && wget http://131.123.42.38/lmcrs/beta/srcML-Ubuntu14.04-64.deb \ + && dpkg -i srcML-Ubuntu14.04-64.deb \ + && pip install jsontree asttokens + +# install cgum +WORKDIR /opt +RUN git clone https://github.com/GumTreeDiff/cgum.git --depth 1 +WORKDIR /opt/cgum +RUN make \ + && ln -s /opt/cgum/cgum /usr/bin/cgum + +# install pythonparser +WORKDIR /opt +RUN git clone https://github.com/GumTreeDiff/pythonparser.git --depth 1 +WORKDIR /opt/pythonparser +RUN ln -s /opt/pythonparser/pythonparser /usr/bin/pythonparser + +# install gumtree +WORKDIR /opt +RUN git clone -b develop https://github.com/GumTreeDiff/gumtree.git --depth 1 +WORKDIR /opt/gumtree +RUN ./gradlew build -x check \ + && unzip dist/build/distributions/gumtree.zip \ + && ln -s /opt/gumtree/gumtree/bin/gumtree /usr/bin/gumtree + +# define volume diff to make available files to diff +RUN mkdir /diff +WORKDIR /diff +VOLUME /diff + +# expose port 4567 for webdiff +EXPOSE 4567 + +ENTRYPOINT ["gumtree"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..710a0b4dd --- /dev/null +++ b/docker/README.md @@ -0,0 +1,17 @@ +# GumTree dockerfile + +## Installation + +First you need to build the container. Go to the `docker` folder and compile the image: `docker build . -t gumtree`. + +## Usage + +Now you can use GumTree container. You need to: +* bind a `/diff` volume where you files of interest are located, +* bind the `4567` port to be able to use webdiff + +A sample command line is `docker run -v /my/folder:/diff -p 4567:4567 gumtree webdiff left.rb right.rb`. You can consult the diff at the URL `http://localhost:4567`. Of course, all other GumTree's commands are available. + +## Debug + +If you wan to debug GumTree container use the following command line: `docker run -v /my/folder:/diff -p 4567:4567 --entrypoint "/bin/bash" -it gumtree`. diff --git a/gen.antlr3-json/src/test/java/com/github/gumtreediff/gen/antlr3/json/TestJsonParsing.java b/gen.antlr3-json/src/test/java/com/github/gumtreediff/gen/antlr3/json/TestJsonParsing.java index fcf12148f..db7b6f88a 100644 --- a/gen.antlr3-json/src/test/java/com/github/gumtreediff/gen/antlr3/json/TestJsonParsing.java +++ b/gen.antlr3-json/src/test/java/com/github/gumtreediff/gen/antlr3/json/TestJsonParsing.java @@ -33,7 +33,7 @@ public class TestJsonParsing { @Test public void testJsonParsing() throws Exception { TreeContext tc = new AntlrJsonTreeGenerator().generateFromReader( - new InputStreamReader(getClass().getResourceAsStream("/sample.json"))); + new InputStreamReader(getClass().getResourceAsStream("/sample.json"), "UTF-8")); ITree tree = tc.getRoot(); assertEquals(4, tree.getType()); assertEquals(37, tree.getSize()); diff --git a/gen.antlr3/src/main/java/com/github/gumtreediff/gen/antlr3/AbstractAntlr3TreeGenerator.java b/gen.antlr3/src/main/java/com/github/gumtreediff/gen/antlr3/AbstractAntlr3TreeGenerator.java index 46213b6b0..a49ada090 100644 --- a/gen.antlr3/src/main/java/com/github/gumtreediff/gen/antlr3/AbstractAntlr3TreeGenerator.java +++ b/gen.antlr3/src/main/java/com/github/gumtreediff/gen/antlr3/AbstractAntlr3TreeGenerator.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; +import com.github.gumtreediff.gen.SyntaxException; import org.antlr.runtime.*; import org.antlr.runtime.tree.CommonTree; @@ -79,10 +80,8 @@ public TreeContext generate(Reader r) throws IOException { buildTree(context, ct); return context; } catch (RecognitionException e) { - System.out.println("at " + e.line + ":" + e.charPositionInLine); - e.printStackTrace(); + throw new SyntaxException(this, r); } - return null; } protected abstract String[] getTokenNames(); diff --git a/gen.antlr4-matlab/src/main/java/com/github/gumtreediff/gen/antlr4/matlab/AntlrMatlabGenerator.java b/gen.antlr4-matlab/src/main/java/com/github/gumtreediff/gen/antlr4/matlab/AntlrMatlabGenerator.java index e257032be..2bcf102d4 100644 --- a/gen.antlr4-matlab/src/main/java/com/github/gumtreediff/gen/antlr4/matlab/AntlrMatlabGenerator.java +++ b/gen.antlr4-matlab/src/main/java/com/github/gumtreediff/gen/antlr4/matlab/AntlrMatlabGenerator.java @@ -17,7 +17,32 @@ * Copyright 2011-2015 Jean-Rémy Falleri * Copyright 2011-2015 Floréal Morandat */ + package com.github.gumtreediff.gen.antlr4.matlab; -public class AntlrMatlabGenerator { +import com.github.gumtreediff.gen.Register; +import com.github.gumtreediff.gen.antlr4.AbstractAntlr4TreeGenerator; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.tree.ParseTree; + +import java.io.IOException; +import java.io.Reader; + +@Register(id = "matlab-antlr", accept = "\\.m$") +public class AntlrMatlabGenerator extends AbstractAntlr4TreeGenerator { + @Override + protected ParseTree getStartSymbol(Reader r) throws RecognitionException, IOException { + MatlabLexer lexer = new MatlabLexer(new ANTLRInputStream(r)); + MatlabParser parser = new MatlabParser(new CommonTokenStream(lexer)); + MatlabParser.ScriptMFileContext script = parser.scriptMFile(); + return script; + } + + @Override + protected String[] getTokenNames() { + return new String[0]; + } } diff --git a/gen.c/src/main/java/com/github/gumtreediff/gen/c/CTreeGenerator.java b/gen.c/src/main/java/com/github/gumtreediff/gen/c/CTreeGenerator.java index 55c66e1ea..590f9a82d 100644 --- a/gen.c/src/main/java/com/github/gumtreediff/gen/c/CTreeGenerator.java +++ b/gen.c/src/main/java/com/github/gumtreediff/gen/c/CTreeGenerator.java @@ -20,8 +20,8 @@ package com.github.gumtreediff.gen.c; +import com.github.gumtreediff.gen.ExternalProcessTreeGenerator; import com.github.gumtreediff.gen.Register; -import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.io.TreeIoUtils; import com.github.gumtreediff.tree.TreeContext; import com.github.gumtreediff.tree.TreeContext.MetadataSerializers; @@ -32,9 +32,9 @@ import java.util.regex.Pattern; @Register(id = "c-cocci", accept = "\\.[ch]$") -public class CTreeGenerator extends TreeGenerator { +public class CTreeGenerator extends ExternalProcessTreeGenerator { - private static final String COCCI_CMD = System.getProperty("gumtree.cgum.path", "cgum"); + private static final String COCCI_CMD = System.getProperty("gt.cgum.path", "cgum"); private static final MetadataSerializers defaultSerializers = new MetadataSerializers(); private static final MetadataUnserializers defaultUnserializers = new MetadataUnserializers(); @@ -54,40 +54,11 @@ public class CTreeGenerator extends TreeGenerator { @Override public TreeContext generate(Reader r) throws IOException { - //FIXME this is not efficient but I am not sure how to speed up things here. - File f = File.createTempFile("gumtree", ".c"); - FileWriter w = new FileWriter(f); - BufferedReader br = new BufferedReader(r); - String line = br.readLine(); - while (line != null) { - w.append(line); - w.append(System.lineSeparator()); - line = br.readLine(); - } - w.close(); - br.close(); - ProcessBuilder b = new ProcessBuilder(COCCI_CMD, f.getAbsolutePath()); - b.directory(f.getParentFile()); - try { - Process p = b.start(); - StringBuffer buf = new StringBuffer(); - br = new BufferedReader(new InputStreamReader(p.getInputStream())); - // TODO Why do we need to read and bufferize eveything, when we could/should only use generateFromStream - line = null; - while ((line = br.readLine()) != null) - buf.append(line + "\n"); - p.waitFor(); - if (p.exitValue() != 0) - throw new RuntimeException( - String.format("cgum Error [%d] %s\n", p.exitValue(), buf.toString()) - ); - r.close(); - String xml = buf.toString(); - return TreeIoUtils.fromXml(CTreeGenerator.defaultUnserializers).generateFromString(xml); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - f.delete(); - } + String output = readStandardOutput(r); + return TreeIoUtils.fromXml(CTreeGenerator.defaultUnserializers).generateFromString(output); + } + + protected String[] getCommandLine(String file) { + return new String[]{COCCI_CMD, file}; } } diff --git a/gen.css/build.gradle b/gen.css/build.gradle index 8d15540af..f62a7619d 100644 --- a/gen.css/build.gradle +++ b/gen.css/build.gradle @@ -1,5 +1,6 @@ description = 'GumTree tree generator for CSS code based on ph-css.' dependencies { - compile 'com.helger:ph-css:6.0.0-b1' + implementation 'net.sf.trove4j:trove4j:3.0.3' + implementation 'com.helger:ph-css:6.1.2' } diff --git a/gen.css/src/main/java/com/github/gumtreediff/gen/css/CssTreeGenerator.java b/gen.css/src/main/java/com/github/gumtreediff/gen/css/CssTreeGenerator.java index 386ee268e..93d5ceac7 100644 --- a/gen.css/src/main/java/com/github/gumtreediff/gen/css/CssTreeGenerator.java +++ b/gen.css/src/main/java/com/github/gumtreediff/gen/css/CssTreeGenerator.java @@ -21,6 +21,7 @@ import com.github.gumtreediff.gen.Register; import com.github.gumtreediff.gen.Registry; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.io.LineReader; import com.github.gumtreediff.tree.TreeContext; @@ -42,6 +43,7 @@ @Register(id = "css-phcss", accept = {"\\.css$"}, priority = Registry.Priority.MAXIMUM) public class CssTreeGenerator extends TreeGenerator { + @Override public TreeContext generate(Reader r) throws IOException { LineReader lr = new LineReader(r); CSSCharStream s = new CSSCharStream(new LineReader(lr)); @@ -57,7 +59,7 @@ public TreeContext generate(Reader r) throws IOException { CSSVisitor.visitCSS(sheet, v); return v.getTreeContext(); } catch (ParseException e) { - throw new IOException(e); + throw new SyntaxException(this, r); } } } diff --git a/gen.css/src/main/java/com/github/gumtreediff/gen/css/GtCssVisitor.java b/gen.css/src/main/java/com/github/gumtreediff/gen/css/GtCssVisitor.java index 6cb0f8909..cb5331ba3 100644 --- a/gen.css/src/main/java/com/github/gumtreediff/gen/css/GtCssVisitor.java +++ b/gen.css/src/main/java/com/github/gumtreediff/gen/css/GtCssVisitor.java @@ -33,13 +33,14 @@ import javax.annotation.Nonnull; import java.io.IOException; +import java.util.ArrayDeque; import java.util.Stack; public class GtCssVisitor implements ICSSVisitor { private TreeContext ctx; - private Stack trees; + private ArrayDeque trees; private LineReader lr; @@ -52,7 +53,7 @@ public GtCssVisitor(CascadingStyleSheet sheet, LineReader lr) throws IOException this.settings = new CSSWriterSettings(); this.sheet = sheet; this.ctx = new TreeContext(); - this.trees = new Stack<>(); + this.trees = new ArrayDeque<>(); ITree root = this.ctx.createTree(hashCode(sheet), "", "CascadingStyleSheet"); setLocation(root, sheet); this.ctx.setRoot(root); @@ -84,7 +85,7 @@ public void begin() { public void onImport(@Nonnull CSSImportRule i) { //TODO add media nodes ITree t = ctx.createTree(hashCode(i), i.getAsCSSString(settings, 0), "CSSImportRule"); - t.setParentAndUpdateChildren(trees.peek()); + t.setParentAndUpdateChildren(trees.peekFirst()); setLocation(t, i); } @@ -96,7 +97,7 @@ public void onNamespace(@Nonnull CSSNamespaceRule n) { @Override public void onDeclaration(@Nonnull CSSDeclaration d) { ITree t = ctx.createTree(hashCode(d), d.getProperty(), "CSSDeclaration"); - t.setParentAndUpdateChildren(trees.peek()); + t.setParentAndUpdateChildren(trees.peekFirst()); setLocation(t, d); CSSExpression e = d.getExpression(); ITree c = ctx.createTree(hashCode(e), e.getAsCSSString(settings, 0), "CSSExpression"); @@ -116,20 +117,20 @@ public void onDeclaration(@Nonnull CSSDeclaration d) { public void onBeginStyleRule(@Nonnull CSSStyleRule s) { ITree t = ctx.createTree(hashCode(s), "", "CSSStyleRule"); setLocation(t, s); - t.setParentAndUpdateChildren(trees.peek()); - trees.push(t); + t.setParentAndUpdateChildren(trees.peekFirst()); + trees.addFirst(t); } @Override public void onStyleRuleSelector(@Nonnull CSSSelector s) { ITree t = ctx.createTree(hashCode(s), s.getAsCSSString(settings, 0), "CSSSelector"); - t.setParentAndUpdateChildren(trees.peek()); + t.setParentAndUpdateChildren(trees.peekFirst()); setLocation(t, s); } @Override public void onEndStyleRule(@Nonnull CSSStyleRule aStyleRule) { - trees.pop(); + trees.removeFirst(); } @Override diff --git a/gen.css/src/test/java/com/github/gumtreediff/gen/css/TestCssTreeGenerator.java b/gen.css/src/test/java/com/github/gumtreediff/gen/css/TestCssTreeGenerator.java index b8fcb4930..3cdac50d1 100644 --- a/gen.css/src/test/java/com/github/gumtreediff/gen/css/TestCssTreeGenerator.java +++ b/gen.css/src/test/java/com/github/gumtreediff/gen/css/TestCssTreeGenerator.java @@ -19,6 +19,7 @@ package com.github.gumtreediff.gen.css; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.io.TreeIoUtils; import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeContext; @@ -43,4 +44,10 @@ public void testSimple() throws Exception { ITree tree = ctx.getRoot(); assertEquals(10, tree.getSize()); } + + @Test(expected = SyntaxException.class) + public void badSyntax() throws IOException { + String input = ".foo \"toto {\nfont-size: 11pt;\n}"; + TreeContext ct = new CssTreeGenerator().generateFromString(input); + } } diff --git a/gen.javaparser/build.gradle b/gen.javaparser/build.gradle new file mode 100644 index 000000000..0fa6ed57b --- /dev/null +++ b/gen.javaparser/build.gradle @@ -0,0 +1,5 @@ +description = 'GumTree tree generator for Java code (JavaParser based).' + +dependencies { + implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.13.1' +} diff --git a/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserGenerator.java b/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserGenerator.java new file mode 100644 index 000000000..1dd351cd4 --- /dev/null +++ b/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserGenerator.java @@ -0,0 +1,53 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2018 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen.javaparser; + +import com.github.gumtreediff.gen.Register; +import com.github.gumtreediff.gen.Registry; +import com.github.gumtreediff.gen.SyntaxException; +import com.github.gumtreediff.gen.TreeGenerator; +import com.github.gumtreediff.io.LineReader; +import com.github.gumtreediff.tree.TreeContext; +import com.github.javaparser.JavaParser; +import com.github.javaparser.ParseException; +import com.github.javaparser.ParseProblemException; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; + +import java.io.IOException; +import java.io.Reader; + +@Register(id = "java-javaparser", accept = "\\.java$", priority = Registry.Priority.MEDIUM) +public class JavaParserGenerator extends TreeGenerator { + + @Override + public TreeContext generate(Reader r) throws IOException { + LineReader lr = new LineReader(r); + try { + CompilationUnit cu = StaticJavaParser.parse(lr); + JavaParserVisitor v = new JavaParserVisitor(lr); + v.visitPreOrder(cu); + return v.getTreeContext(); + } + catch (ParseProblemException e) { + throw new SyntaxException(this, r); + } + } +} diff --git a/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserVisitor.java b/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserVisitor.java new file mode 100644 index 000000000..0696d5579 --- /dev/null +++ b/gen.javaparser/src/main/java/com/github/gumtreediff/gen/javaparser/JavaParserVisitor.java @@ -0,0 +1,102 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2018 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen.javaparser; + +import com.github.gumtreediff.io.LineReader; +import com.github.gumtreediff.tree.ITree; +import com.github.gumtreediff.tree.TreeContext; +import com.github.javaparser.Position; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.visitor.TreeVisitor; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.NoSuchElementException; + +public class JavaParserVisitor extends TreeVisitor { + + protected TreeContext context; + + private Deque trees; + + private LineReader reader; + + public JavaParserVisitor(LineReader reader) { + this.context = new TreeContext(); + this.trees = new ArrayDeque<>(); + this.reader = reader; + } + + public TreeContext getTreeContext() { + return context; + } + + @Override + public void visitPreOrder(Node node) { + process(node); + new ArrayList<>(node.getChildNodes()).forEach(this::visitPreOrder); + if (trees.size() > 0) + trees.pop(); + } + + @Override + public void process(Node node) { + String label = ""; + if (node instanceof SimpleName) + label = ((SimpleName) node).getIdentifier(); + else if (node instanceof StringLiteralExpr) + label = ((StringLiteralExpr) node).asString(); + else if (node instanceof BooleanLiteralExpr) + label = Boolean.toString(((BooleanLiteralExpr) node).getValue()); + else if (node instanceof LiteralStringValueExpr) + label = ((LiteralStringValueExpr) node).getValue(); + pushNode(node, label); + } + + protected void pushNode(Node n, String label) { + int type = n.getClass().getName().hashCode(); + String typeName = n.getClass().getSimpleName(); + try { + Position begin = n.getRange().get().begin; + Position end = n.getRange().get().end; + push(type, typeName, label, reader.positionFor(begin.line, begin.column), + reader.positionFor(end.line,end.column)); + } + catch (NoSuchElementException ignore) { } + + } + + private void push(int type, String typeName, String label, int startPosition, int length) { + ITree t = context.createTree(type, label, typeName); + t.setPos(startPosition); + t.setLength(length); + + if (trees.isEmpty()) + context.setRoot(t); + else { + ITree parent = trees.peek(); + t.setParentAndUpdateChildren(parent); + } + + trees.push(t); + } +} diff --git a/gen.javaparser/src/test/java/com/github/gumtreediff/gen/javaparser/TestJavaParserGenerator.java b/gen.javaparser/src/test/java/com/github/gumtreediff/gen/javaparser/TestJavaParserGenerator.java new file mode 100644 index 000000000..2313cceab --- /dev/null +++ b/gen.javaparser/src/test/java/com/github/gumtreediff/gen/javaparser/TestJavaParserGenerator.java @@ -0,0 +1,69 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2018 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen.javaparser; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; + +import com.github.gumtreediff.gen.SyntaxException; +import com.github.gumtreediff.tree.TreeContext; +import org.junit.Test; + +import com.github.gumtreediff.tree.ITree; + +public class TestJavaParserGenerator { + @Test + public void testSimpleSyntax() throws IOException { + String input = "public class Foo { public int foo; }"; + ITree tree = new JavaParserGenerator().generateFromString(input).getRoot(); + assertEquals(-1795686804, tree.getType()); + assertEquals(9, tree.getSize()); + } + + @Test + public void testJava5Syntax() throws IOException { + String input = "public class Foo { public List foo; public void foo() " + + "{ for (A f : foo) { System.out.println(f); } } }"; + TreeContext context = new JavaParserGenerator().generateFromString(input); + ITree tree = context.getRoot(); + + System.out.println(tree.toTreeString()); + assertEquals(-1795686804, tree.getType()); + assertEquals(37, tree.getSize()); + } + + @Test + public void testJava8Syntax() throws IOException { + String input = "public class Foo { public void foo(){ new ArrayList().stream().forEach(a -> {}); } }"; + ITree tree = new JavaParserGenerator().generateFromString(input).getRoot(); + assertEquals(-1795686804, tree.getType()); + assertEquals(23, tree.getSize()); + } + + @Test(expected = SyntaxException.class) + public void badSyntax() throws IOException { + String input = "public clas Foo {}"; + TreeContext ct = new JavaParserGenerator().generateFromString(input); + } + +} diff --git a/gen.jdt/build.gradle b/gen.jdt/build.gradle index 4d5c185cc..2c8823102 100644 --- a/gen.jdt/build.gradle +++ b/gen.jdt/build.gradle @@ -1,7 +1,5 @@ description = 'GumTree tree generator for Java code (Eclipse JDT based).' dependencies { - compile group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.core', version: '3.12.3' - // compile 'org.eclipse.core:runtime:3.10.0-v20140318-2214' - // compile 'org.eclipse.birt.runtime:org.eclipse.core.resources:3.10.0.v20150423-0755' + implementation 'org.eclipse.jdt:org.eclipse.jdt.core:3.16.0' } diff --git a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/AbstractJdtTreeGenerator.java b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/AbstractJdtTreeGenerator.java index 377408e33..9083e6e9f 100644 --- a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/AbstractJdtTreeGenerator.java +++ b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/AbstractJdtTreeGenerator.java @@ -20,17 +20,20 @@ package com.github.gumtreediff.gen.jdt; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.tree.TreeContext; import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.tree.TreeContext; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import java.util.IllegalFormatException; import java.util.Map; public abstract class AbstractJdtTreeGenerator extends TreeGenerator { @@ -52,17 +55,20 @@ private static char[] readerToCharArray(Reader r) throws IOException { @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public TreeContext generate(Reader r) throws IOException { - ASTParser parser = ASTParser.newParser(AST.JLS8); + ASTParser parser = ASTParser.newParser(AST.JLS9); parser.setKind(ASTParser.K_COMPILATION_UNIT); Map pOptions = JavaCore.getOptions(); - pOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_8); - pOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_8); - pOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8); + pOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_11); + pOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_11); + pOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_11); pOptions.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); parser.setCompilerOptions(pOptions); parser.setSource(readerToCharArray(r)); AbstractJdtVisitor v = createVisitor(); - parser.createAST(null).accept(v); + ASTNode node = parser.createAST(null); + if ((node.getFlags() & ASTNode.MALFORMED) != 0) // bitwise flag to check if the node has a syntax error + throw new SyntaxException(this, r); + node.accept(v); return v.getTreeContext(); } diff --git a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/JdtVisitor.java b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/JdtVisitor.java index 32c9ca48b..7a1def334 100644 --- a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/JdtVisitor.java +++ b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/JdtVisitor.java @@ -48,7 +48,7 @@ protected String getLabel(ASTNode n) { if (n instanceof Assignment) return ((Assignment) n).getOperator().toString(); if (n instanceof TextElement) return n.toString(); if (n instanceof TagElement) return ((TagElement) n).getTagName(); - if (n instanceof TypeDeclaration) return ((TypeDeclaration) n).isInterface()?"interface":"class"; + if (n instanceof TypeDeclaration) return ((TypeDeclaration) n).isInterface() ? "interface" : "class"; return ""; } diff --git a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtTreeGenerator.java b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtTreeGenerator.java index ee9634412..d6021b0dc 100644 --- a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtTreeGenerator.java +++ b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtTreeGenerator.java @@ -21,10 +21,11 @@ package com.github.gumtreediff.gen.jdt.cd; import com.github.gumtreediff.gen.Register; +import com.github.gumtreediff.gen.Registry; import com.github.gumtreediff.gen.jdt.AbstractJdtVisitor; import com.github.gumtreediff.gen.jdt.AbstractJdtTreeGenerator; -@Register(id = "java-jdt-cd") +@Register(id = "java-cdjdt", accept = "\\.java$", priority = Registry.Priority.MINIMUM) public class CdJdtTreeGenerator extends AbstractJdtTreeGenerator { @Override protected AbstractJdtVisitor createVisitor() { diff --git a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtVisitor.java b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtVisitor.java index 8563b9d80..b62712000 100644 --- a/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtVisitor.java +++ b/gen.jdt/src/main/java/com/github/gumtreediff/gen/jdt/cd/CdJdtVisitor.java @@ -217,7 +217,7 @@ public void endVisit(SimpleType node) { @Override public boolean visit(SingleVariableDeclaration node) { - boolean isNotParam = getCurrentParent().getLabel() != EntityType.PARAMETERS.toString();// @inria + boolean isNotParam = !getCurrentParent().getLabel().equals(EntityType.PARAMETERS.toString());// @inria pushNode(node, node.getName().getIdentifier()); node.getType().accept(this); return false; diff --git a/gen.jdt/src/test/java/com/github/gumtreediff/gen/jdt/TestJdtGenerator.java b/gen.jdt/src/test/java/com/github/gumtreediff/gen/jdt/TestJdtGenerator.java index b372dc854..413d9b0b2 100644 --- a/gen.jdt/src/test/java/com/github/gumtreediff/gen/jdt/TestJdtGenerator.java +++ b/gen.jdt/src/test/java/com/github/gumtreediff/gen/jdt/TestJdtGenerator.java @@ -22,6 +22,8 @@ import java.io.IOException; +import com.github.gumtreediff.gen.SyntaxException; +import com.github.gumtreediff.tree.TreeContext; import org.junit.Test; import com.github.gumtreediff.tree.ITree; @@ -54,4 +56,10 @@ public void testJava8Syntax() throws IOException { assertEquals(24, tree.getSize()); } + @Test(expected = SyntaxException.class) + public void badSyntax() throws IOException { + String input = "public clas Foo {}"; + TreeContext ct = new JdtTreeGenerator().generateFromString(input); + } + } diff --git a/gen.js/build.gradle b/gen.js/build.gradle index 111df8e9e..b66ba0835 100644 --- a/gen.js/build.gradle +++ b/gen.js/build.gradle @@ -1,5 +1,5 @@ description = 'GumTree tree generator for JavaScript code (Rhino based).' dependencies { - compile 'org.mozilla:rhino:1.7.7.2' + implementation 'org.mozilla:rhino:1.7.10' } diff --git a/gen.js/src/main/java/com/github/gumtreediff/gen/js/RhinoTreeGenerator.java b/gen.js/src/main/java/com/github/gumtreediff/gen/js/RhinoTreeGenerator.java index bc21c6c5f..73d8e9710 100644 --- a/gen.js/src/main/java/com/github/gumtreediff/gen/js/RhinoTreeGenerator.java +++ b/gen.js/src/main/java/com/github/gumtreediff/gen/js/RhinoTreeGenerator.java @@ -21,9 +21,11 @@ import com.github.gumtreediff.gen.Register; import com.github.gumtreediff.gen.Registry; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.tree.TreeContext; import org.mozilla.javascript.CompilerEnvirons; +import org.mozilla.javascript.EvaluatorException; import org.mozilla.javascript.Parser; import org.mozilla.javascript.ast.AstRoot; @@ -33,15 +35,21 @@ @Register(id = "js-rhino", accept = "\\.js$", priority = Registry.Priority.MAXIMUM) public class RhinoTreeGenerator extends TreeGenerator { + @Override public TreeContext generate(Reader r) throws IOException { CompilerEnvirons env = new CompilerEnvirons(); env.setRecordingLocalJsDocComments(true); env.setAllowSharpComments(true); env.setRecordingComments(true); Parser p = new Parser(env); - AstRoot root = p.parse(r, null, 1); - RhinoTreeVisitor visitor = new RhinoTreeVisitor(root); - root.visitAll(visitor); - return visitor.getTree(root); + try { + AstRoot root = p.parse(r, null, 1); + RhinoTreeVisitor visitor = new RhinoTreeVisitor(root); + root.visitAll(visitor); + return visitor.getTree(root); + } + catch (EvaluatorException e) { + throw new SyntaxException(this, r); + } } } diff --git a/gen.js/src/test/java/com/github/gumtreediff/gen/js/TestJsGenerator.java b/gen.js/src/test/java/com/github/gumtreediff/gen/js/TestJsGenerator.java index 7436c30a6..fb4e90ed2 100644 --- a/gen.js/src/test/java/com/github/gumtreediff/gen/js/TestJsGenerator.java +++ b/gen.js/src/test/java/com/github/gumtreediff/gen/js/TestJsGenerator.java @@ -26,6 +26,8 @@ import java.io.InputStreamReader; import java.io.Reader; +import com.github.gumtreediff.gen.SyntaxException; +import com.github.gumtreediff.tree.TreeContext; import org.junit.Test; import com.github.gumtreediff.tree.ITree; @@ -48,9 +50,15 @@ public void testComment() throws IOException { @Test public void testComplexFile() throws IOException { - Reader r = new InputStreamReader(getClass().getResourceAsStream("/sample.js")); + Reader r = new InputStreamReader(getClass().getResourceAsStream("/sample.js"), "UTF-8"); ITree tree = new RhinoTreeGenerator().generateFromReader(r).getRoot(); assertEquals(402, tree.getSize()); } + @Test(expected = SyntaxException.class) + public void badSyntax() throws IOException { + String input = "function foo((bar) {}"; + TreeContext ct = new RhinoTreeGenerator().generateFromString(input); + } + } diff --git a/gen.python/build.gradle b/gen.python/build.gradle new file mode 100644 index 000000000..ff3d4332b --- /dev/null +++ b/gen.python/build.gradle @@ -0,0 +1,3 @@ +description = 'GumTree tree generator for Python. Requires pythonparser.' + +ext.isNative = true diff --git a/gen.python/src/main/java/com/github/gumtreediff/gen/python/PythonTreeGenerator.java b/gen.python/src/main/java/com/github/gumtreediff/gen/python/PythonTreeGenerator.java new file mode 100644 index 000000000..a07651d5b --- /dev/null +++ b/gen.python/src/main/java/com/github/gumtreediff/gen/python/PythonTreeGenerator.java @@ -0,0 +1,116 @@ +/* + * This file is part of GumTree. + * + * GumTree is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GumTree is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GumTree. If not, see . + * + * Copyright 2016 Jean-Rémy Falleri + */ + +package com.github.gumtreediff.gen.python; + +import com.github.gumtreediff.gen.ExternalProcessTreeGenerator; +import com.github.gumtreediff.gen.Register; +import com.github.gumtreediff.gen.Registry; +import com.github.gumtreediff.io.LineReader; +import com.github.gumtreediff.tree.ITree; +import com.github.gumtreediff.tree.TreeContext; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.events.*; +import java.io.*; +import java.util.*; + +@Register(id = "python-pythonparser", accept = {"\\.py$"}, priority = Registry.Priority.MAXIMUM) +public class PythonTreeGenerator extends ExternalProcessTreeGenerator { + + private static final String PYTHONPARSER_CMD = System.getProperty("gt.pp.path", "pythonparser"); + + private static final QName VALUE = new QName("value"); + + private static final QName LINENO = new QName("lineno"); + + private static final QName COL = new QName("col"); + + private static final QName END_LINENO = new QName("end_line_no"); + + private static final QName END_COL = new QName("end_col"); + + private LineReader lr; + + private TreeContext context; + + @Override + public TreeContext generate(Reader r) throws IOException { + lr = new LineReader(r); + String output = readStandardOutput(lr); + return getTreeContext(output); + } + + public TreeContext getTreeContext(String xml) { + XMLInputFactory fact = XMLInputFactory.newInstance(); + context = new TreeContext(); + try { + ArrayDeque trees = new ArrayDeque<>(); + XMLEventReader r = fact.createXMLEventReader(new StringReader(xml)); + while (r.hasNext()) { + XMLEvent ev = r.nextEvent(); + if (ev.isStartElement()) { + StartElement s = ev.asStartElement(); + String typeLabel = s.getName().getLocalPart(); + String label = ""; + if (s.getAttributeByName(VALUE) != null) + label = s.getAttributeByName(VALUE).getValue(); + int type = typeLabel.hashCode(); + ITree t = context.createTree(type, label, typeLabel); + if (trees.isEmpty()) { + context.setRoot(t); + } else { + t.setParentAndUpdateChildren(trees.peekFirst()); + } + setPos(t, s); + trees.addFirst(t); + } else if (ev.isEndElement()) + trees.removeFirst(); + } + context.validate(); + return context; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private void setPos(ITree t, StartElement e) { + if (e.getAttributeByName(LINENO) == null) { //FIXME some nodes have start position + System.out.println(t.getLabel()); + return; + } + int line = Integer.parseInt(e.getAttributeByName(LINENO).getValue()); + int column = Integer.parseInt(e.getAttributeByName(COL).getValue()); + t.setPos(lr.positionFor(line, column) + 2); + if (e.getAttributeByName(END_LINENO) == null) { //FIXME some nodes have no end position + System.out.println(t.getLabel()); + return; + } + int endLine = Integer.parseInt(e.getAttributeByName(END_LINENO).getValue()); + int endColumn = Integer.parseInt(e.getAttributeByName(END_COL).getValue()); + t.setLength(lr.positionFor(endLine, endColumn) - lr.positionFor(line, column)); + } + + public String[] getCommandLine(String file) { + return new String[]{PYTHONPARSER_CMD, file}; + } +} diff --git a/gen.sax/src/test/java/com/github/gumtreediff/gen/sax/TestParsing.java b/gen.python/src/test/java/com/github/gumtreediff/gen/python/TestPythonTreeGenerator.java similarity index 56% rename from gen.sax/src/test/java/com/github/gumtreediff/gen/sax/TestParsing.java rename to gen.python/src/test/java/com/github/gumtreediff/gen/python/TestPythonTreeGenerator.java index 3b7955cdb..704be1e2c 100644 --- a/gen.sax/src/test/java/com/github/gumtreediff/gen/sax/TestParsing.java +++ b/gen.python/src/test/java/com/github/gumtreediff/gen/python/TestPythonTreeGenerator.java @@ -14,25 +14,26 @@ * You should have received a copy of the GNU Lesser General Public License * along with GumTree. If not, see . * - * Copyright 2011-2015 Jean-Rémy Falleri - * Copyright 2011-2015 Floréal Morandat + * Copyright 2016 Jean-Rémy Falleri */ -package com.github.gumtreediff.gen.sax; +package com.github.gumtreediff.gen.python; -import com.github.gumtreediff.io.TreeIoUtils; -import com.github.gumtreediff.tree.TreeContext; -import com.github.gumtreediff.io.TreeIoUtils; +import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeContext; +import org.junit.Assert; import org.junit.Test; -import java.io.InputStreamReader; +import java.io.IOException; + +public class TestPythonTreeGenerator { -public class TestParsing { @Test - public void testA1() throws Exception { - TreeContext tc = new SaxTreeGenerator().generateFromReader( - new InputStreamReader(getClass().getResourceAsStream("/action_v0.xml"))); - TreeIoUtils.toXml(tc).writeTo(System.out); + public void testSimple() throws IOException { + String input = "import sys\nimport json as json\n"; + TreeContext ctx = new PythonTreeGenerator().generateFromString(input); + ITree t = ctx.getRoot(); + Assert.assertEquals(6, t.getSize()); } + } diff --git a/gen.ruby/build.gradle b/gen.ruby/build.gradle index eeb2310c8..282e636c2 100644 --- a/gen.ruby/build.gradle +++ b/gen.ruby/build.gradle @@ -1,5 +1,5 @@ description = 'GumTree tree generator for Ruby code (JRuby based).' dependencies { - compile 'org.jruby:jrubyparser:0.5.3' + implementation 'org.jruby:jrubyparser:0.5.3' } diff --git a/gen.ruby/src/main/java/com/github/gumtreediff/gen/ruby/RubyTreeGenerator.java b/gen.ruby/src/main/java/com/github/gumtreediff/gen/ruby/RubyTreeGenerator.java index 4da026c72..93ed2e4c8 100644 --- a/gen.ruby/src/main/java/com/github/gumtreediff/gen/ruby/RubyTreeGenerator.java +++ b/gen.ruby/src/main/java/com/github/gumtreediff/gen/ruby/RubyTreeGenerator.java @@ -22,6 +22,7 @@ import com.github.gumtreediff.gen.Register; import com.github.gumtreediff.gen.Registry; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.gen.TreeGenerator; import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeContext; @@ -36,12 +37,18 @@ @Register(id = "ruby-jruby", accept = {"\\.ruby$", "\\.rb$"}, priority = Registry.Priority.MAXIMUM) public class RubyTreeGenerator extends TreeGenerator { + @Override public TreeContext generate(Reader r) throws IOException { Parser p = new Parser(); CompatVersion version = CompatVersion.RUBY2_0; ParserConfiguration config = new ParserConfiguration(0, version); - Node n = p.parse("", r, config); - return extractTreeContext(new TreeContext(), n, null); + try { + Node n = p.parse("", r, config); + return extractTreeContext(new TreeContext(), n, null); + } + catch (org.jrubyparser.lexer.SyntaxException e ) { + throw new SyntaxException(this, r); + } } private TreeContext extractTreeContext(TreeContext treeContext, Node node, ITree parent) { diff --git a/gen.ruby/src/test/java/com/github/gumtreediff/gen/ruby/TestRubyGenerator.java b/gen.ruby/src/test/java/com/github/gumtreediff/gen/ruby/TestRubyGenerator.java index e427c67a5..5d5bbb979 100644 --- a/gen.ruby/src/test/java/com/github/gumtreediff/gen/ruby/TestRubyGenerator.java +++ b/gen.ruby/src/test/java/com/github/gumtreediff/gen/ruby/TestRubyGenerator.java @@ -24,6 +24,7 @@ import java.io.InputStreamReader; import java.io.Reader; +import com.github.gumtreediff.gen.SyntaxException; import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeContext; import org.junit.Test; @@ -35,7 +36,7 @@ public class TestRubyGenerator { @Test public void testFileParsing() throws IOException { - Reader r = new InputStreamReader(getClass().getResourceAsStream("/sample.rb")); + Reader r = new InputStreamReader(getClass().getResourceAsStream("/sample.rb"), "UTF-8"); ITree tree = new RubyTreeGenerator().generateFromReader(r).getRoot(); assertEquals(102, tree.getType()); assertEquals(1726, tree.getSize()); @@ -62,4 +63,10 @@ public void testPosition() throws IOException { ITree root = ctx.getRoot(); } + @Test(expected = SyntaxException.class) + public void badSyntax() throws IOException { + String input = "module Foo\ndef foo((bar)\n\tputs 'foo'\nend\n"; + TreeContext ct = new RubyTreeGenerator().generateFromString(input); + } + } diff --git a/gen.sax/src/main/java/com/github/gumtreediff/gen/sax/SaxTreeGenerator.java b/gen.sax/src/main/java/com/github/gumtreediff/gen/sax/SaxTreeGenerator.java deleted file mode 100644 index 6e9f58a73..000000000 --- a/gen.sax/src/main/java/com/github/gumtreediff/gen/sax/SaxTreeGenerator.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * This file is part of GumTree. - * - * GumTree is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GumTree is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with GumTree. If not, see . - * - * Copyright 2011-2015 Jean-Rémy Falleri - * Copyright 2011-2015 Floréal Morandat - */ - -package com.github.gumtreediff.gen.sax; - -import com.github.gumtreediff.gen.Register; -import com.github.gumtreediff.io.LineReader; -import com.github.gumtreediff.gen.TreeGenerator; -import com.github.gumtreediff.tree.ITree; -import com.github.gumtreediff.tree.TreeContext; -import org.xml.sax.*; -import org.xml.sax.helpers.DefaultHandler; -import org.xml.sax.helpers.XMLReaderFactory; - -import java.io.IOException; -import java.io.Reader; -import java.util.*; - -import static com.github.gumtreediff.tree.ITree.NO_LABEL; - -@Register(id = "xml-sax", accept = {"\\.xml$", "\\.xsd$", "\\.wadl$"}) -public class SaxTreeGenerator extends TreeGenerator { - public static final String DOCUMENT = "Document"; - public static final String ATTR = "Attr"; - public static final String CDATA = "CData"; - public static final String ELT = "Elt"; - public static final String VALUE = "Value"; - - public static final int CDATA_ID = 3; - public static final int DOCUMENT_ID = 0; - public static final int ATTR_ID = 2; - public static final int ELT_ID = 1; - public static final int VALUE_ID = 4; - - public TreeContext generate(Reader reader) throws IOException { - try { -// TreeContext tc = new TreeContext(); - XMLReader xr = XMLReaderFactory.createXMLReader(); - LineReader lr = new LineReader(reader); - XmlHandlers hdl = new XmlHandlers(lr); - - xr.setContentHandler(hdl); - xr.setErrorHandler(hdl); - xr.parse(new InputSource(lr)); - return hdl.tc; - } catch (SAXException e) { - e.printStackTrace(); - } finally { - // close resources - } - return null; - } - - class XmlHandlers extends DefaultHandler { - public int[] lastPosition = {1, 1}; - - Locator locator; - Deque stack = new ArrayDeque(); - TreeContext tc = new TreeContext(); - Map names = new HashMap<>(); - LineReader lineReader; - - public XmlHandlers(LineReader lr) { - lineReader = lr; - } - - @Override - public void setDocumentLocator(Locator locator) { - this.locator = locator; - } - - @Override - public void startDocument() throws SAXException { - debug("startdoc"); - - ITree t = tc.createTree(DOCUMENT_ID, NO_LABEL, DOCUMENT); - t.setPos(0); -// t.setLcPosStart(lastPosition); - tc.setRoot(t); - stack.push(t); - } - - public void processingInstruction(String target, String data) { - System.out.println(target + " " + data); - } - - @Override - public void endDocument() throws SAXException { - debug("enddoc"); - ITree t = stack.pop(); - int line = locator.getLineNumber(); - int col = locator.getColumnNumber(); -// t.setLcPosEnd(new int[]{line, col}); - t.setLength(lineReader.positionFor(line, col)); - assert stack.isEmpty(); - } - - @Override - public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException { - debug("startElt", qName); - ITree t = tc.createTree(ELT_ID, qName, ELT); - addAttributes(t, attributes); - setStartPosition(t); - ITree p = stack.peek(); - p.addChild(t); - stack.push(t); - } - - void debug(Object... o) { - System.out.println("lc: " + locator.getLineNumber() + ":" + locator.getColumnNumber()); - System.out.println("last: " + Arrays.toString(lastPosition)); - System.out.println("stack: " + stack); - if (o.length > 0) - System.out.println("=> " + Arrays.toString(o)); - } - - private void setStartPosition(ITree t) { - int[] pos = currentPosition(); -// t.setLcPosStart(pos); - t.setPos(lineReader.positionFor(pos[0], pos[1])); - } - - private void setEndPosition(ITree t) { - int[] pos = currentPosition(); -// t.setLcPosEnd(new int[]{locator.getLineNumber(), locator.getColumnNumber()}); //FIXME - t.setPos(lineReader.positionFor(locator.getLineNumber(), locator.getColumnNumber()) - t.getPos()); - } - - private int[] currentPosition() { - int[] lp = lastPosition; - lastPosition = new int[]{locator.getLineNumber(), locator.getColumnNumber()}; - return lp; - } - - private void addAttributes(ITree tree, Attributes attrs) { - int len = attrs.getLength(); - for (int i = 0; i < len; i++) { - ITree at = tc.createTree(ATTR_ID, attrs.getQName(i), ATTR); - at.addChild(tc.createTree(VALUE_ID, attrs.getValue(i), VALUE)); - tree.addChild(at); - setStartPosition(at); - setEndPosition(at); // FIXME grab next - } - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - debug("endelt", localName); - - ITree t = stack.pop(); - setEndPosition(t); - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - debug("char", start, length); - tc.createTree(CDATA_ID, new String(ch, start, length), CDATA); - } - } -} diff --git a/gen.sax/src/test/resources/Dummy_big.xml b/gen.sax/src/test/resources/Dummy_big.xml deleted file mode 100644 index f920353e9..000000000 --- a/gen.sax/src/test/resources/Dummy_big.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gen.sax/src/test/resources/Dummy_v0.xml b/gen.sax/src/test/resources/Dummy_v0.xml deleted file mode 100644 index 5678f6959..000000000 --- a/gen.sax/src/test/resources/Dummy_v0.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - diff --git a/gen.sax/src/test/resources/Dummy_v1.xml b/gen.sax/src/test/resources/Dummy_v1.xml deleted file mode 100644 index d84034a30..000000000 --- a/gen.sax/src/test/resources/Dummy_v1.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/gen.sax/src/test/resources/action_v0.xml b/gen.sax/src/test/resources/action_v0.xml deleted file mode 100644 index 3095a2b64..000000000 --- a/gen.sax/src/test/resources/action_v0.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/gen.sax/src/test/resources/action_v1.xml b/gen.sax/src/test/resources/action_v1.xml deleted file mode 100644 index ef2e5e6eb..000000000 --- a/gen.sax/src/test/resources/action_v1.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/gen.srcml/src/main/java/com/github/gumtreediff/gen/srcml/AbstractSrcmlTreeGenerator.java b/gen.srcml/src/main/java/com/github/gumtreediff/gen/srcml/AbstractSrcmlTreeGenerator.java index 7679aad0f..5136a21bb 100644 --- a/gen.srcml/src/main/java/com/github/gumtreediff/gen/srcml/AbstractSrcmlTreeGenerator.java +++ b/gen.srcml/src/main/java/com/github/gumtreediff/gen/srcml/AbstractSrcmlTreeGenerator.java @@ -19,7 +19,7 @@ package com.github.gumtreediff.gen.srcml; -import com.github.gumtreediff.gen.TreeGenerator; +import com.github.gumtreediff.gen.ExternalProcessTreeGenerator; import com.github.gumtreediff.io.LineReader; import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.tree.TreeContext; @@ -31,9 +31,9 @@ import java.io.*; import java.util.*; -public abstract class AbstractSrcmlTreeGenerator extends TreeGenerator { +public abstract class AbstractSrcmlTreeGenerator extends ExternalProcessTreeGenerator { - private static final String SRCML_CMD = System.getProperty("gumtree.srcml.path", "srcml"); + private static final String SRCML_CMD = System.getProperty("gt.srcml.path", "srcml"); private static final QName LINE = new QName("http://www.srcML.org/srcML/position", "line", "pos"); @@ -44,23 +44,23 @@ public abstract class AbstractSrcmlTreeGenerator extends TreeGenerator { private Set labeled = new HashSet( Arrays.asList("specifier", "name", "comment", "literal", "operator")); - private StringBuffer currentLabel; + private StringBuilder currentLabel; private TreeContext context; @Override public TreeContext generate(Reader r) throws IOException { lr = new LineReader(r); - String xml = getXml(lr); - return getTreeContext(xml); + String output = readStandardOutput(lr); + return getTreeContext(output); } public TreeContext getTreeContext(String xml) { XMLInputFactory fact = XMLInputFactory.newInstance(); context = new TreeContext(); - currentLabel = new StringBuffer(); + currentLabel = new StringBuilder(); try { - Stack trees = new Stack<>(); + ArrayDeque trees = new ArrayDeque<>(); XMLEventReader r = fact.createXMLEventReader(new StringReader(xml)); while (r.hasNext()) { XMLEvent ev = r.nextEvent(); @@ -68,7 +68,7 @@ public TreeContext getTreeContext(String xml) { StartElement s = ev.asStartElement(); String typeLabel = s.getName().getLocalPart(); if (typeLabel.equals("position")) - setLength(trees.peek(), s); + setLength(trees.peekFirst(), s); else { int type = typeLabel.hashCode(); ITree t = context.createTree(type, "", typeLabel); @@ -77,18 +77,18 @@ public TreeContext getTreeContext(String xml) { context.setRoot(t); t.setPos(0); } else { - t.setParentAndUpdateChildren(trees.peek()); + t.setParentAndUpdateChildren(trees.peekFirst()); setPos(t, s); } - trees.push(t); + trees.addFirst(t); } } else if (ev.isEndElement()) { EndElement end = ev.asEndElement(); if (!end.getName().getLocalPart().equals("position")) { if (isLabeled(trees)) - trees.peek().setLabel(currentLabel.toString()); - trees.pop(); - currentLabel = new StringBuffer(); + trees.peekFirst().setLabel(currentLabel.toString()); + trees.removeFirst(); + currentLabel = new StringBuilder(); } } else if (ev.isCharacters()) { Characters chars = ev.asCharacters(); @@ -105,8 +105,8 @@ public TreeContext getTreeContext(String xml) { return null; } - private boolean isLabeled(Stack trees) { - return labeled.contains(context.getTypeLabel(trees.peek().getType())); + private boolean isLabeled(ArrayDeque trees) { + return labeled.contains(context.getTypeLabel(trees.peekFirst().getType())); } private void fixPos(TreeContext ctx) { @@ -144,44 +144,9 @@ private void setLength(ITree t, StartElement e) { } } - public String getXml(Reader r) throws IOException { - //FIXME this is not efficient but I am not sure how to speed up things here. - File f = File.createTempFile("gumtree", ""); - FileWriter w = new FileWriter(f); - BufferedReader br = new BufferedReader(r); - String line = br.readLine(); - while (line != null) { - w.append(line); - w.append(System.lineSeparator()); - line = br.readLine(); - } - w.close(); - br.close(); - ProcessBuilder b = new ProcessBuilder(getArguments(f.getAbsolutePath())); - b.directory(f.getParentFile()); - try { - Process p = b.start(); - StringBuffer buf = new StringBuffer(); - br = new BufferedReader(new InputStreamReader(p.getInputStream())); - // TODO Why do we need to read and bufferize everything, when we could/should only use generateFromStream - line = null; - while ((line = br.readLine()) != null) - buf.append(line + "\n"); - p.waitFor(); - if (p.exitValue() != 0) throw new RuntimeException(buf.toString()); - r.close(); - String xml = buf.toString(); - return xml; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - f.delete(); - } - } - public abstract String getLanguage(); - public String[] getArguments(String file) { + public String[] getCommandLine(String file) { return new String[]{SRCML_CMD, "-l", getLanguage(), "--position", file, "--tabs=1"}; } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar old mode 100644 new mode 100755 index a5fe1cb94..87b738cbd Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be280bec0..f0e382406 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue Feb 26 09:47:49 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip diff --git a/gradlew b/gradlew index cccdd3d51..af6708ff2 100755 --- a/gradlew +++ b/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a..0f8d5937c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/gumtree_checkstyle.xml b/gumtree_checkstyle.xml index 69f4a9f6a..e1242764d 100644 --- a/gumtree_checkstyle.xml +++ b/gumtree_checkstyle.xml @@ -4,7 +4,7 @@ "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> - + @@ -151,7 +151,7 @@ value="Method name ''{0}'' must match pattern ''{1}''."/> - + diff --git a/settings.gradle b/settings.gradle index 3bb2f4188..f59d9fe77 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,10 +10,11 @@ include 'benchmark', 'gen.antlr3-r', 'gen.antlr3-xml', 'gen.antlr4', - 'gen.antlr4-matlab', 'gen.c', 'gen.css', + 'gen.javaparser', 'gen.jdt', 'gen.js', + 'gen.python', 'gen.ruby', 'gen.srcml'