mirror of
https://github.com/SonarSource/sonarqube.git
synced 2024-08-07 15:05:55 +02:00
SONAR-3072 Add sonar-diff library
This commit is contained in:
parent
6a907c8f5d
commit
abb8bac7ab
3
pom.xml
3
pom.xml
@ -21,6 +21,7 @@
|
||||
<module>sonar-colorizer</module>
|
||||
<module>sonar-core</module>
|
||||
<module>sonar-deprecated</module>
|
||||
<module>sonar-diff</module>
|
||||
<module>sonar-duplications</module>
|
||||
<module>sonar-graph</module>
|
||||
<module>sonar-gwt-api</module>
|
||||
@ -1086,7 +1087,7 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
|
||||
<profile>
|
||||
<id>javadoc</id>
|
||||
<build>
|
||||
|
35
sonar-diff/pom.xml
Normal file
35
sonar-diff/pom.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.codehaus.sonar</groupId>
|
||||
<artifactId>sonar</artifactId>
|
||||
<version>2.15-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>sonar-diff</artifactId>
|
||||
|
||||
<name>Sonar :: Diff</name>
|
||||
<description>Detect changes in files</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
95
sonar-diff/src/main/java/org/sonar/diff/CodeChurn.java
Normal file
95
sonar-diff/src/main/java/org/sonar/diff/CodeChurn.java
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
public final class CodeChurn {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("2 arguments required");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
Text a = new Text(Files.toByteArray(new File(args[0])));
|
||||
Text b = new Text(Files.toByteArray(new File(args[1])));
|
||||
CodeChurn r = new CodeChurn(a, b, TextComparator.IGNORE_WHITESPACE);
|
||||
System.out.println("Deleted: " + r.getDeleted());
|
||||
System.out.println("Added: " + r.getAdded());
|
||||
for (Edit edit : r.getDiff()) {
|
||||
System.out.println(edit);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public CodeChurn(Text a, Text b, TextComparator cmp) {
|
||||
diff = new DiffAlgorithm().diff(a, b, cmp);
|
||||
|
||||
int added = 0;
|
||||
int deleted = 0;
|
||||
BitSet remains = new BitSet(a.length());
|
||||
for (Edit edit : diff) {
|
||||
switch (edit.getType()) {
|
||||
case INSERT:
|
||||
added += edit.endB - edit.beginB + 1;
|
||||
break;
|
||||
case MOVE:
|
||||
for (int i = edit.beginA; i <= edit.endA; i++) {
|
||||
remains.set(i);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < a.length(); i++) {
|
||||
if (!remains.get(i)) {
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
this.added = added;
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
private final List<Edit> diff;
|
||||
private final int added;
|
||||
private final int deleted;
|
||||
|
||||
public int getAdded() {
|
||||
return added;
|
||||
}
|
||||
|
||||
public int getDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public List<Edit> getDiff() {
|
||||
return diff;
|
||||
}
|
||||
|
||||
}
|
112
sonar-diff/src/main/java/org/sonar/diff/DiffAlgorithm.java
Normal file
112
sonar-diff/src/main/java/org/sonar/diff/DiffAlgorithm.java
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Diff algorithm, based on
|
||||
* "The String-to-String Correction Problem with Block Moves", by Waller F. Tichy.
|
||||
*/
|
||||
public class DiffAlgorithm {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("2 arguments required");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
Text a = new Text(Files.toByteArray(new File(args[0])));
|
||||
Text b = new Text(Files.toByteArray(new File(args[1])));
|
||||
List<Edit> r = new DiffAlgorithm().diff(a, b, TextComparator.IGNORE_WHITESPACE);
|
||||
for (Edit edit : r) {
|
||||
System.out.println(edit);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Edit> diff(Text a, Text b, TextComparator cmp) {
|
||||
return diff(wrap(a, cmp), wrap(b, cmp), cmp);
|
||||
}
|
||||
|
||||
private static HashedSequence<Text> wrap(Text seq, TextComparator cmp) {
|
||||
int size = seq.length();
|
||||
int[] hashes = new int[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
hashes[i] = cmp.hash(seq, i);
|
||||
}
|
||||
return new HashedSequence<Text>(seq, hashes);
|
||||
}
|
||||
|
||||
private List<Edit> diff(HashedSequence<Text> s, HashedSequence<Text> t, TextComparator cmp) {
|
||||
HashedSequenceComparator<Text> comparator = new HashedSequenceComparator<Text>(cmp);
|
||||
Edit lastEdit = null;
|
||||
List<Edit> r = Lists.newArrayList();
|
||||
|
||||
int m = s.length();
|
||||
int n = t.length();
|
||||
|
||||
int q = 0;
|
||||
while (q < n) {
|
||||
// find p and l such that (p,q,l) is a maximal block move
|
||||
int l = 0;
|
||||
int p = 0;
|
||||
int pCur = 0;
|
||||
|
||||
while ((pCur + l < m) && (q + l < n)) {
|
||||
int lCur = 0;
|
||||
while ((pCur + lCur < m) && (q + lCur < n)
|
||||
&& (comparator.equals(s, pCur + lCur, t, q + lCur))) {
|
||||
lCur++;
|
||||
}
|
||||
if (lCur > l) {
|
||||
l = lCur;
|
||||
p = pCur;
|
||||
}
|
||||
pCur++;
|
||||
}
|
||||
|
||||
if (l > 0) {
|
||||
Edit edit = new Edit(Edit.Type.MOVE, p, p + l - 1, q, q + l - 1);
|
||||
r.add(edit);
|
||||
lastEdit = edit;
|
||||
q += l;
|
||||
} else {
|
||||
if (lastEdit == null || lastEdit.getType() != Edit.Type.INSERT) {
|
||||
Edit edit = new Edit(Edit.Type.INSERT, -1, -1, q, q);
|
||||
r.add(edit);
|
||||
lastEdit = edit;
|
||||
} else {
|
||||
lastEdit.endB++;
|
||||
}
|
||||
q++;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
}
|
73
sonar-diff/src/main/java/org/sonar/diff/Edit.java
Normal file
73
sonar-diff/src/main/java/org/sonar/diff/Edit.java
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
public class Edit {
|
||||
|
||||
public static enum Type {
|
||||
INSERT,
|
||||
MOVE
|
||||
}
|
||||
|
||||
int beginA;
|
||||
int endA;
|
||||
int beginB;
|
||||
int endB;
|
||||
final Type type;
|
||||
|
||||
public Edit(Type type, int beginA, int endA, int beginB, int endB) {
|
||||
this.beginA = beginA;
|
||||
this.endA = endA;
|
||||
this.beginB = beginB;
|
||||
this.endB = endB;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof Edit)) {
|
||||
return false;
|
||||
}
|
||||
Edit e = (Edit) obj;
|
||||
return type == e.type
|
||||
&& beginA == e.beginA
|
||||
&& endA == e.endA
|
||||
&& beginB == e.beginB
|
||||
&& endB == e.endB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("type", type)
|
||||
.add("beginA", beginA)
|
||||
.add("endA", endA)
|
||||
.add("beginB", beginB)
|
||||
.add("endB", endB)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
39
sonar-diff/src/main/java/org/sonar/diff/HashedSequence.java
Normal file
39
sonar-diff/src/main/java/org/sonar/diff/HashedSequence.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
/**
|
||||
* Wraps a {@link Sequence} to assign hash codes to elements.
|
||||
*/
|
||||
public class HashedSequence<S extends Sequence> implements Sequence {
|
||||
|
||||
final S base;
|
||||
final int[] hashes;
|
||||
|
||||
public HashedSequence(S base, int[] hashes) {
|
||||
this.base = base;
|
||||
this.hashes = hashes;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return base.length();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
/**
|
||||
* Wrap another {@link SequenceComparator} for use with {@link HashedSequence}.
|
||||
*/
|
||||
public class HashedSequenceComparator<S extends Sequence> extends SequenceComparator<HashedSequence<S>> {
|
||||
|
||||
private final SequenceComparator<? super S> cmp;
|
||||
|
||||
public HashedSequenceComparator(SequenceComparator<? super S> cmp) {
|
||||
this.cmp = cmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(HashedSequence<S> a, int ai, HashedSequence<S> b, int bi) {
|
||||
return a.hashes[ai] == b.hashes[bi] && cmp.equals(a.base, ai, b.base, bi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hash(HashedSequence<S> seq, int i) {
|
||||
return seq.hashes[i];
|
||||
}
|
||||
|
||||
}
|
32
sonar-diff/src/main/java/org/sonar/diff/Sequence.java
Normal file
32
sonar-diff/src/main/java/org/sonar/diff/Sequence.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
/**
|
||||
* Arbitrary sequence of elements.
|
||||
*/
|
||||
public interface Sequence {
|
||||
|
||||
/**
|
||||
* @return total number of items in the sequence
|
||||
*/
|
||||
int length();
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
/**
|
||||
* Equivalence function for a {@link Sequence}.
|
||||
*/
|
||||
public abstract class SequenceComparator<S extends Sequence> {
|
||||
|
||||
/**
|
||||
* Compare two items to determine if they are equivalent.
|
||||
*/
|
||||
public abstract boolean equals(S a, int ai, S b, int bi);
|
||||
|
||||
/**
|
||||
* Get a hash value for an item in a sequence.
|
||||
*
|
||||
* If two items are equal according to this comparator's
|
||||
* {@link #equals(Sequence, int, Sequence, int)} method,
|
||||
* then this hash method must produce the same integer result for both items.
|
||||
* However not required to have different hash values for different items.
|
||||
*/
|
||||
public abstract int hash(S seq, int i);
|
||||
|
||||
}
|
87
sonar-diff/src/main/java/org/sonar/diff/Text.java
Normal file
87
sonar-diff/src/main/java/org/sonar/diff/Text.java
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Text is a {@link Sequence} of lines.
|
||||
*/
|
||||
public class Text implements Sequence {
|
||||
|
||||
final byte[] content;
|
||||
|
||||
/**
|
||||
* Map of line number to starting position within {@link #content}.
|
||||
*/
|
||||
final List<Integer> lines;
|
||||
|
||||
public Text(byte[] bytes) {
|
||||
this.content = bytes;
|
||||
lines = lineMap(content, 0, content.length);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return lines.size() - 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text for a single line.
|
||||
*/
|
||||
public String getString(int line) {
|
||||
int s = getStart(line);
|
||||
int e = getEnd(line);
|
||||
return new String(content, s, e - s);
|
||||
}
|
||||
|
||||
private int getStart(final int line) {
|
||||
return lines.get(line + 1);
|
||||
}
|
||||
|
||||
private int getEnd(final int line) {
|
||||
return lines.get(line + 2);
|
||||
}
|
||||
|
||||
private static List<Integer> lineMap(final byte[] buf, int ptr, int end) {
|
||||
List<Integer> lines = Lists.newArrayList();
|
||||
lines.add(Integer.MIN_VALUE);
|
||||
for (; ptr < end; ptr = nextLF(buf, ptr)) {
|
||||
lines.add(ptr);
|
||||
}
|
||||
lines.add(end);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static final int nextLF(final byte[] b, int ptr) {
|
||||
return next(b, ptr, '\n');
|
||||
}
|
||||
|
||||
private static final int next(final byte[] b, int ptr, final char chrA) {
|
||||
final int sz = b.length;
|
||||
while (ptr < sz) {
|
||||
if (b[ptr++] == chrA)
|
||||
return ptr;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
}
|
141
sonar-diff/src/main/java/org/sonar/diff/TextComparator.java
Normal file
141
sonar-diff/src/main/java/org/sonar/diff/TextComparator.java
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
/**
|
||||
* Equivalence function for {@link Text}.
|
||||
*/
|
||||
public abstract class TextComparator extends SequenceComparator<Text> {
|
||||
|
||||
public static final TextComparator DEFAULT = new TextComparator() {
|
||||
@Override
|
||||
public boolean equals(Text a, int ai, Text b, int bi) {
|
||||
ai++;
|
||||
bi++;
|
||||
int as = a.lines.get(ai);
|
||||
int bs = b.lines.get(bi);
|
||||
int ae = a.lines.get(ai + 1);
|
||||
int be = b.lines.get(bi + 1);
|
||||
if (ae - as != be - bs) {
|
||||
return false;
|
||||
}
|
||||
while (as < ae) {
|
||||
if (a.content[as++] != b.content[bs++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashRegion(final byte[] raw, int start, final int end) {
|
||||
int hash = 5381;
|
||||
for (; start < end; start++) {
|
||||
hash = ((hash << 5) + hash) + (raw[start] & 0xff);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ignores all whitespace.
|
||||
*/
|
||||
public static final TextComparator IGNORE_WHITESPACE = new TextComparator() {
|
||||
@Override
|
||||
public boolean equals(Text a, int ai, Text b, int bi) {
|
||||
ai++;
|
||||
bi++;
|
||||
int as = a.lines.get(ai);
|
||||
int bs = b.lines.get(bi);
|
||||
int ae = a.lines.get(ai + 1);
|
||||
int be = b.lines.get(bi + 1);
|
||||
ae = trimTrailingWhitespace(a.content, as, ae);
|
||||
be = trimTrailingWhitespace(b.content, bs, be);
|
||||
while ((as < ae) && (bs < be)) {
|
||||
byte ac = a.content[as];
|
||||
byte bc = b.content[bs];
|
||||
while ((as < ae - 1) && (isWhitespace(ac))) {
|
||||
as++;
|
||||
ac = a.content[as];
|
||||
}
|
||||
while ((bs < be - 1) && (isWhitespace(bc))) {
|
||||
bs++;
|
||||
bc = b.content[bs];
|
||||
}
|
||||
if (ac != bc) {
|
||||
return false;
|
||||
}
|
||||
as++;
|
||||
bs++;
|
||||
}
|
||||
return (as == ae) && (bs == be);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashRegion(byte[] raw, int start, int end) {
|
||||
int hash = 5381;
|
||||
for (; start < end; start++) {
|
||||
byte c = raw[start];
|
||||
if (!isWhitespace(c)) {
|
||||
hash = ((hash << 5) + hash) + (c & 0xff);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int hash(Text seq, int line) {
|
||||
final int begin = seq.lines.get(line + 1);
|
||||
final int end = seq.lines.get(line + 2);
|
||||
return hashRegion(seq.content, begin, end);
|
||||
}
|
||||
|
||||
protected abstract int hashRegion(final byte[] raw, int start, final int end);
|
||||
|
||||
private static final boolean[] WHITESPACE = new boolean[256];
|
||||
|
||||
static {
|
||||
WHITESPACE['\r'] = true;
|
||||
WHITESPACE['\n'] = true;
|
||||
WHITESPACE['\t'] = true;
|
||||
WHITESPACE[' '] = true;
|
||||
}
|
||||
|
||||
public static boolean isWhitespace(byte c) {
|
||||
return WHITESPACE[c & 0xff];
|
||||
}
|
||||
|
||||
public static int trimTrailingWhitespace(byte[] raw, int start, int end) {
|
||||
end--;
|
||||
while (start <= end && isWhitespace(raw[end])) {
|
||||
end--;
|
||||
}
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
public static int trimLeadingWhitespace(byte[] raw, int start, int end) {
|
||||
while (start < end && isWhitespace(raw[start])) {
|
||||
start++;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
}
|
25
sonar-diff/src/main/java/org/sonar/diff/package-info.java
Normal file
25
sonar-diff/src/main/java/org/sonar/diff/package-info.java
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package org.sonar.diff;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class DiffFunctionalTest {
|
||||
|
||||
@Test
|
||||
public void example0() throws Exception {
|
||||
CodeChurn r = diff("example0");
|
||||
assertThat(r.getAdded(), is(5));
|
||||
assertThat(r.getDeleted(), is(0));
|
||||
assertThat(r.getDiff().size(), is(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void example1() throws Exception {
|
||||
CodeChurn r = diff("example1");
|
||||
assertThat(r.getAdded(), is(2));
|
||||
assertThat(r.getDeleted(), is(1));
|
||||
assertThat(r.getDiff().size(), is(4));
|
||||
}
|
||||
|
||||
private CodeChurn diff(String name) throws IOException {
|
||||
return diff("examples/" + name + "/v1.java", "examples/" + name + "/v2.java");
|
||||
}
|
||||
|
||||
private CodeChurn diff(String resourceA, String resourceB) throws IOException {
|
||||
Text a = new Text(Resources.toByteArray(Resources.getResource(resourceA)));
|
||||
Text b = new Text(Resources.toByteArray(Resources.getResource(resourceB)));
|
||||
return new CodeChurn(a, b, TextComparator.IGNORE_WHITESPACE);
|
||||
}
|
||||
|
||||
}
|
171
sonar-diff/src/test/java/org/sonar/diff/DiffTest.java
Normal file
171
sonar-diff/src/test/java/org/sonar/diff/DiffTest.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.sonar.diff.Edit.Type;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class DiffTest {
|
||||
|
||||
@Test
|
||||
public void emptyInputs() {
|
||||
List<Edit> r = diff(t(""), t(""));
|
||||
assertThat(r.isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFile() {
|
||||
List<Edit> r = diff(t(""), t("AB"));
|
||||
assertThat(r.size(), is(1));
|
||||
assertThat(r.get(0), is(new Edit(Type.INSERT, -1, -1, 0, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteFile() {
|
||||
List<Edit> r = diff(t("AB"), t(""));
|
||||
assertThat(r.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertMiddle() {
|
||||
List<Edit> r = diff(t("ac"), t("aBc"));
|
||||
assertThat(r.size(), is(3));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 0, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.INSERT, -1, -1, 1, 1)));
|
||||
assertThat(r.get(2), is(new Edit(Type.MOVE, 1, 1, 2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteMiddle() {
|
||||
List<Edit> r = diff(t("aBc"), t("ac"));
|
||||
assertThat(r.size(), is(2));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 0, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.MOVE, 2, 2, 1, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMiddle() {
|
||||
List<Edit> r = diff(t("aBc"), t("aDc"));
|
||||
assertThat(r.size(), is(3));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 0, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.INSERT, -1, -1, 1, 1)));
|
||||
assertThat(r.get(2), is(new Edit(Type.MOVE, 2, 2, 2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertStart() {
|
||||
List<Edit> r = diff(t("bc"), t("Abc"));
|
||||
assertThat(r.size(), is(2));
|
||||
assertThat(r.get(0), is(new Edit(Type.INSERT, -1, -1, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.MOVE, 0, 1, 1, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteStart() {
|
||||
List<Edit> r = diff(t("Abc"), t("bc"));
|
||||
assertThat(r.size(), is(1));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 1, 2, 0, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertEnd() {
|
||||
List<Edit> r = diff(t("ab"), t("abC"));
|
||||
assertThat(r.size(), is(2));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 1, 0, 1)));
|
||||
assertThat(r.get(1), is(new Edit(Type.INSERT, -1, -1, 2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteEnd() {
|
||||
List<Edit> r = diff(t("abC"), t("ab"));
|
||||
assertThat(r.size(), is(1));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 1, 0, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is important special case, for which other algorithms can not detect movement.
|
||||
*/
|
||||
@Test
|
||||
public void move() {
|
||||
List<Edit> r = diff(t("Abc"), t("bcA"));
|
||||
assertThat(r.size(), is(2));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 1, 2, 0, 1)));
|
||||
assertThat(r.get(1), is(new Edit(Type.MOVE, 0, 0, 2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void move2() {
|
||||
List<Edit> r = diff(t("abcd"), t("abcda"));
|
||||
assertThat(r.size(), is(2));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 3, 0, 3)));
|
||||
assertThat(r.get(1), is(new Edit(Type.MOVE, 0, 0, 4, 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void move3() {
|
||||
List<Edit> r = diff(t("abcd"), t("bcdaa"));
|
||||
assertThat(r.size(), is(3));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 1, 3, 0, 2)));
|
||||
assertThat(r.get(1), is(new Edit(Type.MOVE, 0, 0, 3, 3)));
|
||||
assertThat(r.get(2), is(new Edit(Type.MOVE, 0, 0, 4, 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void severalInserts() {
|
||||
List<Edit> r = diff(t("ac"), t("aBcD"));
|
||||
assertThat(r.size(), is(4));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 0, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.INSERT, -1, -1, 1, 1)));
|
||||
assertThat(r.get(2), is(new Edit(Type.MOVE, 1, 1, 2, 2)));
|
||||
assertThat(r.get(3), is(new Edit(Type.INSERT, -1, -1, 3, 3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertSeveralLines() {
|
||||
List<Edit> r = diff(t("ade"), t("aBCde"));
|
||||
assertThat(r.size(), is(3));
|
||||
assertThat(r.get(0), is(new Edit(Type.MOVE, 0, 0, 0, 0)));
|
||||
assertThat(r.get(1), is(new Edit(Type.INSERT, -1, -1, 1, 2)));
|
||||
assertThat(r.get(2), is(new Edit(Type.MOVE, 1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
private List<Edit> diff(Text a, Text b) {
|
||||
return new DiffAlgorithm().diff(a, b, TextComparator.DEFAULT);
|
||||
}
|
||||
|
||||
public static Text t(String text) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
sb.append(text.charAt(i)).append('\n');
|
||||
}
|
||||
try {
|
||||
return new Text(sb.toString().getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TextComparatorTest {
|
||||
|
||||
@Test
|
||||
public void testEqualsWithoutWhitespace() {
|
||||
TextComparator cmp = TextComparator.DEFAULT;
|
||||
|
||||
Text a = new Text("abc\nabc\na bc".getBytes());
|
||||
Text b = new Text("abc\nabc d\nab c".getBytes());
|
||||
|
||||
assertThat("abc == abc", cmp.equals(a, 0, b, 0), is(true));
|
||||
assertThat("abc != abc d", cmp.equals(a, 1, b, 1), is(false));
|
||||
assertThat("a bc == ab c", cmp.equals(a, 2, b, 2), is(false));
|
||||
assertThat(cmp.hash(a, 0), equalTo(cmp.hash(b, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsWithWhitespace() {
|
||||
TextComparator cmp = TextComparator.IGNORE_WHITESPACE;
|
||||
|
||||
Text a = new Text("abc\nabc\na bc".getBytes());
|
||||
Text b = new Text("abc\nabc d\nab c".getBytes());
|
||||
|
||||
assertThat("abc == abc", cmp.equals(a, 0, b, 0), is(true));
|
||||
assertThat("abc != abc d", cmp.equals(a, 1, b, 1), is(false));
|
||||
assertThat("a bc == ab c", cmp.equals(a, 2, b, 2), is(true));
|
||||
assertThat(cmp.hash(a, 0), equalTo(cmp.hash(b, 0)));
|
||||
assertThat(cmp.hash(a, 2), equalTo(cmp.hash(b, 2)));
|
||||
}
|
||||
|
||||
}
|
41
sonar-diff/src/test/java/org/sonar/diff/TextTest.java
Normal file
41
sonar-diff/src/test/java/org/sonar/diff/TextTest.java
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Sonar, open source software quality management tool.
|
||||
* Copyright (C) 2008-2012 SonarSource
|
||||
* mailto:contact AT sonarsource DOT com
|
||||
*
|
||||
* Sonar 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.
|
||||
*
|
||||
* Sonar 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 Sonar; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
|
||||
*/
|
||||
package org.sonar.diff;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TextTest {
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
Text r = new Text(new byte[0]);
|
||||
assertThat(r.length(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoLines() {
|
||||
Text r = new Text("a\nb".getBytes());
|
||||
assertThat(r.length(), is(2));
|
||||
}
|
||||
|
||||
}
|
10
sonar-diff/src/test/resources/examples/example0/v1.java
Normal file
10
sonar-diff/src/test/resources/examples/example0/v1.java
Normal file
@ -0,0 +1,10 @@
|
||||
public class Toto {
|
||||
|
||||
public void doSomething() {
|
||||
System.out.println("doSomething");
|
||||
}
|
||||
|
||||
public void doSomethingElse() {
|
||||
System.out.println("doSomethingElse");
|
||||
}
|
||||
}
|
20
sonar-diff/src/test/resources/examples/example0/v2.java
Normal file
20
sonar-diff/src/test/resources/examples/example0/v2.java
Normal file
@ -0,0 +1,20 @@
|
||||
public class Toto {
|
||||
|
||||
public Toto(){}
|
||||
|
||||
public void doSomethingNew() {
|
||||
System.out.println("doSomethingNew");
|
||||
}
|
||||
|
||||
public void doSomethingElseNew() {
|
||||
System.out.println("doSomethingElseNew");
|
||||
}
|
||||
|
||||
public void doSomething() {
|
||||
System.out.println("doSomething");
|
||||
}
|
||||
|
||||
public void doSomethingElse() {
|
||||
System.out.println("doSomethingElse");
|
||||
}
|
||||
}
|
5
sonar-diff/src/test/resources/examples/example1/v1.java
Normal file
5
sonar-diff/src/test/resources/examples/example1/v1.java
Normal file
@ -0,0 +1,5 @@
|
||||
public class HelloWorld {
|
||||
public void sayHello() {
|
||||
System.out.println("Hello");
|
||||
}
|
||||
}
|
7
sonar-diff/src/test/resources/examples/example1/v2.java
Normal file
7
sonar-diff/src/test/resources/examples/example1/v2.java
Normal file
@ -0,0 +1,7 @@
|
||||
public class HelloWorld {
|
||||
public void sayHello(int i) {
|
||||
if (i > 0) {
|
||||
System.out.println("Hello");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user