001 package org.sonar.ide.wsclient;
002
003 import org.apache.commons.lang.StringUtils;
004 import org.slf4j.Logger;
005 import org.slf4j.LoggerFactory;
006 import org.sonar.ide.api.SourceCodeDiff;
007 import org.sonar.ide.api.SourceCodeDiffEngine;
008 import org.sonar.wsclient.services.Source;
009
010 /**
011 * Actually this is an implementation of heuristic algorithm - magic happens here.
012 *
013 * @author Evgeny Mandrikov
014 */
015 public class SimpleSourceCodeDiffEngine implements SourceCodeDiffEngine {
016 private static final Logger LOG = LoggerFactory.getLogger(SimpleSourceCodeDiffEngine.class);
017
018 public static SourceCodeDiffEngine getInstance() {
019 return new SimpleSourceCodeDiffEngine();
020 }
021
022 public SourceCodeDiff diff(String local, String remote) {
023 return diff(split(local), split(remote));
024 }
025
026 /**
027 * {@inheritDoc}
028 */
029 public SourceCodeDiff diff(String[] local, String remote[]) {
030 SourceCodeDiff result = new SourceCodeDiff();
031
032 int[] hashCodes = getHashCodes(local);
033 // Works for O(S*L) time, where S - number of lines on server and L - number of lines in working copy.
034 for (int i = 0; i < remote.length; i++) {
035 int originalLine = i + 1;
036 int newLine = internalMatch(remote[i], hashCodes, originalLine);
037 if (newLine != -1) {
038 result.map(originalLine, newLine);
039 }
040 }
041
042 return result;
043 }
044
045 /**
046 * Currently this method just compares hash codes (see {@link #getHashCode(String)}).
047 *
048 * @return -1 if not found
049 */
050 private int internalMatch(String originalSourceLine, int[] hashCodes, int originalLine) {
051 int newLine = -1;
052 int originalHashCode = getHashCode(originalSourceLine);
053 // line might not exists in working copy
054 if (originalLine - 1 < hashCodes.length) {
055 if (hashCodes[originalLine - 1] == originalHashCode) {
056 newLine = originalLine;
057 }
058 }
059 for (int i = 0; i < hashCodes.length; i++) {
060 if (hashCodes[i] == originalHashCode) {
061 if (newLine != -1 && newLine != originalLine) {
062 // may be more than one match, but we take into account only first
063 LOG.warn("Found more than one match for line '{}'", originalSourceLine);
064 break;
065 }
066 newLine = i + 1;
067 }
068 }
069 return newLine;
070 }
071
072 public static String[] split(String text) {
073 return StringUtils.splitPreserveAllTokens(text, '\n');
074 }
075
076 /**
077 * Returns hash code for specified string after removing whitespaces.
078 *
079 * @param str string
080 * @return hash code for specified string after removing whitespaces
081 */
082 static int getHashCode(String str) {
083 if (str == null) {
084 return 0;
085 }
086 return StringUtils.deleteWhitespace(str).hashCode();
087 }
088
089 /**
090 * Returns hash codes for specified strings after removing whitespaces.
091 *
092 * @param str strings
093 * @return hash codes for specified strings after removing whitespaces
094 */
095 private static int[] getHashCodes(String[] str) {
096 int[] hashCodes = new int[str.length];
097 for (int i = 0; i < str.length; i++) {
098 hashCodes[i] = getHashCode(str[i]);
099 }
100 return hashCodes;
101 }
102
103 public static String[] getLines(Source source) {
104 String[] remote = new String[source.getLinesById().lastKey()];
105 for (int i = 0; i < remote.length; i++) {
106 remote[i] = source.getLine(i + 1);
107 if (remote[i] == null) {
108 remote[i] = "";
109 }
110 }
111 return remote;
112 }
113
114 }