Java tutorial
/* * Copyright (C) 2016 Jorge Ruesga * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ruesga.rview.tasks; import android.content.Context; import android.graphics.Typeface; import android.os.AsyncTask; import android.support.v4.content.ContextCompat; import android.support.v4.util.Pair; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import com.ruesga.rview.R; import com.ruesga.rview.gerrit.model.BlameInfo; import com.ruesga.rview.gerrit.model.CommentInfo; import com.ruesga.rview.gerrit.model.DiffContentInfo; import com.ruesga.rview.gerrit.model.DiffInfo; import com.ruesga.rview.gerrit.model.RangeInfo; import com.ruesga.rview.misc.AndroidHelper; import com.ruesga.rview.misc.Formatter; import com.ruesga.rview.misc.StringHelper; import com.ruesga.rview.widget.DiffView; import com.ruesga.rview.widget.DiffView.DiffInfoModel; import com.ruesga.rview.widget.DiffView.SkipLineModel; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class AsyncTextDiffProcessor extends AsyncTask<Void, Void, List<DiffView.AbstractModel>> { public interface OnTextDiffProcessEndedListener { void onTextDiffProcessEnded(List<DiffView.AbstractModel> model); } public static final int SKIPPED_LINES = 10; private final Pattern HIGHLIGHT_TRAIL_SPACES_PATTERN = Pattern.compile("( )+$", Pattern.MULTILINE); private final Context mContext; private final int mMode; private final boolean mIsBinary; private final DiffContentInfo[] mDiffs; private final Pair<List<CommentInfo>, List<CommentInfo>> mComments; private final Pair<List<CommentInfo>, List<CommentInfo>> mDrafts; private final Pair<List<BlameInfo>, List<BlameInfo>> mBlames; private final boolean mHighlightTabs; private final boolean mHighlightTrailingWhitespaces; private final boolean mHighlightIntralineDiffs; private final OnTextDiffProcessEndedListener mCallback; public AsyncTextDiffProcessor(Context context, int mode, DiffInfo diff, Pair<List<CommentInfo>, List<CommentInfo>> comments, Pair<List<CommentInfo>, List<CommentInfo>> drafts, Pair<List<BlameInfo>, List<BlameInfo>> blames, boolean highlightTabs, boolean highlightTrailingWhitespaces, boolean highlightIntralineDiffs, OnTextDiffProcessEndedListener cb) { mContext = context; mMode = mode; mIsBinary = diff.binary; mDiffs = diff.content; mComments = comments; mDrafts = drafts; mBlames = blames; mHighlightTabs = highlightTabs; mHighlightTrailingWhitespaces = highlightTrailingWhitespaces; mHighlightIntralineDiffs = highlightIntralineDiffs; mCallback = cb; } @Override protected List<DiffView.AbstractModel> doInBackground(Void... params) { return processDrafts(processComments(processDiffs())); } @Override protected void onPostExecute(List<DiffView.AbstractModel> model) { mCallback.onTextDiffProcessEnded(model); } private List<DiffView.AbstractModel> processDiffs() { List<DiffView.AbstractModel> model; if (mMode == DiffView.SIDE_BY_SIDE_MODE) { model = processSideBySideDiffs(); } else { model = processUnifiedDiffs(); } if (!model.isEmpty()) { // Process blames processBlames(model); // Process hidden lines (show lines with non-visible comments) processHiddenLines(model); // Add a decorator line model.add(new DiffView.DecoratorModel()); } return model; } private List<DiffView.AbstractModel> processSideBySideDiffs() { final List<DiffView.AbstractModel> model = new ArrayList<>(); addBinaryAdviseIfNeeded(model); if (mDiffs == null) { return model; } int lineNumberA = 0; int lineNumberB = 0; final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); final int noColor = ContextCompat.getColor(mContext, android.R.color.transparent); final int addedBgColor = ContextCompat.getColor(mContext, R.color.diffAddedBackgroundColor); final int addedFgColor = ContextCompat.getColor(mContext, R.color.diffAddedForegroundColor); final int deletedBgColor = ContextCompat.getColor(mContext, R.color.diffDeletedBackgroundColor); final int deletedFgColor = ContextCompat.getColor(mContext, R.color.diffDeletedForegroundColor); boolean noDiffs = mDiffs.length == 1 && mDiffs[0].a == null && mDiffs[0].b == null; int j = 0; for (DiffContentInfo diff : mDiffs) { if (diff.ab != null) { // Unchanged lines int[] p = processUnchangedLines(diff, model, j, lineNumberA, lineNumberB, noColor, noDiffs); lineNumberA = p[0]; lineNumberB = p[1]; } else { int posA = 0; int posB = 0; int count = Math.max(diff.a == null ? 0 : diff.a.length, diff.b == null ? 0 : diff.b.length); for (int i = 0; i < count; i++) { DiffInfoModel m = new DiffInfoModel(); m.colorA = noColor; m.colorB = noColor; if (diff.a != null && i < diff.a.length) { String line = diff.a[i]; m.a = ++lineNumberA; m.lineNumberA = String.valueOf(m.a); if (diff.editA != null) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); if (mHighlightIntralineDiffs) { int s2 = 0; for (ArrayList<Integer> intra : diff.editA) { int s1 = s2 + intra.get(0); s2 = s1 + intra.get(1); int l = posA + line.length(); if ((s1 >= posA && s1 <= l) || (s2 >= posA && s2 <= l) || (s1 <= posA && s2 >= l)) { span.setSpan(new BackgroundColorSpan(deletedFgColor), Math.max(posA, s1) - posA, Math.min(l, s2) - posA, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } m.lineA = span; } else { // No intraline data, but it still could differ at start or at end processNoIntralineDataA(diff, m, line, deletedFgColor); } m.colorA = deletedBgColor; posA += line.length() + 1; } if (diff.b != null && i < diff.b.length) { String line = diff.b[i]; m.b = ++lineNumberB; m.lineNumberB = String.valueOf(m.b); if (diff.editB != null) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); if (mHighlightIntralineDiffs) { int s2 = 0; for (ArrayList<Integer> intra : diff.editB) { int s1 = s2 + intra.get(0); s2 = s1 + intra.get(1); int l = posB + line.length(); if ((s1 >= posB && s1 <= l) || (s2 >= posB && s2 <= l) || (s1 <= posB && s2 >= l)) { span.setSpan(new BackgroundColorSpan(addedFgColor), Math.max(posB, s1) - posB, Math.min(l, s2) - posB, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } m.lineB = span; } else { // No intraline data, but it still could differ at start or at end processNoIntralineDataB(diff, m, line, addedFgColor); } m.colorB = addedBgColor; posB += line.length() + 1; } processHighlights(m); model.add(m); } } j++; } return model; } private List<DiffView.AbstractModel> processUnifiedDiffs() { final List<DiffView.AbstractModel> model = new ArrayList<>(); addBinaryAdviseIfNeeded(model); if (mDiffs == null) { return model; } int lineNumberA = 0; int lineNumberB = 0; final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); final int noColor = ContextCompat.getColor(mContext, android.R.color.transparent); final int addedBgColor = ContextCompat.getColor(mContext, R.color.diffAddedBackgroundColor); final int addedFgColor = ContextCompat.getColor(mContext, R.color.diffAddedForegroundColor); final int deletedBgColor = ContextCompat.getColor(mContext, R.color.diffDeletedBackgroundColor); final int deletedFgColor = ContextCompat.getColor(mContext, R.color.diffDeletedForegroundColor); boolean noDiffs = mDiffs.length == 1 && mDiffs[0].a == null && mDiffs[0].b == null; int j = 0; for (DiffContentInfo diff : mDiffs) { if (diff.ab != null) { // Unchanged lines int[] p = processUnchangedLines(diff, model, j, lineNumberA, lineNumberB, noColor, noDiffs); lineNumberA = p[0]; lineNumberB = p[1]; } else { if (diff.a != null) { int pos = 0; for (String line : diff.a) { DiffInfoModel m = new DiffInfoModel(); m.a = ++lineNumberA; m.lineNumberA = String.valueOf(m.a); if (diff.editA != null) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); if (mHighlightIntralineDiffs) { int s2 = 0; for (ArrayList<Integer> intra : diff.editA) { int s1 = s2 + intra.get(0); s2 = s1 + intra.get(1); int l = pos + line.length(); if ((s1 >= pos && s1 <= l) || (s2 >= pos && s2 <= l) || (s1 <= pos && s2 >= l)) { span.setSpan(new BackgroundColorSpan(deletedFgColor), Math.max(pos, s1) - pos, Math.min(l, s2) - pos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } m.lineA = span; } else { // No intraline data, but it still could differ at start or at end processNoIntralineDataA(diff, m, line, deletedFgColor); } m.colorA = deletedBgColor; m.colorB = noColor; processHighlights(m); model.add(m); pos += line.length() + 1; } } if (diff.b != null) { int pos = 0; for (String line : diff.b) { DiffInfoModel m = new DiffInfoModel(); m.b = ++lineNumberB; m.lineNumberB = String.valueOf(m.b); if (diff.editB != null) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); if (mHighlightIntralineDiffs) { int s2 = 0; for (ArrayList<Integer> intra : diff.editB) { int s1 = s2 + intra.get(0); s2 = s1 + intra.get(1); int l = pos + line.length(); if ((s1 >= pos && s1 <= l) || (s2 >= pos && s2 <= l) || (s1 <= pos && s2 >= l)) { span.setSpan(new BackgroundColorSpan(addedFgColor), Math.max(pos, s1) - pos, Math.min(l, s2) - pos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } m.lineB = span; } else { // No intraline data, but it still could differ at start or at end processNoIntralineDataB(diff, m, line, addedFgColor); } m.colorA = addedBgColor; m.colorB = noColor; processHighlights(m); model.add(m); pos += line.length() + 1; } } } j++; } return model; } private void addBinaryAdviseIfNeeded(List<DiffView.AbstractModel> model) { if (mIsBinary) { DiffView.AdviseModel advise = new DiffView.AdviseModel(); advise.msg = mContext.getString(R.string.diff_viewer_binary_file); model.add(advise); } } private int[] processUnchangedLines(DiffContentInfo diff, List<DiffView.AbstractModel> model, int j, int lineNumberA, int lineNumberB, int noColor, boolean noDiffs) { if (noDiffs) { DiffView.AdviseModel advise = new DiffView.AdviseModel(); advise.msg = mContext.getString(R.string.diff_viewer_no_diffs); model.add(advise); } int count = diff.ab.length; int skipStartAt = -1, skippedLines = -1; boolean breakAfterSkip = false; if (!noDiffs) { if (j == 0 && diff.ab.length > SKIPPED_LINES) { skipStartAt = 0; skippedLines = count - SKIPPED_LINES - skipStartAt; } else if (j == (mDiffs.length - 1) && diff.ab.length > SKIPPED_LINES) { skipStartAt = SKIPPED_LINES; skippedLines = count - skipStartAt; breakAfterSkip = true; } else if (diff.ab.length > (SKIPPED_LINES * 2)) { skipStartAt = SKIPPED_LINES; skippedLines = count - SKIPPED_LINES - skipStartAt; } } for (int i = 0; i < count; i++) { if (!noDiffs) { if (skipStartAt != -1 && skipStartAt == i) { int startA = lineNumberA + 1; int startB = lineNumberB + 1; lineNumberA += skippedLines; lineNumberB += skippedLines; i += skippedLines; SkipLineModel skip = new SkipLineModel(); skip.msg = mContext.getResources().getQuantityString(R.plurals.skipped_lines, skippedLines, skippedLines); skip.skippedLines = new DiffInfoModel[skippedLines]; for (int k = i - skippedLines, l = 0; k < i; k++, l++) { DiffInfoModel m = new DiffInfoModel(); m.a = startA + l; m.b = startB + l; m.lineNumberA = String.valueOf(m.a); m.lineNumberB = String.valueOf(m.b); if (mMode == DiffView.SIDE_BY_SIDE_MODE) { m.lineA = m.lineB = processHighlights(diff.ab[k]); } else { m.lineA = processHighlights(diff.ab[k]); } m.colorA = m.colorB = noColor; skip.skippedLines[l] = m; } model.add(skip); if (breakAfterSkip) { break; } } } String line = diff.ab[i]; DiffInfoModel m = new DiffInfoModel(); m.a = ++lineNumberA; m.b = ++lineNumberB; m.lineNumberA = String.valueOf(m.a); m.lineNumberB = String.valueOf(m.b); m.lineA = m.lineB = prepareTabs(line); m.colorA = m.colorB = noColor; processHighlights(m); model.add(m); } return new int[] { lineNumberA, lineNumberB }; } private void processNoIntralineDataA(DiffContentInfo diff, DiffInfoModel m, String line, int color) { final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); if (diff.a != null && diff.b != null && diff.a.length == 1 && diff.b.length == 1) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); int z = diff.a[0].indexOf(diff.b[0]); if (z != -1) { if (z > 0) { span.setSpan(new BackgroundColorSpan(color), 0, z, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (z + diff.b[0].length() < diff.a[0].length()) { z = z + diff.b[0].length(); span.setSpan(new BackgroundColorSpan(color), z, diff.a[0].length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } m.lineA = span; } else { m.lineA = prepareTabs(line); } } private void processNoIntralineDataB(DiffContentInfo diff, DiffInfoModel m, String line, int color) { final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); if (diff.a != null && diff.b != null && diff.a.length == 1 && diff.b.length == 1) { Spannable span = spannableFactory.newSpannable(prepareTabs(line)); int z = diff.b[0].indexOf(diff.a[0]); if (z != -1) { if (z > 0) { span.setSpan(new BackgroundColorSpan(color), 0, z, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (z + diff.a[0].length() < diff.b[0].length()) { z = z + diff.a[0].length(); span.setSpan(new BackgroundColorSpan(color), z, diff.b[0].length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } m.lineB = span; } else { m.lineB = prepareTabs(line); } } private void processHighlights(DiffInfoModel model) { if (model.lineA != null) { model.lineA = processHighlightTrailingSpaces(processHighlightTabs(model.lineA)); } if (model.lineB != null) { model.lineB = processHighlightTrailingSpaces(processHighlightTabs(model.lineB)); } } private CharSequence processHighlights(CharSequence line) { return processHighlightTrailingSpaces(processHighlightTabs(line)); } private CharSequence processHighlightTabs(CharSequence text) { if (!mHighlightTabs || !text.toString().contains(StringHelper.NON_PRINTABLE_CHAR)) { return text; } int color = ContextCompat.getColor(mContext, R.color.diffHighlightColor); SpannableStringBuilder ssb = new SpannableStringBuilder(text); String line = text.toString(); int index = line.length(); while ((index = line.lastIndexOf(StringHelper.NON_PRINTABLE_CHAR, index)) != -1) { ssb.replace(index, index + 1, "\u00BB "); ssb.setSpan(new ForegroundColorSpan(color), index, index + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new StyleSpan(Typeface.BOLD), index, index + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); index--; } return ssb; } private CharSequence processHighlightTrailingSpaces(CharSequence text) { if (!mHighlightTrailingWhitespaces) { return text; } int color = ContextCompat.getColor(mContext, R.color.diffHighlightColor); final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); String line = text.toString(); final Matcher matcher = HIGHLIGHT_TRAIL_SPACES_PATTERN.matcher(line); if (matcher.find()) { int start = matcher.start(); int end = matcher.end(); Spannable span = spannableFactory.newSpannable(text); span.setSpan(new BackgroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return span; } return text; } private String prepareTabs(String line) { return line.replaceAll("\t", StringHelper.NON_PRINTABLE_CHAR); } private List<DiffView.AbstractModel> processComments(List<DiffView.AbstractModel> model) { if (mComments != null) { // Comments on A if (mComments.first != null) { addCommentsToModel(model, mComments.first, true, false); } // Comments on B if (mComments.second != null) { addCommentsToModel(model, mComments.second, false, false); } } return model; } private List<DiffView.AbstractModel> processDrafts(List<DiffView.AbstractModel> model) { if (mDrafts != null) { // Comments on A if (mDrafts.first != null) { addCommentsToModel(model, mDrafts.first, true, true); } // Comments on B if (mDrafts.second != null) { addCommentsToModel(model, mDrafts.second, false, true); } } return model; } private void addCommentsToModel(List<DiffView.AbstractModel> model, List<CommentInfo> comments, boolean isA, boolean isDraft) { if (comments == null) { return; } int count = comments.size(); for (int i = 0; i < count; i++) { CommentInfo comment = comments.get(i); boolean isLeft = comment.patchSet == 0 || isA; if (comment.line == null && comment.range == null) { // File comment if (mMode == DiffView.UNIFIED_MODE) { DiffView.CommentModel commentModel = new DiffView.CommentModel(); commentModel.diff = null; commentModel.isDraft = isDraft; commentModel.commentA = comment; int pos = findNextPositionWithoutComment(model, -1); model.add(pos == -1 ? 0 : pos, commentModel); } else { int reusablePos = findReusableCommentView(model, -1, isLeft); if (reusablePos != -1) { DiffView.CommentModel commentModel = (DiffView.CommentModel) model.get(reusablePos); commentModel.isDraft = isDraft; if (isLeft) { commentModel.commentA = comment; } else { commentModel.commentB = comment; } } else { DiffView.CommentModel commentModel = new DiffView.CommentModel(); commentModel.isDraft = isDraft; if (isLeft) { commentModel.commentA = comment; } else { commentModel.commentB = comment; } int pos = findNextPositionWithoutComment(model, -1); model.add(pos == -1 ? 0 : pos, commentModel); } } continue; } // We don't support comment range yet, so skip this comment if (comment.line == null) { continue; } int pos = findLineInModel(model, isLeft, comment.line); if (pos != -1) { if (mMode == DiffView.UNIFIED_MODE) { DiffInfoModel diff = (DiffInfoModel) model.get(findDiffForComment(model, pos)); DiffView.CommentModel commentModel = new DiffView.CommentModel(); commentModel.diff = diff; commentModel.isDraft = isDraft; commentModel.commentA = comment; int nextPos = findNextPositionWithoutComment(model, pos); if (nextPos != -1) { model.add(nextPos, commentModel); } else { model.add(pos + 1, commentModel); } } else { int reusablePos = findReusableCommentView(model, pos, isLeft); if (reusablePos != -1) { DiffInfoModel diff = (DiffInfoModel) model.get(findDiffForComment(model, reusablePos)); DiffView.CommentModel commentModel = (DiffView.CommentModel) model.get(reusablePos); commentModel.diff = diff; commentModel.isDraft = isDraft; if (isLeft) { commentModel.commentA = comment; } else { commentModel.commentB = comment; } } else { DiffInfoModel diff = (DiffInfoModel) model.get(findDiffForComment(model, pos)); DiffView.CommentModel commentModel = new DiffView.CommentModel(); commentModel.diff = diff; commentModel.isDraft = isDraft; if (isLeft) { commentModel.commentA = comment; } else { commentModel.commentB = comment; } int nextPos = findNextPositionWithoutComment(model, pos); if (nextPos != -1) { model.add(nextPos, commentModel); } else { model.add(pos + 1, commentModel); } } } } } } private int findLineInModel(List<DiffView.AbstractModel> model, boolean isA, int line) { int count = model.size(); for (int i = 0; i < count; i++) { DiffView.AbstractModel m = model.get(i); if (m instanceof DiffInfoModel) { DiffInfoModel diff = (DiffInfoModel) m; if (isA && diff.a == line) { return i; } else if (!isA && diff.b == line) { return i; } } } return -1; } private int findDiffForComment(List<DiffView.AbstractModel> model, int pos) { for (int i = pos; i >= 0; i--) { if (model.get(i) instanceof DiffInfoModel) { return i; } } return -1; } private int findReusableCommentView(List<DiffView.AbstractModel> model, int pos, boolean isA) { int count = model.size(); for (int i = pos + 1; i < count; i++) { DiffView.AbstractModel m = model.get(i); if (!(m instanceof DiffView.CommentModel)) { break; } DiffView.CommentModel comment = (DiffView.CommentModel) m; if ((isA && comment.commentA == null && comment.commentB != null) || (!isA && comment.commentB == null && comment.commentA != null)) { return i; } } return -1; } private int findNextPositionWithoutComment(List<DiffView.AbstractModel> model, int pos) { int count = model.size(); for (int i = pos + 1; i < count; i++) { DiffView.AbstractModel m = model.get(i); if (!(m instanceof DiffView.CommentModel)) { return i; } } return -1; } private void processHiddenLines(List<DiffView.AbstractModel> model) { int count = model.size(); for (int i = 0; i < count; i++) { DiffView.AbstractModel line = model.get(i); if (line instanceof SkipLineModel) { SkipLineModel skip = (SkipLineModel) line; int c = skip.skippedLines.length; for (int j = 0; j < c; j++) { if (hasCommentOrDraftInSkippedLine(skip.skippedLines[j])) { // Add the needed skipped lines int n = 0; int from = Math.max(0, j - SKIPPED_LINES + 1); for (int m = from; m <= j; m++, n++) { model.add(i + n + 1, skip.skippedLines[m]); } int to = Math.min(skip.skippedLines.length - 1, j + SKIPPED_LINES); for (int m = j + 1; m <= to; m++, n++) { model.add(i + n + 1, skip.skippedLines[m]); } // Add the new skip marker int next = to + 1; if (next < skip.skippedLines.length) { int length = skip.skippedLines.length - next; DiffInfoModel[] copy = new DiffInfoModel[length]; SkipLineModel newSkip = new SkipLineModel(); System.arraycopy(skip.skippedLines, next, copy, 0, length); newSkip.skippedLines = copy; newSkip.msg = mContext.getResources().getQuantityString(R.plurals.skipped_lines, newSkip.skippedLines.length, newSkip.skippedLines.length); model.add(i + n + 1, newSkip); } // Remove or update the previous marker if (from == 0) { model.remove(i); } else { // Update the marker DiffInfoModel[] copy = new DiffInfoModel[from]; System.arraycopy(skip.skippedLines, 0, copy, 0, from); skip.skippedLines = copy; skip.msg = mContext.getResources().getQuantityString(R.plurals.skipped_lines, skip.skippedLines.length, skip.skippedLines.length); } // Recompute model counters count = model.size(); break; } } } } } @SuppressWarnings("RedundantIfStatement") private boolean hasCommentOrDraftInSkippedLine(DiffInfoModel diff) { if (mComments != null && mComments.first != null && hasComment(diff.a, mComments.first)) { return true; } if (mComments != null && mComments.second != null && hasComment(diff.b, mComments.second)) { return true; } if (mDrafts != null && mDrafts.first != null && hasComment(diff.a, mDrafts.first)) { return true; } if (mDrafts != null && mDrafts.second != null && hasComment(diff.b, mDrafts.second)) { return true; } return false; } private boolean hasComment(int diffLine, List<CommentInfo> comments) { for (CommentInfo c : comments) { if (c.line != null && c.line == diffLine) { return true; } } return false; } private void processBlames(List<DiffView.AbstractModel> model) { if (mBlames == null) { return; } final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, AndroidHelper.getCurrentLocale(mContext)); if (mBlames.first != null) { for (BlameInfo blame : mBlames.first) { int start = 0; for (RangeInfo range : blame.ranges) { Pair<Integer, DiffInfoModel> p = findDiffModelByLine(model, range.start, start, true); if (p != null) { start = p.first; String commit = Formatter.toShortenCommit(blame.id); String date = df.format(new Date(blame.time * 1000L)); //Unix time p.second.blameA = mContext.getString(R.string.blame_format, commit, date, blame.author); } } } } if (mBlames.second != null) { for (BlameInfo blame : mBlames.second) { int start = 0; for (RangeInfo range : blame.ranges) { Pair<Integer, DiffInfoModel> p = findDiffModelByLine(model, range.start, start, false); if (p != null) { start = p.first; String commit = Formatter.toShortenCommit(blame.id); String date = df.format(new Date(blame.time * 1000L)); //Unix time p.second.blameB = mContext.getString(R.string.blame_format, commit, date, blame.author); } } } } } private Pair<Integer, DiffInfoModel> findDiffModelByLine(List<DiffView.AbstractModel> model, int line, int start, boolean isLeft) { int count = model.size(); for (int i = start; i < count; i++) { DiffView.AbstractModel m = model.get(i); if (m instanceof DiffInfoModel) { if ((isLeft && ((DiffInfoModel) m).a == line) || (!isLeft && ((DiffInfoModel) m).b == line)) { return new Pair<>(i, (DiffInfoModel) m); } } else if (m instanceof SkipLineModel) { if (((SkipLineModel) m).skippedLines != null) { for (DiffInfoModel m1 : ((SkipLineModel) m).skippedLines) { if ((isLeft && m1.a == line) || (!isLeft && m1.b == line)) { return new Pair<>(i, m1); } } } } } return null; } }