diff --git a/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Birthmarks.java b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Birthmarks.java index eb5be535..57abbd2c 100644 --- a/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Birthmarks.java +++ b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Birthmarks.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import io.vavr.control.Either; @@ -24,6 +26,10 @@ public Birthmarks(Stream>> stream) { stream.forEach(either -> either.bimap(t -> exceptions.add(t), r -> list.add(r))); } + public Optional> unify() { + return BirthmarksMerger.unifyTo(this); + } + public Stream> find(ClassName name) { return list.stream() .filter(birthmark -> birthmark.isSame(name)); diff --git a/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/BirthmarksMerger.java b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/BirthmarksMerger.java new file mode 100644 index 00000000..cc25ca48 --- /dev/null +++ b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/BirthmarksMerger.java @@ -0,0 +1,61 @@ +package jp.cafebabe.birthmarks.entities; + +import io.vavr.control.Try; +import jp.cafebabe.birthmarks.utils.LongestCommonSubstring; +import jp.cafebabe.kunai.entries.ClassName; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class BirthmarksMerger { + public Optional> unify(Birthmarks birthmarks) { + Optional> elements = mergeElements(birthmarks); + Metadata metadata = mergeMetadata(birthmarks); + return elements.map(e -> new Birthmark<>(metadata, e)); + } + + private Metadata mergeMetadata(Birthmarks birthmarks) { + String location = findCommonLocation(birthmarks.stream()); + return constructMetadata(location, findType(birthmarks)); + } + + private Optional findType(Birthmarks birthmarks) { + return birthmarks.stream().map(b -> b.type()) + .collect(Collectors.reducing((a, b) -> a)); + } + + private Metadata constructMetadata(String location, Optional type) { + Try tryUri = Try.of(() -> new URI(location)); + Optional uri = tryUri.toJavaOptional(); + Optional name = uri.map(u -> new ClassName(findBaseName(u))); + return new Metadata(name.orElseGet(() -> new ClassName("")), uri.get(), type.get()); + } + + private String findBaseName(URI uri) { + String path = uri.toString(); + int index = path.lastIndexOf('/'); + if(index >= 0) + return path.substring(index + 1); + return path; + } + + private String findCommonLocation(Stream> stream) { + Optional location = stream.map(birthmark -> birthmark.metadata()) + .map(m -> m.location().toString()) + .collect(Collectors.reducing((s1, s2) -> LongestCommonSubstring.of(s1, s2))); + return location.orElseGet(() -> ""); + } + + private Optional> mergeElements(Birthmarks birthmarks) { + return birthmarks.stream() + .map(birthmark -> birthmark.elements()) + .collect(Collectors.reducing((a, b) -> a.merge(b))); + } + + public static Optional> unifyTo(Birthmarks birthmarks) { + return new BirthmarksMerger().unify(birthmarks); + } +} diff --git a/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Couple.java b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Couple.java index 278cd52a..45042590 100644 --- a/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Couple.java +++ b/pochi-api/src/main/java/jp/cafebabe/birthmarks/entities/Couple.java @@ -16,6 +16,11 @@ protected Couple(L left, R right) { this.right = Objects.requireNonNull(right); } + public void applyIf(BiPredicate predicate, BiConsumer consumer) { + if(predicate.test(left, right)) + consumer.accept(left, right); + } + public L left() { return left; } diff --git a/pochi-api/src/main/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstring.java b/pochi-api/src/main/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstring.java new file mode 100644 index 00000000..881b0c12 --- /dev/null +++ b/pochi-api/src/main/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstring.java @@ -0,0 +1,47 @@ +package jp.cafebabe.birthmarks.utils; + +import jp.cafebabe.birthmarks.entities.Couple; + +import java.util.Optional; + +public class LongestCommonSubstring { + public static final String of(String s1, String s2) { + return new LongestCommonSubstring() + .calculate(s1, s2); + } + + private String calculate(String s1, String s2) { + int[][] table = new int[s1.length() + 1][s2.length() + 1]; + Result r = Result.of(0, 0, 0); + for(int i = 0; i < table.length; i++) { + for(int j = 0; j < table[i].length; j++) { + if(i == 0 || j == 0) table[i][j] = 0; + else if(s1.charAt(i - 1) == s2.charAt(j - 1)) { + table[i][j] = 1 + table[i - 1][j - 1]; + r = updateResultIfNeeded(r, i, j, table[i][j]); + } + // printTable(table); + } + } + return s1.substring(r.i - r.max, r.i); + } + + private Result updateResultIfNeeded(Result r, int i, int j, int v) { + if(r.max > v) + return r; + return Result.of(i, j, v); + } + + private static final class Result { + private int i, j; + private int max; + private Result(int i, int j, int max) { + this.i = i; + this.j = j; + this.max = max; + } + public static Result of(int i, int j, int max) { + return new Result(i, j, max); + } + } +} diff --git a/pochi-api/src/main/java/module-info.java b/pochi-api/src/main/java/module-info.java index 30cf358c..8e046bf6 100644 --- a/pochi-api/src/main/java/module-info.java +++ b/pochi-api/src/main/java/module-info.java @@ -60,8 +60,9 @@ exports jp.cafebabe.birthmarks.entities.elements; exports jp.cafebabe.birthmarks.extractors; exports jp.cafebabe.birthmarks.pairs; + exports jp.cafebabe.birthmarks.utils; - uses jp.cafebabe.birthmarks.comparators.ComparatorBuilder; + uses jp.cafebabe.birthmarks.comparators.ComparatorBuilder; uses jp.cafebabe.birthmarks.extractors.ExtractorBuilder; uses jp.cafebabe.birthmarks.pairs.PairMatcherBuilder; } diff --git a/pochi-api/src/test/java/jp/cafebabe/birthmarks/entities/BirthmarksTest.java b/pochi-api/src/test/java/jp/cafebabe/birthmarks/entities/BirthmarksTest.java index 0211a8b1..cda35b8e 100644 --- a/pochi-api/src/test/java/jp/cafebabe/birthmarks/entities/BirthmarksTest.java +++ b/pochi-api/src/test/java/jp/cafebabe/birthmarks/entities/BirthmarksTest.java @@ -7,6 +7,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -68,4 +69,16 @@ public void testAppend() throws Exception{ assertThat(list.get(2).metadata().toString(), is("c3,source3,hoge1")); assertThat(list.get(3).metadata().toString(), is("o1,otherSource,hoge1")); } + + @Test + public void testUnify() throws Exception { + Optional> optionalResult = birthmarks.unify(); + if(optionalResult.isEmpty()) + throw new InternalError(); + Birthmark result = optionalResult.get(); + Elements elements = result.elements(); + + assertThat(result.className(), is(new ClassName("source"))); + assertThat(elements.size(), is(9)); + } } diff --git a/pochi-api/src/test/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstringTest.java b/pochi-api/src/test/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstringTest.java new file mode 100644 index 00000000..ad68372e --- /dev/null +++ b/pochi-api/src/test/java/jp/cafebabe/birthmarks/utils/LongestCommonSubstringTest.java @@ -0,0 +1,15 @@ +package jp.cafebabe.birthmarks.utils; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.junit.Test; + +public class LongestCommonSubstringTest { + @Test + public void basicTest() { + assertThat(LongestCommonSubstring.of("abcdxyz", "xyzabcd"), is("abcd")); + assertThat(LongestCommonSubstring.of("abracadabra", "open sesame"), is("a")); + assertThat(LongestCommonSubstring.of("aaaa", "bbb"), is("")); + } +}