Java tutorial
/* * Copyright 2017 (c) Stein Eldar Johnsen * * 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 net.morimekta.idltool.cmd; import net.morimekta.console.args.ArgumentParser; import net.morimekta.console.chr.Color; import net.morimekta.diff.Change; import net.morimekta.diff.DiffLines; import net.morimekta.diff.Operation; import net.morimekta.idltool.IdlTool; import net.morimekta.idltool.IdlUtils; import net.morimekta.idltool.meta.Meta; import net.morimekta.idltool.meta.Remote; import net.morimekta.util.Strings; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.errors.GitAPIException; import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.util.ArrayList; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import static net.morimekta.util.io.IOUtils.readString; /** * Interactively manage branches. */ public class RemoteStatus extends Command { private final boolean diff; public RemoteStatus(ArgumentParser parent, boolean diff) { super(parent); this.diff = diff; } @Override public ArgumentParser makeParser() { if (diff) { return new ArgumentParser(getParent(), "diff", "Shows file diff between local idl files and remote."); } else { return new ArgumentParser(getParent(), "status", "Shows status diff between local idl files and remote."); } } @Override public void execute(IdlTool idlTool) throws IOException, GitAPIException { // Part 1: Check for localRemoteName in repository. File localIDL = idlTool.getLocalIdl(); boolean first = true; File localRemoteDir = new File(localIDL, idlTool.getLocalRemoteName()); if (Files.exists(localRemoteDir.toPath())) { Meta publishedMeta = idlTool.getRepositoryMeta(idlTool.getIdl().getUpstreamRepository()); if (publishedMeta == null) { throw new IllegalStateException("Upstream repository does not have valid meta.json"); } Remote publishedRemote = publishedMeta.getRemotes().get(idlTool.getLocalRemoteName()); if (publishedRemote == null) { // assume nothing is published. publishedRemote = Remote.builder().build(); } File publishedDir = new File(idlTool.getRepositoryGitCacheIdl(idlTool.getIdl().getUpstreamRepository()), idlTool.getLocalRemoteName()); Map<String, String> localSha1sums = IdlUtils.buildSha1Sums(localRemoteDir.toPath()); first = showStatus(true, "local " + idlTool.getLocalRemoteName(), localRemoteDir, localSha1sums, publishedDir, publishedRemote.getShasums()); } Meta localMeta = idlTool.getLocalMeta(); for (Map.Entry<String, Remote> remoteEntry : localMeta.getRemotes().entrySet()) { String remoteName = remoteEntry.getKey(); if (idlTool.getLocalRemoteName().equals(remoteName)) { continue; } String repository = idlTool.getRemoteRepository(remoteName); if (repository == null) { System.out.println("No repository for remote " + remoteName); continue; } Remote remoteRemote = idlTool.getRepositoryMeta(repository).getRemotes().get(remoteName); if (remoteRemote == null) { throw new IllegalStateException("No remote remote"); } File localDir = new File(localIDL, remoteName); Map<String, String> localSha1Sums = IdlUtils.buildSha1Sums(localDir.toPath()); File publishedDir = new File(idlTool.getRepositoryGitCacheIdl(repository), remoteName); Map<String, String> publishedSha1Sums = remoteRemote.getShasums(); first = showStatus(first, "remote " + remoteName, publishedDir, publishedSha1Sums, localDir, localSha1Sums); } } /** * Show standard status for a remote. * * @param first If this is the first remote to print diffs. * @param sourceSha1sums The remote file to sha1sum map. * @param targetSha1sums The local file to sha1sum map. * @return If the next remote is the first to print diffs. */ private boolean showStatus(boolean first, @Nonnull String remoteName, @Nonnull File sourceDirectory, @Nonnull Map<String, String> sourceSha1sums, @Nonnull File targetDirectory, @Nonnull Map<String, String> targetSha1sums) throws IOException { Set<String> removedFiles = new TreeSet<>(targetSha1sums.keySet()); removedFiles.removeAll(sourceSha1sums.keySet()); Set<String> addedFiles = new TreeSet<>(sourceSha1sums.keySet()); addedFiles.removeAll(targetSha1sums.keySet()); Set<String> updatedFiles = new TreeSet<>(sourceSha1sums.keySet()); updatedFiles.removeAll(addedFiles); updatedFiles = updatedFiles.stream().filter(f -> !targetSha1sums.get(f).equals(sourceSha1sums.get(f))) .collect(Collectors.toSet()); Set<String> allFiles = new TreeSet<>(); allFiles.addAll(removedFiles); allFiles.addAll(addedFiles); allFiles.addAll(updatedFiles); if (allFiles.size() == 0) { return first; } int longestName = allFiles.stream().mapToInt(String::length).max().orElse(0); int diffSeparatorLength = Math.max(72, longestName + 6 + remoteName.length()); if (!first) { System.out.println(); } System.out.println(String.format("%sUpdates on %s%s", Color.BOLD, remoteName, Color.CLEAR)); for (String file : allFiles) { String paddedFile = StringUtils.rightPad(file, longestName); File sourceFile = new File(sourceDirectory, file); File targetFile = new File(targetDirectory, file); if (diff) { System.out.println(); System.out.println(Color.DIM + Strings.times("#", diffSeparatorLength) + Color.CLEAR); paddedFile = remoteName + "/" + paddedFile; } if (removedFiles.contains(file)) { System.out.println(String.format(" %s%s%s (%sD%s)%s", Color.YELLOW, paddedFile, Color.CLEAR, Color.RED, Color.CLEAR, getDiffStats(sourceFile, targetFile))); } else if (addedFiles.contains(file)) { System.out.println(String.format(" %s%s%s (%sA%s)%s", Color.YELLOW, paddedFile, Color.CLEAR, Color.GREEN, Color.CLEAR, getDiffStats(sourceFile, targetFile))); } else { System.out.println(String.format(" %s%s%s %s", Color.YELLOW, paddedFile, Color.CLEAR, getDiffStats(sourceFile, targetFile))); } if (diff) { System.out.println(Color.DIM + Strings.times("-", diffSeparatorLength) + Color.CLEAR); printDiffLines(sourceFile, targetFile); System.out.println(Color.DIM + Strings.times("#", diffSeparatorLength) + Color.CLEAR); } } return false; } /** * Get line diff string, so that it shows what updates are done if target * is overwritten by source file. * * @param source Source file. * @param target Target file. * @return Diff line or empty. * @throws IOException If file reading failed. */ private String getDiffStats(File source, File target) throws IOException { if (!source.exists()) { // no source file, all lines are removed. return String.format(" (%s+%s%s)", Color.RED, countLines(target), Color.CLEAR); } if (!target.exists()) { // no target file, all lines are added. return String.format(" (%s+%s%s)", Color.GREEN, countLines(source), Color.CLEAR); } // What to do about target to get to source content. DiffLines diffLines = new DiffLines(readFile(target), readFile(source)); long inserts = diffLines.getChangeList().stream().filter(c -> c.operation == Operation.INSERT).count(); long deletes = diffLines.getChangeList().stream().filter(c -> c.operation == Operation.DELETE).count(); if (inserts == 0) { return String.format(" (%s-%s%s)", Color.RED, deletes, Color.CLEAR); } else if (deletes == 0) { return String.format(" (%s+%s%s)", Color.GREEN, inserts, Color.CLEAR); } return String.format(" (%s+%s%s,%s-%s%s)", Color.GREEN, inserts, Color.CLEAR, Color.RED, deletes, Color.CLEAR); } /** * Get line diff string, so that it shows what updates are done if target * is overwritten by source file. * * @param source Source file. * @param target Target file. * @return Diff line or empty. * @throws IOException If file reading failed. */ private void printDiffLines(File source, File target) throws IOException { if (!source.exists()) { // no source file, all lines are removed. for (String line : readLines(target)) { System.out.println(Color.RED + "- " + line + Color.CLEAR); } } else if (!target.exists()) { // no target file, all lines are added. for (String line : readLines(source)) { System.out.println(Color.GREEN + "+ " + line + Color.CLEAR); } } else { // What to do about target to get to source content. DiffLines diffLines = new DiffLines(readFile(target), readFile(source)); int skippedLines = 0; int sourceLineNo = 0; int targetLineNo = 0; ArrayList<Change> changes = new ArrayList<>(diffLines.getChangeList()); for (int i = 0; i < changes.size(); ++i) { Change ch = changes.get(i); switch (ch.operation) { case EQUAL: { ++sourceLineNo; ++targetLineNo; { // print if line (-1..-3) before is not equal. if (i > 0) { Change bef = changes.get(i - 1); if (bef.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } if (i > 1) { Change bef = changes.get(i - 2); if (bef.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } if (i > 2) { Change bef = changes.get(i - 3); if (bef.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } } { // never skip 1 line. if (i > 3 && i < (changes.size() - 4)) { Change bef = changes.get(i - 4); Change aft = changes.get(i + 4); if (bef.operation != Operation.EQUAL && aft.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } } { // print if line (+1..+3) after is not equal. if (i < (changes.size() - 3)) { Change aft = changes.get(i + 3); if (aft.operation != Operation.EQUAL) { if (skippedLines > 0) { System.out.println(Color.CYAN + "@@ -" + sourceLineNo + " +" + targetLineNo + " @@" + Color.CLEAR + Color.DIM + " -- (skipped " + skippedLines + " lines)" + Color.CLEAR); } System.out.println(" " + ch.text); skippedLines = 0; continue; } } if (i < (changes.size() - 2)) { Change aft = changes.get(i + 2); if (aft.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } if (i < (changes.size() - 1)) { Change aft = changes.get(i + 1); if (aft.operation != Operation.EQUAL) { System.out.println(" " + ch.text); continue; } } } ++skippedLines; break; } case DELETE: { ++sourceLineNo; skippedLines = 0; System.out.println(Color.RED + "-" + ch.text + Color.CLEAR); break; } case INSERT: { ++targetLineNo; skippedLines = 0; System.out.println(Color.GREEN + "+" + ch.text + Color.CLEAR); break; } } } } } private int countLines(File file) throws IOException { return readLines(file).length; } private String[] readLines(File file) throws IOException { // remove the last line if empty, e.g. trailing newline vs no trailing newline. return readFile(file).replaceAll("\r?\n$", "").split("\r?\n"); } private String readFile(File file) throws IOException { try (FileInputStream fis = new FileInputStream(file.getCanonicalFile().getAbsoluteFile())) { return readString(new BufferedReader(new InputStreamReader(fis))); } } }